void handle_connection (struct conn_s *connptr, union sockaddr_union* addr)
{

#define HC_FAIL() \
        do {handle_connection_failure(connptr, got_headers); goto done;} \
        while(0)

        int got_headers = 0, fd = connptr->client_fd;
        size_t i;
        struct request_s *request = NULL;
        orderedmap hashofheaders = NULL;

        char sock_ipaddr[IP_LENGTH];
        char peer_ipaddr[IP_LENGTH];

        getpeer_information (addr, peer_ipaddr, sizeof(peer_ipaddr));

        if (config->bindsame)
                getsock_ip (fd, sock_ipaddr);

        log_message (LOG_CONN, config->bindsame ?
                     "Connect (file descriptor %d): %s at [%s]" :
                     "Connect (file descriptor %d): %s",
                     fd, peer_ipaddr, sock_ipaddr);

        if(!conn_init_contents (connptr, peer_ipaddr,
                                   config->bindsame ? sock_ipaddr : NULL)) {
                close (fd);
                return;
        }

        set_socket_timeout(fd);

        if (connection_loops (addr))  {
                log_message (LOG_CONN,
                             "Prevented endless loop (file descriptor %d): %s",
                             fd, peer_ipaddr);

                indicate_http_error(connptr, 400, "Bad Request",
                                    "detail",
                                    "You tried to connect to the "
                                    "machine the proxy is running on",
                                    NULL);
                HC_FAIL();
        }

        if (check_acl (peer_ipaddr, addr, config->access_list) <= 0) {
                update_stats (STAT_DENIED);
                indicate_http_error (connptr, 403, "Access denied",
                                     "detail",
                                     "The administrator of this proxy has not configured "
                                     "it to service requests from your host.",
                                     NULL);
                HC_FAIL();
        }

        if (read_request_line (connptr) < 0) {
                update_stats (STAT_BADCONN);
                goto done;
        }

        /*
         * The "hashofheaders" store the client's headers.
         */
        hashofheaders = orderedmap_create (HEADER_BUCKETS);
        if (hashofheaders == NULL) {
                update_stats (STAT_BADCONN);
                indicate_http_error (connptr, 503, "Internal error",
                                     "detail",
                                     "An internal server error occurred while processing "
                                     "your request. Please contact the administrator.",
                                     NULL);
                HC_FAIL();
        }

        /*
         * Get all the headers from the client in a big hash.
         */
        if (get_all_headers (connptr->client_fd, hashofheaders) < 0) {
                log_message (LOG_WARNING,
                             "Could not retrieve all the headers from the client");
                indicate_http_error (connptr, 400, "Bad Request",
                                     "detail",
                                     "Could not retrieve all the headers from "
                                     "the client.", NULL);
                update_stats (STAT_BADCONN);
                HC_FAIL();
        }
        got_headers = 1;

        if (config->basicauth_list != NULL) {
                char *authstring;
                int failure = 1, stathost_connect = 0;
                authstring = orderedmap_find (hashofheaders, "proxy-authorization");

                if (!authstring && config->stathost) {
                        authstring = orderedmap_find (hashofheaders, "host");
                        if (authstring && !strncmp(authstring, config->stathost, strlen(config->stathost))) {
                                authstring = orderedmap_find (hashofheaders, "authorization");
                                stathost_connect = 1;
                        } else authstring = 0;
                }

                if (!authstring) {
                        if (stathost_connect) goto e401;
                        update_stats (STAT_DENIED);
                        indicate_http_error (connptr, 407, "Proxy Authentication Required",
                                             "detail",
                                             "This proxy requires authentication.",
                                             NULL);
                        HC_FAIL();
                }
                if ( /* currently only "basic" auth supported */
                        (strncmp(authstring, "Basic ", 6) == 0 ||
                         strncmp(authstring, "basic ", 6) == 0) &&
                        basicauth_check (config->basicauth_list, authstring + 6) == 1)
                                failure = 0;
                if(failure) {
e401:
                        update_stats (STAT_DENIED);
                        indicate_http_error (connptr, 401, "Unauthorized",
                                             "detail",
                                             "The administrator of this proxy has not configured "
                                             "it to service requests from you.",
                                             NULL);
                        HC_FAIL();
                }
                orderedmap_remove (hashofheaders, "proxy-authorization");
        }

        /*
         * Add any user-specified headers (AddHeader directive) to the
         * outgoing HTTP request.
         */
        if (config->add_headers)
        for (i = 0; i < sblist_getsize (config->add_headers); i++) {
                http_header_t *header = sblist_get (config->add_headers, i);

                orderedmap_append (hashofheaders, header->name, header->value);
        }

        request = process_request (connptr, hashofheaders);
        if (!request) {
                if (!connptr->show_stats) {
                        update_stats (STAT_BADCONN);
                }
                HC_FAIL();
        }

        connptr->upstream_proxy = UPSTREAM_HOST (request->host);
        if (connptr->upstream_proxy != NULL) {
                if (connect_to_upstream (connptr, request) < 0) {
                        HC_FAIL();
                }
        } else {
                connptr->server_fd = opensock (request->host, request->port,
                                               connptr->server_ip_addr);
                if (connptr->server_fd < 0) {
                        indicate_http_error (connptr, 500, "Unable to connect",
                                             "detail",
                                             PACKAGE_NAME " "
                                             "was unable to connect to the remote web server.",
                                             "error", strerror (errno), NULL);
                        HC_FAIL();
                }

                log_message (LOG_CONN,
                             "Established connection to host \"%s\" using "
                             "file descriptor %d.", request->host,
                             connptr->server_fd);

                if (!connptr->connect_method)
                        establish_http_connection (connptr, request);
        }

        if (process_client_headers (connptr, hashofheaders) < 0) {
                update_stats (STAT_BADCONN);
                log_message (LOG_INFO,
                             "process_client_headers failed: %s. host \"%s\" using "
                             "file descriptor %d.", strerror(errno),
                             request->host,
                             connptr->server_fd);

                HC_FAIL();
        }

        if (!connptr->connect_method || UPSTREAM_IS_HTTP(connptr)) {
                if (process_server_headers (connptr) < 0) {
                        update_stats (STAT_BADCONN);
                        log_message (LOG_INFO,
                             "process_server_headers failed: %s. host \"%s\" using "
                             "file descriptor %d.", strerror(errno),
                             request->host,
                             connptr->server_fd);

                        HC_FAIL();
                }
        } else {
                if (send_connect_method_response (connptr) < 0) {
                        log_message (LOG_ERR,
                                     "handle_connection: Could not send CONNECT"
                                     " method greeting to client.");
                        update_stats (STAT_BADCONN);
                        HC_FAIL();
                }
        }

        relay_connection (connptr);

        log_message (LOG_INFO,
                     "Closed connection between local client (fd:%d) "
                     "and remote client (fd:%d)",
                     connptr->client_fd, connptr->server_fd);

done:
        free_request_struct (request);
        orderedmap_destroy (hashofheaders);
        conn_destroy_contents (connptr);
        return;
#undef HC_FAIL
}


prevent junk from showing up in error page in invalid requests

fixes #457

process_client_headers (struct conn_s *connptr, orderedmap hashofheaders)
{
        static const char *skipheaders[] = {
                "host",
                "keep-alive",
                "proxy-connection",
                "te",
                "trailers",
                "upgrade"
        };
        int i;
        size_t iter;
        int ret = 0;

        char *data, *header;

        /*
         * Don't send headers if there's already an error, if the request was
         * a stats request, or if this was a CONNECT method (unless upstream
         * http proxy is in use.)
         */
        if (connptr->server_fd == -1 || connptr->show_stats
            || (connptr->connect_method && ! UPSTREAM_IS_HTTP(connptr))) {
                log_message (LOG_INFO,
                             "Not sending client headers to remote machine");
                return 0;
        }

        /*
         * See if there is a "Content-Length" header.  If so, again we need
         * to do a bit of processing.
         */
        connptr->content_length.client = get_content_length (hashofheaders);

        /* Check whether client sends chunked data. */
        if (connptr->content_length.client == -1 && is_chunked_transfer (hashofheaders))
                connptr->content_length.client = -2;

        /*
         * See if there is a "Connection" header.  If so, we need to do a bit
         * of processing. :)
         */
        remove_connection_headers (hashofheaders);

        /*
         * Delete the headers listed in the skipheaders list
         */
        for (i = 0; i != (sizeof (skipheaders) / sizeof (char *)); i++) {
                orderedmap_remove (hashofheaders, skipheaders[i]);
        }

        /* Send, or add the Via header */
        ret = write_via_header (connptr->server_fd, hashofheaders,
                                connptr->protocol.major,
                                connptr->protocol.minor);
        if (ret < 0) {
                indicate_http_error (connptr, 503,
                                     "Could not send data to remote server",
                                     "detail",
                                     "A network error occurred while "
                                     "trying to write data to the remote web server.",
                                     NULL);
                goto PULL_CLIENT_DATA;
        }

        /*
         * Output all the remaining headers to the remote machine.
         */
        iter = 0;
        while((iter = orderedmap_next(hashofheaders, iter, &data, &header))) {
                if (!is_anonymous_enabled (config)
                    || anonymous_search (config, data) > 0) {
                        ret =
                            write_message (connptr->server_fd,
                                           "%s: %s\r\n", data, header);
                        if (ret < 0) {
                                indicate_http_error (connptr, 503,
                                                     "Could not send data to remote server",
                                                     "detail",
                                                     "A network error occurred while "
                                                     "trying to write data to the "
                                                     "remote web server.",
                                                     NULL);
                                goto PULL_CLIENT_DATA;
                        }
                }
        }
#if defined(XTINYPROXY_ENABLE)
        if (config->add_xtinyproxy)
                add_xtinyproxy_header (connptr);
#endif

        /* Write the final "blank" line to signify the end of the headers */
        if (safe_write (connptr->server_fd, "\r\n", 2) < 0)
                return -1;

        /*
         * Spin here pulling the data from the client.
         */
PULL_CLIENT_DATA:
        if (connptr->content_length.client > 0) {
                ret = pull_client_data (connptr,
                                        connptr->content_length.client, 1);
        } else if (connptr->content_length.client == -2)
                ret = pull_client_data_chunked (connptr);

        return ret;
}


static void strip_username_password (char *host)
{
        char *p;

        assert (host);
        assert (strlen (host) > 0);

        if ((p = strchr (host, '@')) == NULL)
                return;

        /*
         * Move the pointer past the "@" and then copy from that point
         * until the NUL to the beginning of the host buffer.
         */
        p++;
        while (*p)
                *host++ = *p++;
        *host = '\0';
}


static int is_chunked_transfer (orderedmap hashofheaders)
{
        char *data;
        data = orderedmap_find (hashofheaders, "transfer-encoding");
        return data ? !strcmp (data, "chunked") : 0;
}


write_via_header (int fd, orderedmap hashofheaders,
                  unsigned int major, unsigned int minor)
{
        char hostname[512];
        char *data;
        int ret;

        if (config->disable_viaheader) {
                ret = 0;
                goto done;
        }

        if (config->via_proxy_name) {
                strlcpy (hostname, config->via_proxy_name, sizeof (hostname));
        } else if (gethostname (hostname, sizeof (hostname)) < 0) {
                strlcpy (hostname, "unknown", 512);
        }

        /*
         * See if there is a "Via" header.  If so, again we need to do a bit
         * of processing.
         */
        data = orderedmap_find (hashofheaders, "via");
        if (data) {
                ret = write_message (fd,
                                     "Via: %s, %hu.%hu %s (%s/%s)\r\n",
                                     data, major, minor, hostname, PACKAGE,
                                     VERSION);

                orderedmap_remove (hashofheaders, "via");
        } else {
                ret = write_message (fd,
                                     "Via: %hu.%hu %s (%s/%s)\r\n",
                                     major, minor, hostname, PACKAGE, VERSION);
        }

done:
        return ret;
}


static int extract_url (const char *url, int default_port,
                        struct request_s *request)
{
        char *p;
        int port;

        /* Split the URL on the slash to separate host from path */
        p = strchr (url, '/');
        if (p != NULL) {
                int len;
                len = p - url;
                request->host = (char *) safemalloc (len + 1);
                memcpy (request->host, url, len);
                request->host[len] = '\0';
                request->path = safestrdup (p);
        } else {
                request->host = safestrdup (url);
                request->path = safestrdup ("/");
        }

        if (!request->host || !request->path)
                goto ERROR_EXIT;

        /* Remove the username/password if they're present */
        strip_username_password (request->host);

        /* Find a proper port in www.site.com:8001 URLs */
        port = strip_return_port (request->host);
        request->port = (port != 0) ? port : default_port;

        /* Remove any surrounding '[' and ']' from IPv6 literals */
        p = strrchr (request->host, ']');
        if (p && (*(request->host) == '[')) {
                memmove(request->host, request->host + 1,
                        strlen(request->host) - 2);
                *p = '\0';
                p--;
                *p = '\0';
        }

        return 0;

ERROR_EXIT:
        if (request->host)
                safefree (request->host);
        if (request->path)
                safefree (request->path);

        return -1;
}


static int pull_client_data (struct conn_s *connptr, long int length, int iehack)
{
        char *buffer;
        ssize_t len;
        int ret;

        buffer =
            (char *) safemalloc (min (MAXBUFFSIZE, (unsigned long int) length));
        if (!buffer)
                return -1;

        do {
                len = safe_read (connptr->client_fd, buffer,
                                 min (MAXBUFFSIZE, (unsigned long int) length));
                if (len <= 0)
                        goto ERROR_EXIT;

                if (!connptr->error_variables) {
                        if (safe_write (connptr->server_fd, buffer, len) < 0)
                                goto ERROR_EXIT;
                }

                length -= len;
        } while (length > 0);

        if (iehack) {
                /*
                 * BUG FIX: Internet Explorer will leave two bytes (carriage
                 * return and line feed) at the end of a POST message.  These
                 * need to be eaten for tinyproxy to work correctly.
                 */
                ret = socket_nonblocking (connptr->client_fd);
                if (ret != 0) {
                        log_message(LOG_ERR, "Failed to set the client socket "
                                    "to non-blocking: %s", strerror(errno));
                        goto ERROR_EXIT;
                }

                len = recv (connptr->client_fd, buffer, 2, MSG_PEEK);

                ret = socket_blocking (connptr->client_fd);
                if (ret != 0) {
                        log_message(LOG_ERR, "Failed to set the client socket "
                                    "to blocking: %s", strerror(errno));
                        goto ERROR_EXIT;
                }

                if (len < 0 && errno != EAGAIN)
                        goto ERROR_EXIT;

                if ((len == 2) && CHECK_CRLF (buffer, len)) {
                        ssize_t bytes_read;

                        bytes_read = read (connptr->client_fd, buffer, 2);
                        if (bytes_read == -1) {
                                log_message
                                        (LOG_WARNING,
                                         "Could not read two bytes from POST message");
                        }
                }
        }

        safefree (buffer);
        return 0;

ERROR_EXIT:
        safefree (buffer);
        return -1;
}


static int strip_return_port (char *host)
{
        char *ptr1;
        char *ptr2;
        int port;

        ptr1 = strrchr (host, ':');
        if (ptr1 == NULL)
                return 0;

        /* Check for IPv6 style literals */
        ptr2 = strchr (ptr1, ']');
        if (ptr2 != NULL)
                return 0;

        *ptr1++ = '\0';
        if (sscanf (ptr1, "%d", &port) != 1)    /* one conversion required */
                return 0;
        return port;
}


static struct request_s *process_request (struct conn_s *connptr,
                                          orderedmap hashofheaders)
{
        char *url;
        struct request_s *request;
        int ret, skip_trans;
        size_t request_len;

        skip_trans = 0;

        /* NULL out all the fields so frees don't cause segfaults. */
        request =
            (struct request_s *) safecalloc (1, sizeof (struct request_s));
        if (!request)
                return NULL;

        request_len = strlen (connptr->request_line) + 1;

        request->method = (char *) safemalloc (request_len);
        url = (char *) safemalloc (request_len);
        request->protocol = (char *) safemalloc (request_len);

        if (!request->method || !url || !request->protocol) {
                goto fail;
        }

        /* zero-terminate the strings so they don't contain junk in error page */
        request->method[0] = url[0] = request->protocol[0] = 0;

        ret = sscanf (connptr->request_line, "%[^ ] %[^ ] %[^ ]",
                      request->method, url, request->protocol);

        if (ret == 2 && !strcasecmp (request->method, "GET")) {
                request->protocol[0] = 0;

                /* Indicate that this is a HTTP/0.9 GET request */
                connptr->protocol.major = 0;
                connptr->protocol.minor = 9;
        } else if (ret == 3 && !strncasecmp (request->protocol, "HTTP/", 5)) {
                /*
                 * Break apart the protocol and update the connection
                 * structure.
                 */
                ret = sscanf (request->protocol + 5, "%u.%u",
                              &connptr->protocol.major,
                              &connptr->protocol.minor);

                /*
                 * If the conversion doesn't succeed, drop down below and
                 * send the error to the user.
                 */
                if (ret != 2)
                        goto BAD_REQUEST_ERROR;
        } else {
BAD_REQUEST_ERROR:
                log_message (LOG_ERR,
                             "process_request: Bad Request on file descriptor %d",
                             connptr->client_fd);
                indicate_http_error (connptr, 400, "Bad Request",
                                     "detail", "Request has an invalid format",
                                     "url", url, NULL);
                goto fail;
        }

#ifdef REVERSE_SUPPORT
        if (config->reversepath_list != NULL) {
                /*
                 * Rewrite the URL based on the reverse path.  After calling
                 * reverse_rewrite_url "url" can be freed since we either
                 * have the newly rewritten URL, or something failed and
                 * we'll be closing anyway.
                 */
                char *reverse_url;
                int reverse_status;

                reverse_url = reverse_rewrite_url (connptr, hashofheaders, url, &reverse_status);

                if (reverse_url != NULL) {
                        if (reverse_status == 301) {
                                char buf[PATH_MAX];
                                snprintf (buf, sizeof buf, "Location: %s\r\n", reverse_url);
                                send_http_headers (connptr, 301, "Moved Permanently", buf);
                                goto fail;
                        }
                        safefree (url);
                        url = reverse_url;
                        skip_trans = 1;
                } else if (config->reverseonly) {
                        log_message (LOG_ERR,
                                     "Bad request, no mapping for '%s' found",
                                     url);
                        indicate_http_error (connptr, 400, "Bad Request",
                                             "detail", "No mapping found for "
                                             "requested url", "url", url, NULL);
                        goto fail;
                }
        }
#endif

        if (strncasecmp (url, "http://", 7) == 0
            || (UPSTREAM_CONFIGURED () && strncasecmp (url, "ftp://", 6) == 0))
        {
                char *skipped_type = strstr (url, "//") + 2;

                if (extract_url (skipped_type, HTTP_PORT, request) < 0) {
                        indicate_http_error (connptr, 400, "Bad Request",
                                             "detail", "Could not parse URL",
                                             "url", url, NULL);
                        goto fail;
                }
        } else if (strcmp (request->method, "CONNECT") == 0) {
                if (extract_url (url, HTTP_PORT_SSL, request) < 0) {
                        indicate_http_error (connptr, 400, "Bad Request",
                                             "detail", "Could not parse URL",
                                             "url", url, NULL);
                        goto fail;
                }

                /* Verify that the port in the CONNECT method is allowed */
                if (!check_allowed_connect_ports (request->port,
                                                  config->connect_ports))
                {
                        indicate_http_error (connptr, 403, "Access violation",
                                             "detail",
                                             "The CONNECT method not allowed "
                                             "with the port you tried to use.",
                                             "url", url, NULL);
                        log_message (LOG_INFO,
                                     "Refused CONNECT method on port %d",
                                     request->port);
                        goto fail;
                }

                connptr->connect_method = TRUE;
        } else {
#ifdef TRANSPARENT_PROXY
                if (!skip_trans) {
                        if (!do_transparent_proxy
                            (connptr, hashofheaders, request, config, &url))
                                goto fail;
                } else
#endif
                {
                indicate_http_error (connptr, 501, "Not Implemented",
                                     "detail",
                                     "Unknown method or unsupported protocol.",
                                     "url", url, NULL);
                log_message (LOG_INFO, "Unknown method (%s) or protocol (%s)",
                             request->method, url);
                goto fail;
                }
        }

#ifdef FILTER_ENABLE
        /*
         * Filter restricted domains/urls
         */
        if (config->filter) {
                int fu = config->filter_opts & FILTER_OPT_URL;
                ret = filter_run (fu ? url : request->host);

                if (ret) {
                        update_stats (STAT_DENIED);

                        log_message (LOG_NOTICE,
                                     "Proxying refused on filtered %s \"%s\"",
                                     fu ? "url" : "domain",
                                     fu ? url : request->host);

                        indicate_http_error (connptr, 403, "Filtered",
                                             "detail",
                                             "The request you made has been filtered",
                                             "url", url, NULL);
                        goto fail;
                }
        }
#endif

        /*
         * Check to see if they're requesting the stat host
         */
        if (config->stathost && strcmp (config->stathost, request->host) == 0) {
                log_message (LOG_NOTICE, "Request for the stathost.");
                connptr->show_stats = TRUE;
                goto fail;
        }

        safefree (url);

        return request;

fail:
        safefree (url);
        free_request_struct (request);
        return NULL;
}


static int send_connect_method_response (struct conn_s *connptr)
{
        return write_message (connptr->client_fd,
                              "HTTP/1.%u 200 Connection established\r\n"
                              "Proxy-agent: " PACKAGE "/" VERSION "\r\n"
                              "\r\n", connptr->protocol.major != 1 ? 0 :
                                      connptr->protocol.minor);
}


static void relay_connection (struct conn_s *connptr)
{
        int ret;
        ssize_t bytes_received;

        for (;;) {
                pollfd_struct fds[2] = {0};
                fds[0].fd = connptr->client_fd;
                fds[1].fd = connptr->server_fd;

                if (buffer_size (connptr->sbuffer) > 0)
                        fds[0].events |= MYPOLL_WRITE;
                if (buffer_size (connptr->cbuffer) > 0)
                        fds[1].events |= MYPOLL_WRITE;
                if (buffer_size (connptr->sbuffer) < MAXBUFFSIZE)
                        fds[1].events |= MYPOLL_READ;
                if (buffer_size (connptr->cbuffer) < MAXBUFFSIZE)
                        fds[0].events |= MYPOLL_READ;

                ret = mypoll(fds, 2, config->idletimeout);

                if (ret == 0) {
                        log_message (LOG_INFO,
                                     "Idle Timeout (after " SELECT_OR_POLL ")");
                                return;
                } else if (ret < 0) {
                        log_message (LOG_ERR,
                                     "relay_connection: " SELECT_OR_POLL "() error \"%s\". "
                                     "Closing connection (client_fd:%d, server_fd:%d)",
                                     strerror (errno), connptr->client_fd,
                                     connptr->server_fd);
                        return;
                }

                if (fds[1].revents & MYPOLL_READ) {
                        bytes_received =
                            read_buffer (connptr->server_fd, connptr->sbuffer);
                        if (bytes_received < 0)
                                break;

                        connptr->content_length.server -= bytes_received;
                        if (connptr->content_length.server == 0)
                                break;
                }
                if ((fds[0].revents & MYPOLL_READ)
                    && read_buffer (connptr->client_fd, connptr->cbuffer) < 0) {
                        break;
                }
                if ((fds[1].revents & MYPOLL_WRITE)
                    && write_buffer (connptr->server_fd, connptr->cbuffer) < 0) {
                        break;
                }
                if ((fds[0].revents & MYPOLL_WRITE)
                    && write_buffer (connptr->client_fd, connptr->sbuffer) < 0) {
                        break;
                }
        }

        while (buffer_size (connptr->sbuffer) > 0) {
                if (write_buffer (connptr->client_fd, connptr->sbuffer) < 0)
                        break;
        }
        shutdown (connptr->client_fd, SHUT_WR);

        /*
         * Try to send any remaining data to the server if we can.
         */
        ret = socket_blocking (connptr->server_fd);
        if (ret != 0) {
                log_message(LOG_ERR,
                            "Failed to set server socket to blocking: %s",
                            strerror(errno));
                return;
        }

        while (buffer_size (connptr->cbuffer) > 0) {
                if (write_buffer (connptr->server_fd, connptr->cbuffer) < 0)
                        break;
        }

        return;
}


get_request_entity(struct conn_s *connptr)
{
        int ret;
        pollfd_struct fds[1] = {0};

        fds[0].fd = connptr->client_fd;
        fds[0].events |= MYPOLL_READ;

        ret = mypoll(fds, 1, config->idletimeout);

        if (ret == -1) {
                log_message (LOG_ERR,
                             "Error calling " SELECT_OR_POLL " on client fd %d: %s",
                             connptr->client_fd, strerror(errno));
        } else if (ret == 0) {
               log_message (LOG_INFO, "no entity");
        } else if (ret == 1 && (fds[0].revents & MYPOLL_READ)) {
                ssize_t nread;
                nread = read_buffer (connptr->client_fd, connptr->cbuffer);
                if (nread < 0) {
                        log_message (LOG_ERR,
                                     "Error reading readable client_fd %d (%s)",
                                     connptr->client_fd, strerror(errno));
                        ret = -1;
                } else {
                        log_message (LOG_INFO,
                                     "Read request entity of %ld bytes",
                                     (long) nread);
                        ret = 0;
                }
        } else {
                log_message (LOG_ERR, "strange situation after " SELECT_OR_POLL ": "
                             "ret = %d, but client_fd (%d) is not readable...",
                             ret, connptr->client_fd);
                ret = -1;
        }

        return ret;
}


static long get_content_length (orderedmap hashofheaders)
{
        char *data;
        long content_length = -1;

        data = orderedmap_find (hashofheaders, "content-length");

        if (data)
                content_length = atol (data);

        return content_length;
}


Prevent segfault in `GetSessionHandle{,V2}`.

In eager mode, session state is null.

PiperOrigin-RevId: 332548597
Change-Id: If094812c2e094044220b9ba28f7d7601be042f38

  explicit GetSessionHandleOp(OpKernelConstruction* context)
      : OpKernel(context) {}

In eager mode, TensorFlow before versions 1.15.4, 2.0.3, 2.1.2, 2.2.1 and 2.3.1 does not set the session state. Hence, calling `tf.raw_ops.GetSessionHandle` or `tf.raw_ops.GetSessionHandleV2` results in a null pointer dereference In linked snippet, in eager mode, `ctx->session_state()` returns `nullptr`. Since code immediately dereferences this, we get a segmentation fault. The issue is patched in commit 9a133d73ae4b4664d22bd1aa6d654fec13c52ee1, and is released in TensorFlow versions 1.15.4, 2.0.3, 2.1.2, 2.2.1, or 2.3.1.


  explicit GetSessionTensorOp(OpKernelConstruction* context)
      : OpKernel(context) {}


  explicit DeleteSessionTensorOp(OpKernelConstruction* context)
      : OpKernel(context) {}


  void Compute(OpKernelContext* ctx) override {
    const Tensor& val = ctx->input(0);
    auto session_state = ctx->session_state();
    OP_REQUIRES(ctx, session_state != nullptr,
                errors::FailedPrecondition(
                    "GetSessionHandle called on null session state"));
    int64 id = session_state->GetNewId();
    TensorStore::TensorAndKey tk{val, id, requested_device()};
    OP_REQUIRES_OK(ctx, ctx->tensor_store()->AddTensor(name(), tk));

    Tensor* handle = nullptr;
    OP_REQUIRES_OK(ctx, ctx->allocate_output(0, TensorShape({}), &handle));
    if (ctx->expected_output_dtype(0) == DT_RESOURCE) {
      ResourceHandle resource_handle = MakeResourceHandle<Tensor>(
          ctx, SessionState::kTensorHandleResourceTypeName,
          tk.GetHandle(name()));
      resource_handle.set_maybe_type_name(
          SessionState::kTensorHandleResourceTypeName);
      handle->scalar<ResourceHandle>()() = resource_handle;
    } else {
      // Legacy behavior in V1.
      handle->flat<tstring>().setConstant(tk.GetHandle(name()));
    }
  }


Fix heap out of bounds error in tf.raw_ops.SparseCountSparseOutput shape inference when it is called with invalid inputs, and add a test for it.

PiperOrigin-RevId: 405766415
Change-Id: I77d244ef35f351ef7b6f821efd959cac2c66db24

Status SparseCountSparseOutputShapeFn(InferenceContext *c) {
  ShapeHandle unused;
  TF_RETURN_IF_ERROR(c->WithRank(c->input(0), 2, &unused));
  auto rank = c->Dim(c->input(0), 1);
  auto nvals = c->UnknownDim();
  c->set_output(0, c->Matrix(nvals, rank));  // out.indices
  c->set_output(1, c->Vector(nvals));        // out.values
  c->set_output(2, c->Vector(rank));         // out.dense_shape
  return Status::OK();
}

TensorFlow is an open source platform for machine learning. In affected versions the shape inference functions for `SparseCountSparseOutput` can trigger a read outside of bounds of heap allocated array. The fix will be included in TensorFlow 2.7.0. We will also cherrypick this commit on TensorFlow 2.6.1, TensorFlow 2.5.2, and TensorFlow 2.4.4, as these are also affected and still in supported range.


Status DenseCountSparseOutputShapeFn(InferenceContext *c) {
  auto values = c->input(0);
  auto weights = c->input(1);
  ShapeHandle output;
  auto num_weights = c->NumElements(weights);
  if (c->ValueKnown(num_weights) && c->Value(num_weights) == 0) {
    output = values;
  } else {
    TF_RETURN_IF_ERROR(c->Merge(weights, values, &output));
  }
  auto rank = c->Rank(output);
  auto nvals = c->UnknownDim();
  c->set_output(0, c->Matrix(nvals, rank));  // out.indices
  c->set_output(1, c->Vector(nvals));        // out.values
  c->set_output(2, c->Vector(rank));         // out.dense_shape
  return Status::OK();
}


Status RaggedCountSparseOutputShapeFn(InferenceContext *c) {
  int32_t rank = c->Rank(c->input(1));
  if (rank != c->kUnknownRank) {
    ++rank;  // Add the ragged dimension
  }
  auto nvals = c->UnknownDim();
  c->set_output(0, c->Matrix(nvals, rank));  // out.indices
  c->set_output(1, c->Vector(nvals));        // out.values
  c->set_output(2, c->Vector(rank));         // out.dense_shape
  return Status::OK();
}


inline void FurnaceGUI::patternRow(int i, bool isPlaying, float lineHeight, int chans, int ord, const DivPattern** patCache) {
  static char id[32];
  bool selectedRow=(i>=sel1.y && i<=sel2.y);
  ImGui::TableNextRow(0,lineHeight);
  ImGui::TableNextColumn();
  float cursorPosY=ImGui::GetCursorPos().y-ImGui::GetScrollY();
  // check if the row is visible
  if (cursorPosY<-lineHeight || cursorPosY>ImGui::GetWindowSize().y) {
    return;
  }
  // check if we are in range
  if (ord<0 || ord>=e->song.ordersLen) {
    return;
  }
  if (i<0 || i>=e->song.patLen) {
    return;
  }
  bool isPushing=false;
  ImVec4 activeColor=uiColors[GUI_COLOR_PATTERN_ACTIVE];
  ImVec4 inactiveColor=uiColors[GUI_COLOR_PATTERN_INACTIVE];
  ImVec4 rowIndexColor=uiColors[GUI_COLOR_PATTERN_ROW_INDEX];
  if (e->song.hilightB>0 && !(i%e->song.hilightB)) {
    activeColor=uiColors[GUI_COLOR_PATTERN_ACTIVE_HI2];
    inactiveColor=uiColors[GUI_COLOR_PATTERN_INACTIVE_HI2];
    rowIndexColor=uiColors[GUI_COLOR_PATTERN_ROW_INDEX_HI2];
  } else if (e->song.hilightA>0 && !(i%e->song.hilightA)) {
    activeColor=uiColors[GUI_COLOR_PATTERN_ACTIVE_HI1];
    inactiveColor=uiColors[GUI_COLOR_PATTERN_INACTIVE_HI1];
    rowIndexColor=uiColors[GUI_COLOR_PATTERN_ROW_INDEX_HI1];
  }
  // check overflow highlight
  if (settings.overflowHighlight) {
    if (edit && cursor.y==i) {
      ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,ImGui::GetColorU32(uiColors[GUI_COLOR_EDITING]));
    } else if (isPlaying && oldRow==i) {
      ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_PLAY_HEAD]));
    } else if (e->song.hilightB>0 && !(i%e->song.hilightB)) {
      ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_HI_2]));
    } else if (e->song.hilightA>0 && !(i%e->song.hilightA)) {
      ImGui::TableSetBgColor(ImGuiTableBgTarget_RowBg0,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_HI_1]));
    }
  } else {
    isPushing=true;
    if (edit && cursor.y==i) {
      ImGui::PushStyleColor(ImGuiCol_Header,ImGui::GetColorU32(uiColors[GUI_COLOR_EDITING]));
    } else if (isPlaying && oldRow==i) {
      ImGui::PushStyleColor(ImGuiCol_Header,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_PLAY_HEAD]));
    } else if (e->song.hilightB>0 && !(i%e->song.hilightB)) {
      ImGui::PushStyleColor(ImGuiCol_Header,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_HI_2]));
    } else if (e->song.hilightA>0 && !(i%e->song.hilightA)) {
      ImGui::PushStyleColor(ImGuiCol_Header,ImGui::GetColorU32(uiColors[GUI_COLOR_PATTERN_HI_1]));
    } else {
      isPushing=false;
    }
  }
  // row number
  if (settings.patRowsBase==1) {
    ImGui::TextColored(rowIndexColor," %.2X ",i);
  } else {
    ImGui::TextColored(rowIndexColor,"%3d ",i);
  }
  // for each column
  for (int j=0; j<chans; j++) {
    // check if channel is not hidden
    if (!e->song.chanShow[j]) {
      patChanX[j]=ImGui::GetCursorPosX();
      continue;
    }
    int chanVolMax=e->getMaxVolumeChan(j);
    if (chanVolMax<1) chanVolMax=1;
    const DivPattern* pat=patCache[j];
    ImGui::TableNextColumn();
    patChanX[j]=ImGui::GetCursorPosX();

    // selection highlight flags
    int sel1XSum=sel1.xCoarse*32+sel1.xFine;
    int sel2XSum=sel2.xCoarse*32+sel2.xFine;
    int j32=j*32;
    bool selectedNote=selectedRow && (j32>=sel1XSum && j32<=sel2XSum);
    bool selectedIns=selectedRow && (j32+1>=sel1XSum && j32+1<=sel2XSum);
    bool selectedVol=selectedRow && (j32+2>=sel1XSum && j32+2<=sel2XSum);
    bool cursorNote=(cursor.y==i && cursor.xCoarse==j && cursor.xFine==0);
    bool cursorIns=(cursor.y==i && cursor.xCoarse==j && cursor.xFine==1);
    bool cursorVol=(cursor.y==i && cursor.xCoarse==j && cursor.xFine==2);

    // note
    sprintf(id,"%s##PN_%d_%d",noteName(pat->data[i][0],pat->data[i][1]),i,j);
    if (pat->data[i][0]==0 && pat->data[i][1]==0) {
      ImGui::PushStyleColor(ImGuiCol_Text,inactiveColor);
    } else {
      ImGui::PushStyleColor(ImGuiCol_Text,activeColor);
    }
    if (cursorNote) {
      ImGui::PushStyleColor(ImGuiCol_Header,uiColors[GUI_COLOR_PATTERN_CURSOR]);
      ImGui::PushStyleColor(ImGuiCol_HeaderActive,uiColors[GUI_COLOR_PATTERN_CURSOR_ACTIVE]);
      ImGui::PushStyleColor(ImGuiCol_HeaderHovered,uiColors[GUI_COLOR_PATTERN_CURSOR_HOVER]);
      ImGui::Selectable(id,true,ImGuiSelectableFlags_NoPadWithHalfSpacing,threeChars);
      demandX=ImGui::GetCursorPosX();
      ImGui::PopStyleColor(3);
    } else {
      if (selectedNote) ImGui::PushStyleColor(ImGuiCol_Header,uiColors[GUI_COLOR_PATTERN_SELECTION]);
      ImGui::Selectable(id,isPushing || selectedNote,ImGuiSelectableFlags_NoPadWithHalfSpacing,threeChars);
      if (selectedNote) ImGui::PopStyleColor();
    }
    if (ImGui::IsItemClicked()) {
      startSelection(j,0,i);
    }
    if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) {
      updateSelection(j,0,i);
    }
    ImGui::PopStyleColor();

    // the following is only visible when the channel is not collapsed
    if (!e->song.chanCollapse[j]) {
      // instrument
      if (pat->data[i][2]==-1) {
        ImGui::PushStyleColor(ImGuiCol_Text,inactiveColor);
        sprintf(id,"..##PI_%d_%d",i,j);
      } else {
        if (pat->data[i][2]<0 || pat->data[i][2]>=e->song.insLen) {
          ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_INS_ERROR]);
        } else {
          DivInstrumentType t=e->song.ins[pat->data[i][2]]->type;
          if (t!=DIV_INS_AMIGA && t!=e->getPreferInsType(j)) {
            ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_INS_WARN]);
          } else {
            ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_INS]);
          }
        }
        sprintf(id,"%.2X##PI_%d_%d",pat->data[i][2],i,j);
      }
      ImGui::SameLine(0.0f,0.0f);
      if (cursorIns) {
        ImGui::PushStyleColor(ImGuiCol_Header,uiColors[GUI_COLOR_PATTERN_CURSOR]);
        ImGui::PushStyleColor(ImGuiCol_HeaderActive,uiColors[GUI_COLOR_PATTERN_CURSOR_ACTIVE]);
        ImGui::PushStyleColor(ImGuiCol_HeaderHovered,uiColors[GUI_COLOR_PATTERN_CURSOR_HOVER]);
        ImGui::Selectable(id,true,ImGuiSelectableFlags_NoPadWithHalfSpacing,twoChars);
        demandX=ImGui::GetCursorPosX();
        ImGui::PopStyleColor(3);
      } else {
        if (selectedIns) ImGui::PushStyleColor(ImGuiCol_Header,uiColors[GUI_COLOR_PATTERN_SELECTION]);
        ImGui::Selectable(id,isPushing || selectedIns,ImGuiSelectableFlags_NoPadWithHalfSpacing,twoChars);
        if (selectedIns) ImGui::PopStyleColor();
      }
      if (ImGui::IsItemClicked()) {
        startSelection(j,1,i);
      }
      if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) {
        updateSelection(j,1,i);
      }
      ImGui::PopStyleColor();

      // volume
      if (pat->data[i][3]==-1) {
        sprintf(id,"..##PV_%d_%d",i,j);
        ImGui::PushStyleColor(ImGuiCol_Text,inactiveColor);
      } else {
        int volColor=(pat->data[i][3]*127)/chanVolMax;
        if (volColor>127) volColor=127;
        if (volColor<0) volColor=0;
        sprintf(id,"%.2X##PV_%d_%d",pat->data[i][3],i,j);
        ImGui::PushStyleColor(ImGuiCol_Text,volColors[volColor]);
      }
      ImGui::SameLine(0.0f,0.0f);
      if (cursorVol) {
        ImGui::PushStyleColor(ImGuiCol_Header,uiColors[GUI_COLOR_PATTERN_CURSOR]);
        ImGui::PushStyleColor(ImGuiCol_HeaderActive,uiColors[GUI_COLOR_PATTERN_CURSOR_ACTIVE]);
        ImGui::PushStyleColor(ImGuiCol_HeaderHovered,uiColors[GUI_COLOR_PATTERN_CURSOR_HOVER]);
        ImGui::Selectable(id,true,ImGuiSelectableFlags_NoPadWithHalfSpacing,twoChars);
        demandX=ImGui::GetCursorPosX();
        ImGui::PopStyleColor(3);
      } else {
        if (selectedVol) ImGui::PushStyleColor(ImGuiCol_Header,uiColors[GUI_COLOR_PATTERN_SELECTION]);
        ImGui::Selectable(id,isPushing || selectedVol,ImGuiSelectableFlags_NoPadWithHalfSpacing,twoChars);
        if (selectedVol) ImGui::PopStyleColor();
      }
      if (ImGui::IsItemClicked()) {
        startSelection(j,2,i);
      }
      if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) {
        updateSelection(j,2,i);
      }
      ImGui::PopStyleColor();

      // effects
      for (int k=0; k<e->song.pat[j].effectRows; k++) {
        int index=4+(k<<1);
        bool selectedEffect=selectedRow && (j32+index-1>=sel1XSum && j32+index-1<=sel2XSum);
        bool selectedEffectVal=selectedRow && (j32+index>=sel1XSum && j32+index<=sel2XSum);
        bool cursorEffect=(cursor.y==i && cursor.xCoarse==j && cursor.xFine==index-1);
        bool cursorEffectVal=(cursor.y==i && cursor.xCoarse==j && cursor.xFine==index);

        // effect
        if (pat->data[i][index]==-1) {
          sprintf(id,"..##PE%d_%d_%d",k,i,j);
          ImGui::PushStyleColor(ImGuiCol_Text,inactiveColor);
        } else {
          if (pat->data[i][index]>0xff) {
            sprintf(id,"??##PE%d_%d_%d",k,i,j);
            ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_INVALID]);
          } else {
            const unsigned char data=pat->data[i][index];
            sprintf(id,"%.2X##PE%d_%d_%d",data,k,i,j);
            if (data<0x10) {
              ImGui::PushStyleColor(ImGuiCol_Text,uiColors[fxColors[data]]);
            } else if (data<0x20) {
              ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY]);
            } else if (data<0x30) {
              ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_SYS_SECONDARY]);
            } else if (data<0x48) {
              ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_SYS_PRIMARY]);
            } else if (data<0x90) {
              ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_INVALID]);
            } else if (data<0xa0) {
              ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_MISC]);
            } else if (data<0xc0) {
              ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_INVALID]);
            } else if (data<0xd0) {
              ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_SPEED]);
            } else if (data<0xe0) {
              ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_PATTERN_EFFECT_INVALID]);
            } else {
              ImGui::PushStyleColor(ImGuiCol_Text,uiColors[extFxColors[data-0xe0]]);
            }
          }
        }
        ImGui::SameLine(0.0f,0.0f);
        if (cursorEffect) {
          ImGui::PushStyleColor(ImGuiCol_Header,uiColors[GUI_COLOR_PATTERN_CURSOR]);
          ImGui::PushStyleColor(ImGuiCol_HeaderActive,uiColors[GUI_COLOR_PATTERN_CURSOR_ACTIVE]);
          ImGui::PushStyleColor(ImGuiCol_HeaderHovered,uiColors[GUI_COLOR_PATTERN_CURSOR_HOVER]);
          ImGui::Selectable(id,true,ImGuiSelectableFlags_NoPadWithHalfSpacing,twoChars);
          demandX=ImGui::GetCursorPosX();
          ImGui::PopStyleColor(3);
        } else {
          if (selectedEffect) ImGui::PushStyleColor(ImGuiCol_Header,uiColors[GUI_COLOR_PATTERN_SELECTION]);
          ImGui::Selectable(id,isPushing || selectedEffect,ImGuiSelectableFlags_NoPadWithHalfSpacing,twoChars);
          if (selectedEffect) ImGui::PopStyleColor();
        }
        if (ImGui::IsItemClicked()) {
          startSelection(j,index-1,i);
        }
        if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) {
          updateSelection(j,index-1,i);
        }

        // effect value
        if (pat->data[i][index+1]==-1) {
          sprintf(id,"..##PF%d_%d_%d",k,i,j);
        } else {
          sprintf(id,"%.2X##PF%d_%d_%d",pat->data[i][index+1],k,i,j);
        }
        ImGui::SameLine(0.0f,0.0f);
        if (cursorEffectVal) {
          ImGui::PushStyleColor(ImGuiCol_Header,uiColors[GUI_COLOR_PATTERN_CURSOR]);
          ImGui::PushStyleColor(ImGuiCol_HeaderActive,uiColors[GUI_COLOR_PATTERN_CURSOR_ACTIVE]);
          ImGui::PushStyleColor(ImGuiCol_HeaderHovered,uiColors[GUI_COLOR_PATTERN_CURSOR_HOVER]);
          ImGui::Selectable(id,true,ImGuiSelectableFlags_NoPadWithHalfSpacing,twoChars);
          demandX=ImGui::GetCursorPosX();
          ImGui::PopStyleColor(3);
        } else {
          if (selectedEffectVal) ImGui::PushStyleColor(ImGuiCol_Header,uiColors[GUI_COLOR_PATTERN_SELECTION]);
          ImGui::Selectable(id,isPushing || selectedEffectVal,ImGuiSelectableFlags_NoPadWithHalfSpacing,twoChars);
          if (selectedEffectVal) ImGui::PopStyleColor();
        }
        if (ImGui::IsItemClicked()) {
          startSelection(j,index,i);
        }
        if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenBlockedByActiveItem)) {
          updateSelection(j,index,i);
        }
        ImGui::PopStyleColor();
      }
    }
  }
  if (isPushing) {
    ImGui::PopStyleColor();
  }
  ImGui::TableNextColumn();
  patChanX[chans]=ImGui::GetCursorPosX();
}

A denial of service vulnerability was found in tildearrow Furnace. It has been classified as problematic. This is due to an incomplete fix of CVE-2022-1211. It is possible to initiate the attack remotely but it requires user interaction. The issue got fixed with the patch 0eb02422d5161767e9983bdaa5c429762d3477ce.


inline float randRange(float min, float max) {
  return min+((float)rand()/(float)RAND_MAX)*(max-min);
}


void FurnaceGUI::drawPattern() {
  //int delta0=SDL_GetPerformanceCounter();
  if (nextWindow==GUI_WINDOW_PATTERN) {
    patternOpen=true;
    ImGui::SetNextWindowFocus();
    nextWindow=GUI_WINDOW_NOTHING;
  }
  if (!patternOpen) return;

  bool inhibitMenu=false;
  float scrollX=0;

  if (e->isPlaying() && followPattern) cursor.y=oldRow;
  demandX=0;
  sel1=selStart;
  sel2=selEnd;
  if (sel2.y<sel1.y) {
    sel2.y^=sel1.y;
    sel1.y^=sel2.y;
    sel2.y^=sel1.y;
  }
  if (sel2.xCoarse<sel1.xCoarse) {
    sel2.xCoarse^=sel1.xCoarse;
    sel1.xCoarse^=sel2.xCoarse;
    sel2.xCoarse^=sel1.xCoarse;

    sel2.xFine^=sel1.xFine;
    sel1.xFine^=sel2.xFine;
    sel2.xFine^=sel1.xFine;
  } else if (sel2.xCoarse==sel1.xCoarse && sel2.xFine<sel1.xFine) {
    sel2.xFine^=sel1.xFine;
    sel1.xFine^=sel2.xFine;
    sel2.xFine^=sel1.xFine;
  }
  ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding,ImVec2(0.0f,0.0f));
  if (ImGui::Begin("Pattern",&patternOpen,settings.avoidRaisingPattern?ImGuiWindowFlags_NoBringToFrontOnFocus:0)) {
    //ImGui::SetWindowSize(ImVec2(scrW*dpiScale,scrH*dpiScale));
    patWindowPos=ImGui::GetWindowPos();
    patWindowSize=ImGui::GetWindowSize();
    //char id[32];
    ImGui::PushFont(patFont);
    int ord=e->isPlaying()?oldOrder:e->getOrder();
    oldOrder=e->getOrder();
    int chans=e->getTotalChannelCount();
    int displayChans=0;
    const DivPattern* patCache[DIV_MAX_CHANS];
    for (int i=0; i<chans; i++) {
      if (e->song.chanShow[i]) displayChans++;
    }
    ImGui::PushStyleVar(ImGuiStyleVar_CellPadding,ImVec2(0.0f,0.0f));
    ImGui::PushStyleColor(ImGuiCol_Header,uiColors[GUI_COLOR_PATTERN_SELECTION]);
    ImGui::PushStyleColor(ImGuiCol_HeaderHovered,uiColors[GUI_COLOR_PATTERN_SELECTION_HOVER]);
    ImGui::PushStyleColor(ImGuiCol_HeaderActive,uiColors[GUI_COLOR_PATTERN_SELECTION_ACTIVE]);
    if (ImGui::BeginTable("PatternView",displayChans+2,ImGuiTableFlags_BordersInnerV|ImGuiTableFlags_ScrollX|ImGuiTableFlags_ScrollY|ImGuiTableFlags_NoPadInnerX)) {
      ImGui::TableSetupColumn("pos",ImGuiTableColumnFlags_WidthFixed);
      char chanID[2048];
      float lineHeight=(ImGui::GetTextLineHeight()+2*dpiScale);
      int curRow=e->getRow();
      if (e->isPlaying() && followPattern) updateScroll(curRow);
      if (nextScroll>-0.5f) {
        ImGui::SetScrollY(nextScroll);
        nextScroll=-1.0f;
        nextAddScroll=0.0f;
      }
      if (nextAddScroll!=0.0f) {
        ImGui::SetScrollY(ImGui::GetScrollY()+nextAddScroll);
        nextScroll=-1.0f;
        nextAddScroll=0.0f;
      }
      ImGui::TableSetupScrollFreeze(1,1);
      for (int i=0; i<chans; i++) {
        if (!e->song.chanShow[i]) continue;
        ImGui::TableSetupColumn(fmt::sprintf("c%d",i).c_str(),ImGuiTableColumnFlags_WidthFixed);
      }
      ImGui::TableNextRow();
      ImGui::TableNextColumn();
      if (ImGui::Selectable((extraChannelButtons==2)?" --##ExtraChannelButtons":" ++##ExtraChannelButtons",false,ImGuiSelectableFlags_NoPadWithHalfSpacing,ImVec2(0.0f,lineHeight+1.0f*dpiScale))) {
        if (++extraChannelButtons>2) extraChannelButtons=0;
      }
      if (ImGui::IsItemHovered()) {
        if (extraChannelButtons==2) {
          ImGui::SetTooltip("Pattern names (click to collapse)\nRight-click for visualizer");
        } else if (extraChannelButtons==1) {
          ImGui::SetTooltip("Expanded (click for pattern names)\nRight-click for visualizer");
        } else {
          ImGui::SetTooltip("Compact (click to expand)\nRight-click for visualizer");
        }
      }
      if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
        fancyPattern=!fancyPattern;
        inhibitMenu=true;
        e->enableCommandStream(fancyPattern);
        e->getCommandStream(cmdStream);
        cmdStream.clear();
      }
      for (int i=0; i<chans; i++) {
        if (!e->song.chanShow[i]) continue;
        ImGui::TableNextColumn();
        bool displayTooltip=false;
        if (e->song.chanCollapse[i]) {
          const char* chName=e->getChannelShortName(i);
          if (strlen(chName)>3) {
            snprintf(chanID,2048,"...##_CH%d",i);
          } else {
            snprintf(chanID,2048,"%s##_CH%d",chName,i);
          }
          displayTooltip=true;
        } else {
          const char* chName=e->getChannelName(i);
          size_t chNameLimit=6+4*e->song.pat[i].effectRows;
          if (strlen(chName)>chNameLimit) {
            String shortChName=chName;
            shortChName.resize(chNameLimit-3);
            shortChName+="...";
            snprintf(chanID,2048," %s##_CH%d",shortChName.c_str(),i);
            displayTooltip=true;
          } else {
            snprintf(chanID,2048," %s##_CH%d",chName,i);
          }
        }
        bool muted=e->isChannelMuted(i);
        ImVec4 chanHead=muted?uiColors[GUI_COLOR_CHANNEL_MUTED]:uiColors[GUI_COLOR_CHANNEL_FM+e->getChannelType(i)];
        ImVec4 chanHeadActive=chanHead;
        ImVec4 chanHeadHover=chanHead;
        if (e->keyHit[i]) {
          keyHit[i]=0.2;
          e->keyHit[i]=false;
        }
        if (settings.guiColorsBase) {
          chanHead.x*=1.0-keyHit[i]; chanHead.y*=1.0-keyHit[i]; chanHead.z*=1.0-keyHit[i];
          chanHeadActive.x*=0.5; chanHeadActive.y*=0.5; chanHeadActive.z*=0.5;
          chanHeadHover.x*=0.9-keyHit[i]; chanHeadHover.y*=0.9-keyHit[i]; chanHeadHover.z*=0.9-keyHit[i];
        } else {
          chanHead.x*=0.25+keyHit[i]; chanHead.y*=0.25+keyHit[i]; chanHead.z*=0.25+keyHit[i];
          chanHeadActive.x*=0.8; chanHeadActive.y*=0.8; chanHeadActive.z*=0.8;
          chanHeadHover.x*=0.4+keyHit[i]; chanHeadHover.y*=0.4+keyHit[i]; chanHeadHover.z*=0.4+keyHit[i];
        }
        keyHit[i]-=0.02*60.0*ImGui::GetIO().DeltaTime;
        if (keyHit[i]<0) keyHit[i]=0;
        ImGui::PushStyleColor(ImGuiCol_Header,chanHead);
        ImGui::PushStyleColor(ImGuiCol_HeaderActive,chanHeadActive);
        ImGui::PushStyleColor(ImGuiCol_HeaderHovered,chanHeadHover);
        ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg,ImGui::GetColorU32(chanHead));
        if (muted) ImGui::PushStyleColor(ImGuiCol_Text,uiColors[GUI_COLOR_CHANNEL_MUTED]);
        ImGui::Selectable(chanID,true,ImGuiSelectableFlags_NoPadWithHalfSpacing,ImVec2(0.0f,lineHeight+1.0f*dpiScale));
        if (displayTooltip && ImGui::IsItemHovered()) {
          ImGui::SetTooltip("%s",e->getChannelName(i));
        }
        if (ImGui::IsItemClicked(ImGuiMouseButton_Left)) {
          if (settings.soloAction!=1 && soloTimeout>0 && soloChan==i) {
            e->toggleSolo(i);
            soloTimeout=0;
          } else {
            e->toggleMute(i);
            soloTimeout=20;
            soloChan=i;
          }
        }
        if (muted) ImGui::PopStyleColor();
        ImGui::PopStyleColor(3);
        if (settings.soloAction!=2) if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
          inhibitMenu=true;
          e->toggleSolo(i);
        }
        if (extraChannelButtons==2) {
          DivPattern* pat=e->song.pat[i].getPattern(e->song.orders.ord[i][ord],true);
          ImGui::PushFont(mainFont);
          if (patNameTarget==i) {
            snprintf(chanID,2048,"##PatNameI%d_%d",i,ord);
            ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x-(8.0f*dpiScale));
            ImGui::SetCursorPosX(ImGui::GetCursorPosX()+4.0f*dpiScale);
            ImGui::InputText(chanID,&pat->name);
            if (wantPatName) {
              wantPatName=false;
              ImGui::SetItemDefaultFocus();
              ImGui::SetKeyboardFocusHere(-1);
            } else {
              if (!ImGui::IsItemActive()) {
                patNameTarget=-1;
              }
            }
          } else {
            snprintf(chanID,2048," %s##PatName%d",pat->name.c_str(),i);
            if (ImGui::Selectable(chanID,true,ImGuiSelectableFlags_NoPadWithHalfSpacing,ImVec2(0.0f,lineHeight+1.0f*dpiScale))) {
              patNameTarget=i;
              wantPatName=true;
              snprintf(chanID,2048,"##PatNameI%d_%d",i,ord);
              ImGui::SetActiveID(ImGui::GetID(chanID),ImGui::GetCurrentWindow());
            }
          }
          ImGui::PopFont();
        } else if (extraChannelButtons==1) {
          snprintf(chanID,2048,"%c##_HCH%d",e->song.chanCollapse[i]?'+':'-',i);
          ImGui::SetCursorPosX(ImGui::GetCursorPosX()+4.0f*dpiScale);
          if (ImGui::SmallButton(chanID)) {
            e->song.chanCollapse[i]=!e->song.chanCollapse[i];
          }
          if (!e->song.chanCollapse[i]) {
            ImGui::SameLine();
            snprintf(chanID,2048,"<##_LCH%d",i);
            ImGui::BeginDisabled(e->song.pat[i].effectRows<=1);
            if (ImGui::SmallButton(chanID)) {
              e->song.pat[i].effectRows--;
              if (e->song.pat[i].effectRows<1) e->song.pat[i].effectRows=1;
            }
            ImGui::EndDisabled();
            ImGui::SameLine();
            ImGui::BeginDisabled(e->song.pat[i].effectRows>=8);
            snprintf(chanID,2048,">##_RCH%d",i);
            if (ImGui::SmallButton(chanID)) {
              e->song.pat[i].effectRows++;
              if (e->song.pat[i].effectRows>8) e->song.pat[i].effectRows=8;
            }
            ImGui::EndDisabled();
          }
          ImGui::Spacing();
        }
      }
      ImGui::TableNextColumn();
      if (e->hasExtValue()) {
        ImGui::TextColored(uiColors[GUI_COLOR_EE_VALUE]," %.2X",e->getExtValue());
      }
      float oneCharSize=ImGui::CalcTextSize("A").x;
      threeChars=ImVec2(oneCharSize*3.0f,lineHeight);
      twoChars=ImVec2(oneCharSize*2.0f,lineHeight);
      //ImVec2 oneChar=ImVec2(oneCharSize,lineHeight);
      dummyRows=(ImGui::GetWindowSize().y/lineHeight)/2;
      // オップナー2608 i owe you one more for this horrible code
      // previous pattern
      ImGui::BeginDisabled();
      if (settings.viewPrevPattern) {
        if ((ord-1)>=0) for (int i=0; i<chans; i++) {
          patCache[i]=e->song.pat[i].getPattern(e->song.orders.ord[i][ord-1],true);
        }
        for (int i=0; i<dummyRows-1; i++) {
          patternRow(e->song.patLen+i-dummyRows+1,e->isPlaying(),lineHeight,chans,ord-1,patCache);
        }
      } else {
        for (int i=0; i<dummyRows-1; i++) {
          ImGui::TableNextRow(0,lineHeight);
          ImGui::TableNextColumn();
        }
      }
      ImGui::EndDisabled();
      // active area
      for (int i=0; i<chans; i++) {
        patCache[i]=e->song.pat[i].getPattern(e->song.orders.ord[i][ord],true);
      }
      for (int i=0; i<e->song.patLen; i++) {
        patternRow(i,e->isPlaying(),lineHeight,chans,ord,patCache);
      }
      // next pattern
      ImGui::BeginDisabled();
      if (settings.viewPrevPattern) {
        if ((ord+1)<e->song.ordersLen) for (int i=0; i<chans; i++) {
          patCache[i]=e->song.pat[i].getPattern(e->song.orders.ord[i][ord+1],true);
        }
        for (int i=0; i<=dummyRows; i++) {
          patternRow(i,e->isPlaying(),lineHeight,chans,ord+1,patCache);
        }
      } else {
        for (int i=0; i<=dummyRows; i++) {
          ImGui::TableNextRow(0,lineHeight);
          ImGui::TableNextColumn();
        }
      }
      ImGui::EndDisabled();
      oldRow=curRow;
      if (demandScrollX) {
        int totalDemand=demandX-ImGui::GetScrollX();
        if (totalDemand<80) {
          ImGui::SetScrollX(demandX-200*dpiScale);
        } else if (totalDemand>(ImGui::GetWindowWidth()-200*dpiScale)) {
          ImGui::SetScrollX(demandX+200*dpiScale);
        }
        demandScrollX=false;
      }
      scrollX=ImGui::GetScrollX();
      ImGui::EndTable();
    }

    if (fancyPattern) { // visualizer
      e->getCommandStream(cmdStream);
      ImDrawList* dl=ImGui::GetWindowDrawList();
      ImVec2 off=ImGui::GetWindowPos();

      // commands
      for (DivCommand& i: cmdStream) {
        if (i.cmd==DIV_CMD_PITCH) continue;
        if (i.cmd==DIV_CMD_NOTE_PORTA) continue;
        //if (i.cmd==DIV_CMD_NOTE_ON) continue;
        if (i.cmd==DIV_CMD_PRE_PORTA) continue;
        if (i.cmd==DIV_CMD_PRE_NOTE) continue;
        if (i.cmd==DIV_CMD_SAMPLE_BANK) continue;
        if (i.cmd==DIV_CMD_GET_VOLUME) continue;
        if (i.cmd==DIV_ALWAYS_SET_VOLUME) continue;

        float width=patChanX[i.chan+1]-patChanX[i.chan];
        float speedX=0.0f;
        float speedY=-18.0f;
        float grav=0.6f;
        float frict=1.0f;
        float life=255.0f;
        float lifeSpeed=8.0f;
        float spread=5.0f;
        int num=3;
        const char* partIcon=ICON_FA_MICROCHIP;
        ImU32* color=noteGrad;

        switch (i.cmd) {
          case DIV_CMD_NOTE_ON:
            partIcon=ICON_FA_ASTERISK;
            life=96.0f;
            lifeSpeed=3.0f;
            break;
          case DIV_CMD_LEGATO:
            partIcon=ICON_FA_COG;
            color=insGrad;
            life=64.0f;
            lifeSpeed=2.0f;
            break;
          case DIV_CMD_NOTE_OFF:
          case DIV_CMD_NOTE_OFF_ENV:
          case DIV_CMD_ENV_RELEASE:
            partIcon=ICON_FA_ASTERISK;
            speedX=0.0f;
            speedY=0.0f;
            grav=0.0f;
            life=24.0f;
            lifeSpeed=4.0f;
            break;
          case DIV_CMD_VOLUME: {
            float scaledVol=(float)i.value/(float)e->getMaxVolumeChan(i.chan);
            if (scaledVol>1.0f) scaledVol=1.0f;
            speedY=-18.0f-(10.0f*scaledVol);
            life=128+scaledVol*127;
            partIcon=ICON_FA_VOLUME_UP;
            num=12.0f*pow(scaledVol,2.0);
            color=volGrad;
            break;
          }
          case DIV_CMD_INSTRUMENT: {
            if (lastIns[i.chan]==i.value) {
              num=0;
              break;
            }
            lastIns[i.chan]=i.value;
            speedX=0.0f;
            speedY=0.0f;
            grav=0.0f;
            frict=0.98;
            spread=30.0f;
            life=128.0f;
            lifeSpeed=6.0f;
            color=insGrad;
            num=7+pow(i.value,0.6);
            break;
          }
          case DIV_CMD_PANNING: {
            if (i.value==0) {
              num=0;
              break;
            }
            float ratio=float(((i.value>>4)&15)-(i.value&15))/MAX(((i.value>>4)&15),(i.value&15));
            speedX=-22.0f*sin(ratio*M_PI*0.5);
            speedY=-22.0f*cos(ratio*M_PI*0.5);
            spread=5.0f+fabs(sin(ratio*M_PI*0.5))*7.0f;
            grav=0.0f;
            frict=0.96f;
            if (((i.value>>4)&15)==(i.value&15)) {
              partIcon=ICON_FA_ARROWS_H;
            } else if (ratio>0) {
              partIcon=ICON_FA_ARROW_LEFT;
            } else {
              partIcon=ICON_FA_ARROW_RIGHT;
            }
            num=9;
            color=panGrad;
            break;
          }
          case DIV_CMD_SAMPLE_FREQ:
            speedX=0.0f;
            speedY=0.0f;
            grav=0.0f;
            frict=0.98;
            spread=19.0f;
            life=128.0f;
            lifeSpeed=3.0f;
            color=sysCmd2Grad;
            num=10+pow(i.value,0.6);
            break;
          default:
            //printf("unhandled %d\n",i.cmd);
            color=sysCmd1Grad;
            break;
        }

        for (int j=0; j<num; j++) {
          particles.push_back(Particle(
            color,
            partIcon,
            off.x+patChanX[i.chan]+fmod(rand(),width)-scrollX,
            off.y+(ImGui::GetWindowHeight()*0.5f)+randRange(0,patFont->FontSize),
            (speedX+randRange(-spread,spread))*0.5*dpiScale,
            (speedY+randRange(-spread,spread))*0.5*dpiScale,
            grav,
            frict,
            life-randRange(0,8),
            lifeSpeed
          ));
        }
      }

      float frameTime=ImGui::GetIO().DeltaTime*60.0f;

      // note slides
      ImVec2 arrowPoints[7];
      if (e->isPlaying()) for (int i=0; i<chans; i++) {
        if (!e->song.chanShow[i]) continue;
        DivChannelState* ch=e->getChanState(i);
        if (ch->portaSpeed>0) {
          ImVec4 col=uiColors[GUI_COLOR_PATTERN_EFFECT_PITCH];
          col.w*=0.2;
          float width=patChanX[i+1]-patChanX[i];

          particles.push_back(Particle(
            pitchGrad,
            (ch->portaNote<=ch->note)?ICON_FA_CHEVRON_DOWN:ICON_FA_CHEVRON_UP,
            off.x+patChanX[i]+fmod(rand(),width)-scrollX,
            off.y+fmod(rand(),MAX(1,ImGui::GetWindowHeight())),
            0.0f,
            (7.0f+(rand()%5)+pow(ch->portaSpeed,0.7f))*((ch->portaNote<=ch->note)?1:-1),
            0.0f,
            1.0f,
            255.0f,
            15.0f
          ));

          if (width>0.1) for (float j=-patChanSlideY[i]; j<ImGui::GetWindowHeight(); j+=width*0.7) {
            ImVec2 tMin=ImVec2(off.x+patChanX[i]-scrollX,off.y+j);
            ImVec2 tMax=ImVec2(off.x+patChanX[i+1]-scrollX,off.y+j+width*0.6);
            if (ch->portaNote<=ch->note) {
              arrowPoints[0]=ImLerp(tMin,tMax,ImVec2(0.1,1.0-0.8));
              arrowPoints[1]=ImLerp(tMin,tMax,ImVec2(0.5,1.0-0.0));
              arrowPoints[2]=ImLerp(tMin,tMax,ImVec2(0.9,1.0-0.8));
              arrowPoints[3]=ImLerp(tMin,tMax,ImVec2(0.8,1.0-1.0));
              arrowPoints[4]=ImLerp(tMin,tMax,ImVec2(0.5,1.0-0.37));
              arrowPoints[5]=ImLerp(tMin,tMax,ImVec2(0.2,1.0-1.0));
              arrowPoints[6]=arrowPoints[0];
              dl->AddPolyline(arrowPoints,7,ImGui::GetColorU32(col),ImDrawFlags_None,5.0f*dpiScale);
            } else {
              arrowPoints[0]=ImLerp(tMin,tMax,ImVec2(0.1,0.8));
              arrowPoints[1]=ImLerp(tMin,tMax,ImVec2(0.5,0.0));
              arrowPoints[2]=ImLerp(tMin,tMax,ImVec2(0.9,0.8));
              arrowPoints[3]=ImLerp(tMin,tMax,ImVec2(0.8,1.0));
              arrowPoints[4]=ImLerp(tMin,tMax,ImVec2(0.5,0.37));
              arrowPoints[5]=ImLerp(tMin,tMax,ImVec2(0.2,1.0));
              arrowPoints[6]=arrowPoints[0];
              dl->AddPolyline(arrowPoints,7,ImGui::GetColorU32(col),ImDrawFlags_None,5.0f*dpiScale);
            }
          }
          patChanSlideY[i]+=((ch->portaNote<=ch->note)?-8:8)*dpiScale*frameTime;
          if (width>0) {
            if (patChanSlideY[i]<0) {
              patChanSlideY[i]=-fmod(-patChanSlideY[i],width*0.7);
            } else {
              patChanSlideY[i]=fmod(patChanSlideY[i],width*0.7);
            }
          }
        }
      }

      // particle simulation
      ImDrawList* fdl=ImGui::GetForegroundDrawList();
      for (size_t i=0; i<particles.size(); i++) {
        Particle& part=particles[i];
        if (part.update(frameTime)) {
          if (part.life>255) part.life=255;
          fdl->AddText(
            iconFont,
            iconFont->FontSize,
            ImVec2(part.pos.x-iconFont->FontSize*0.5,part.pos.y-iconFont->FontSize*0.5),
            part.colors[(int)part.life],
            part.type
          );
        } else {
          particles.erase(particles.begin()+i);
          i--;
        }
      }
    }

    ImGui::PopStyleColor(3);
    ImGui::PopStyleVar();
    ImGui::PopFont();
  }
  ImGui::PopStyleVar();
  if (patternOpen) {
    if (!inhibitMenu && ImGui::IsItemClicked(ImGuiMouseButton_Right)) ImGui::OpenPopup("patternActionMenu");
    if (ImGui::BeginPopup("patternActionMenu",ImGuiWindowFlags_NoMove|ImGuiWindowFlags_AlwaysAutoResize|ImGuiWindowFlags_NoTitleBar|ImGuiWindowFlags_NoSavedSettings)) {
      editOptions(false);
      ImGui::EndPopup();
    }
  }
  if (ImGui::IsWindowFocused(ImGuiFocusedFlags_ChildWindows)) curWindow=GUI_WINDOW_PATTERN;
  ImGui::End();
  //int delta1=SDL_GetPerformanceCounter();
  //logV("render time: %dµs\n",(delta1-delta0)/(SDL_GetPerformanceFrequency()/1000000));
}


static void free_import_entry(RBinWasmImportEntry *entry) {
	if (entry) {
		free (entry->module_str);
		free (entry->field_str);
		free (entry);
	}
}


RPVector *r_bin_wasm_get_imports(RBinWasmObj *bin) {
	r_return_val_if_fail (bin && bin->g_sections, NULL);
	return bin->g_imports? bin->g_imports: parse_unique_subsec_vec_by_id (bin, R_BIN_WASM_SECTION_IMPORT);
}


void wasm_obj_free(RBinWasmObj *bin) {
	if (bin) {
		r_buf_free (bin->buf);
		r_list_free (bin->g_sections);
		r_pvector_free (bin->g_types);
		r_pvector_free (bin->g_imports);
		r_pvector_free (bin->g_funcs);
		r_pvector_free (bin->g_tables);
		r_pvector_free (bin->g_memories);
		r_pvector_free (bin->g_globals);
		r_pvector_free (bin->g_exports);
		r_pvector_free (bin->g_elements);
		r_pvector_free (bin->g_codes);
		r_pvector_free (bin->g_datas);
		r_list_free (bin->g_names);
		free (bin);
	}
}


static inline RPVector *parse_unique_subsec_vec_by_id(RBinWasmObj *bin, ut8 id) {
	RBinWasmSection *sec = section_by_id_unique (bin->g_sections, id);
	if (sec) {
		return parse_sub_section_vec (bin, sec);
	}
	return false;
}


RPVector *r_bin_wasm_get_memories(RBinWasmObj *bin) {
	r_return_val_if_fail (bin && bin->g_sections, NULL);
	return bin->g_memories? bin->g_memories: parse_unique_subsec_vec_by_id (bin, R_BIN_WASM_SECTION_MEMORY);
}


RPVector *r_bin_wasm_get_globals(RBinWasmObj *bin) {
	r_return_val_if_fail (bin && bin->g_sections, NULL);
	return bin->g_globals? bin->g_globals: parse_unique_subsec_vec_by_id (bin, R_BIN_WASM_SECTION_GLOBAL);
}


static RBinWasmMemoryEntry *parse_memory_entry(RBinWasmObj *bin, ut64 bound, ut32 index) {
	RBuffer *b = bin->buf;
	RBinWasmMemoryEntry *ptr = R_NEW0 (RBinWasmMemoryEntry);
	if (ptr) {
		ptr->sec_i = index;
		ptr->file_offset = r_buf_tell (b);
		if (!consume_limits_r (b, bound, &ptr->limits)) {
			free (ptr);
			return NULL;
		}
	}
	return ptr;
}


static size_t consume_locals_r(RBuffer *b, ut64 bound, RBinWasmCodeEntry *out) {
	r_return_val_if_fail (out, 0);
	ut32 count = out->local_count;
	if (count <= 0) {
		return 0;
	}
	out->locals = R_NEWS0 (struct r_bin_wasm_local_entry_t, count);
	if (!out->locals) {
		return 0;
	}

	ut32 i = 0;
	for (i = 0; i < count; i++) {
		struct r_bin_wasm_local_entry_t *local = &out->locals[i];
		if (!consume_u32_r (b, bound, &local->count)) {
			return 0;
		}
		if (!consume_s7_r (b, bound, &local->type)) {
			return 0;
		}
	}
	return i;
}


static size_t consume_u32_r(RBuffer *b, ut64 bound, ut32 *out) {
	size_t n = 0;
	ut32 tmp = consume_r (b, bound, &n, read_u32_leb128);
	if (out) {
		*out = tmp;
	}
	return n;
}


static inline bool strbuf_append_type_vec(RStrBuf *sb, RBinWasmTypeVec *vec) {
	if (!r_strbuf_append (sb, "(")) {
		return false;
	}
	ut32 i;
	for (i = 0; i < vec->count; i++) {
		if (i > 0 && !r_strbuf_append (sb, ", ")) {
			return false;
		}
		const char *s = r_bin_wasm_valuetype_to_string (vec->types[i]);
		if (!s || !r_strbuf_append (sb, s)) {
			return false;
		}
	}

	if (!r_strbuf_append (sb, ")")) {
		return false;
	}
	return true;
}


RPVector *r_bin_wasm_get_exports(RBinWasmObj *bin) {
	r_return_val_if_fail (bin && bin->g_sections, NULL);
	return bin->g_exports? bin->g_exports: parse_unique_subsec_vec_by_id (bin, R_BIN_WASM_SECTION_EXPORT);
}


static inline RBinWasmCustomNameLocalName *parse_local_name(RBuffer *b, ut64 bound) {
	RBinWasmCustomNameLocalName *local_name = R_NEW0 (RBinWasmCustomNameLocalName);
	if (local_name) {
		if (!consume_u32_r (b, bound, &local_name->index)) {
			goto beach;
		}

		local_name->names = r_id_storage_new (0, UT32_MAX);
		if (!local_name->names) {
			goto beach;
		}

		if (!parse_namemap (b, bound, local_name->names, &local_name->names_count)) {
			goto beach;
		}

		return local_name;
	}
beach:
	wasm_custom_name_local_free (local_name);
	return NULL;
}


RPVector *r_bin_wasm_get_datas(RBinWasmObj *bin) {
	r_return_val_if_fail (bin && bin->g_sections, NULL);
	return bin->g_datas? bin->g_datas: parse_unique_subsec_vec_by_id (bin, R_BIN_WASM_SECTION_DATA);
}


static RPVector *parse_sub_section_vec(RBinWasmObj *bin, RBinWasmSection *sec) {
	RPVector **cache = NULL;
	RPVectorFree pfree = (RPVectorFree)free;
	ParseEntryFcn parser;
	switch (sec->id) {
	case R_BIN_WASM_SECTION_TYPE:
		parser = (ParseEntryFcn)parse_type_entry;
		pfree = (RPVectorFree)free_type_entry;
		cache = &bin->g_types;
		break;
	case R_BIN_WASM_SECTION_IMPORT:
		parser = (ParseEntryFcn)parse_import_entry;
		pfree = (RPVectorFree)free_import_entry;
		cache = &bin->g_imports;
		break;
	case R_BIN_WASM_SECTION_FUNCTION:
		parser = (ParseEntryFcn)parse_function_entry;
		cache = &bin->g_funcs;
		break;
	case R_BIN_WASM_SECTION_TABLE:
		parser = (ParseEntryFcn)parse_table_entry;
		cache = &bin->g_tables;
		break;
	case R_BIN_WASM_SECTION_MEMORY:
		parser = (ParseEntryFcn)parse_memory_entry;
		cache = &bin->g_memories;
		break;
	case R_BIN_WASM_SECTION_GLOBAL:
		parser = (ParseEntryFcn)parse_global_entry;
		cache = &bin->g_globals;
		break;
	case R_BIN_WASM_SECTION_EXPORT:
		parser = (ParseEntryFcn)parse_export_entry;
		pfree = (RPVectorFree)free_export_entry;
		cache = &bin->g_exports;
		break;
	case R_BIN_WASM_SECTION_ELEMENT:
		parser = (ParseEntryFcn)parse_element_entry;
		cache = &bin->g_elements;
		break;
	case R_BIN_WASM_SECTION_CODE:
		parser = (ParseEntryFcn)parse_code_entry;
		pfree = (RPVectorFree)free_code_entry;
		cache = &bin->g_codes;
		break;
	case R_BIN_WASM_SECTION_DATA:
		parser = (ParseEntryFcn)parse_data_entry;
		cache = &bin->g_datas;
		break;
	default:
		return NULL;
	}

	RBuffer *buf = bin->buf;
	ut64 offset = sec->payload_data;
	ut64 len = sec->payload_len;
	ut64 bound = offset + len - 1;

	if (bound >= r_buf_size (buf)) {
		r_warn_if_reached (); // section parsing should prevent this
		eprintf ("[wasm] End of %s section data is beyond file end\n", sec->name);
		return NULL;
	}
	if (r_buf_seek (buf, offset, R_BUF_SET) != offset) {
		return NULL;
	}

	*cache = parse_vec (bin, bound, parser, pfree);
	return *cache;
}


static bool parse_namemap(RBuffer *b, ut64 bound, RIDStorage *map, ut32 *count) {
	size_t i;
	if (!consume_u32_r (b, bound, count)) {
		return false;
	}

	for (i = 0; i < *count; i++) {
		ut32 idx;
		if (!consume_u32_r (b, bound, &idx)) {
			return false;
		}

		char *name = NULL;
		if (!consume_encoded_name_new (b, bound, NULL, &name)) {
			R_FREE (name);
			return false;
		}

		if (!r_id_storage_add (map, name, &idx)) {
			R_FREE (name);
			return false;
		};
	}

	return true;
}


static void free_type_vec(RBinWasmTypeVec *vec) {
	if (vec) {
		free (vec->types);
		free (vec);
	}
}


static void wasm_custom_name_free(RBinWasmCustomNameEntry *cust) {
	if (cust) {
		switch (cust->type) {
		case R_BIN_WASM_NAMETYPE_Module:
			R_FREE (cust->mod_name);
			break;
		case R_BIN_WASM_NAMETYPE_Function:
			if (cust->func) {
				r_id_storage_free (cust->func->names);
				R_FREE (cust->func);
			}
			break;
		case R_BIN_WASM_NAMETYPE_Local:
			wasm_custom_local_names_free (cust->local);
			break;
		case R_BIN_WASM_NAMETYPE_None:
			break;
		default:
			eprintf ("Unkown type: 0x%x\n", cust->type);
			r_warn_if_reached ();
		}
		R_FREE (cust);
	}
}


static RBinWasmCustomNameEntry *parse_custom_name_entry(RBinWasmObj *bin, ut64 bound) {
	RBuffer *b = bin->buf;
	RBinWasmCustomNameEntry *cust = R_NEW0 (RBinWasmCustomNameEntry);
	if (!cust) {
		return NULL;
	}
	cust->type = R_BIN_WASM_NAMETYPE_None;

	size_t start = r_buf_tell (b);
	if (!consume_u7_r (b, bound, &cust->type)) {
		goto beach;
	};

	if (!consume_u32_r (b, bound, &cust->size)) {
		goto beach;
	};

	switch (cust->type) {
	case R_BIN_WASM_NAMETYPE_Module:
		if (!consume_encoded_name_new (b, bound, NULL, &cust->mod_name)) {
			goto beach;
		}
		break;
	case R_BIN_WASM_NAMETYPE_Function:
		cust->func = R_NEW0 (RBinWasmCustomNameFunctionNames);
		if (!cust->func) {
			goto beach;
		}
		cust->func->names = r_id_storage_new (0, UT32_MAX);
		if (!cust->func->names) {
			goto beach;
		}

		if (!parse_namemap (b, bound, cust->func->names, &cust->func->count)) {
			goto beach;
		}
		break;
	case R_BIN_WASM_NAMETYPE_Local:
		cust->local = parse_custom_names_local (b, bound);
		if (!cust->local) {
			goto beach;
		}
		break;
	default:
		R_LOG_WARN ("[wasm] Halting custom name section parsing at unknown type 0x%x offset 0x%" PFMTSZx "\n", cust->type, start);
		cust->type = R_BIN_WASM_NAMETYPE_None;
		goto beach;
	}

	return cust;
beach:
	wasm_custom_name_free (cust);
	return NULL;
}


ut32 r_bin_wasm_get_entrypoint(RBinWasmObj *bin) {
	r_return_val_if_fail (bin && bin->g_sections, 0);

	if (bin->entrypoint) {
		return bin->entrypoint;
	}
	ut32 start = r_bin_wasm_get_start (bin);
	RPVector *code = r_bin_wasm_get_codes (bin);
	// FIX: entrypoint can be also an import
	if (code && start != UT32_MAX) {
		RBinWasmCodeEntry *func = r_pvector_at (code, start);
		return func? func->code: 0;
	}
	return 0;
}


RPVector *r_bin_wasm_get_elements(RBinWasmObj *bin) {
	r_return_val_if_fail (bin && bin->g_sections, NULL);
	return bin->g_elements? bin->g_elements: parse_unique_subsec_vec_by_id (bin, R_BIN_WASM_SECTION_ELEMENT);
}


static inline RPVector *parse_vec(RBinWasmObj *bin, ut64 bound, ParseEntryFcn parse_entry, RPVectorFree free_entry) {
	RBuffer *buf = bin->buf;

	ut32 count;
	if (!consume_u32_r (buf, bound, &count)) {
		return NULL;
	}

	RPVector *vec = r_pvector_new (free_entry);
	if (vec) {
		r_pvector_reserve (vec, count);
		ut32 i;
		for (i = 0; i < count; i++) {
			ut64 start = r_buf_tell (buf);
			void *e = parse_entry (bin, bound, i);
			if (!e || !r_pvector_push (vec, e)) {
				eprintf ("[wasm] Failed to parse entry %u/%u of vec at 0x%" PFMT64x "\n", i, count, start);
				free_entry (e);
				break;
			}
		}
	}
	return vec;
}


static void free_export_entry(RBinWasmExportEntry *entry) {
	if (entry) {
		free (entry->field_str);
		free (entry);
	}
}


static size_t consume_init_expr_r(RBuffer *b, ut64 bound, ut8 eoc, void *out) {
	if (!b || bound >= r_buf_size (b) || r_buf_tell (b) > bound) {
		return 0;
	}
	size_t res = 0;
	ut8 cur = r_buf_read8 (b);
	while (r_buf_tell (b) <= bound && cur != eoc) {
		cur = r_buf_read8 (b);
		res++;
	}
	if (cur != eoc) {
		return 0;
	}
	return res + 1;
}


const char *r_bin_wasm_valuetype_to_string (r_bin_wasm_value_type_t type) {
	switch (type) {
	case R_BIN_WASM_VALUETYPE_i32:
		return "i32";
	case R_BIN_WASM_VALUETYPE_i64:
		return "i62";
	case R_BIN_WASM_VALUETYPE_f32:
		return "f32";
	case R_BIN_WASM_VALUETYPE_f64:
		return "f64";
	case R_BIN_WASM_VALUETYPE_REFTYPE:
		return "ANYFUNC";
	case R_BIN_WASM_VALUETYPE_FUNC:
		return "FUNC";
	default:
		return "<?>";
	}
}


static RBinWasmFunctionEntry *parse_function_entry(RBinWasmObj *bin, ut64 bound, ut32 index) {
	RBuffer *b = bin->buf;
	RBinWasmFunctionEntry *func = R_NEW0 (RBinWasmFunctionEntry);
	if (func && consume_u32_r (b, bound, &func->typeindex)) {
		func->sec_i = index;
		func->file_offset = r_buf_tell (b);
		return func;
	}
	free (func);
	return NULL;
}


static bool consume_encoded_name_new(RBuffer *b, ut64 bound, ut32 *len_out, char **str_out) {
	ut32 len;
	char *orig = NULL;
	if (!consume_str_new (b, bound, &len, &orig)) {
		return false;
	}

	// room for even every character getting encoded
	size_t maxsize = (len * 4) + 2;
	char *sout = malloc (maxsize);
	if (!sout) {
		free (orig);
		return false;
	}

	size_t i, oi = 0;
	for (i = 0; i < len && oi + 6 < maxsize; i++) {
		if (WASM_IS_OK (orig, i, len)) {
			sout[oi++] = orig[i];
		} else {
			int res = snprintf (sout + oi, maxsize - oi, "_%02x_", orig[i]);
			oi += res;
		}
	}
	if (oi >= maxsize) {
		sout[maxsize - 1] = '\0';
	} else {
		sout[oi++] = '\0';
	}
	free (orig);

	char *tmp = realloc (sout, oi);
	if (!tmp) {
		free (sout);
		free (tmp);
		return false;
	}
	*str_out = tmp;
	if (len_out) {
		*len_out = len;
	}
	return true;
}


static RBinWasmDataEntry *parse_data_entry(RBinWasmObj *bin, ut64 bound, ut32 index) {
	RBuffer *b = bin->buf;
	RBinWasmDataEntry *ptr = R_NEW0 (RBinWasmDataEntry);
	if (!ptr) {
		return NULL;
	}
	ptr->sec_i = index;
	ptr->file_offset = r_buf_tell (b);
	if (!consume_u32_r (b, bound, &ptr->index)) {
		goto beach;
	}
	if (!(ptr->offset.len = consume_init_expr_r (b, bound, R_BIN_WASM_END_OF_CODE, NULL))) {
		goto beach;
	}
	if (!consume_u32_r (b, bound, &ptr->size)) {
		goto beach;
	}
	ptr->data = r_buf_tell (b);
	r_buf_seek (b, ptr->size, R_BUF_CUR);
	return ptr;

beach:
	free (ptr);
	return NULL;
}


static size_t consume_s7_r(RBuffer *b, ut64 bound, st8 *out) {
	size_t n = 0;
	ut32 tmp = consume_r (b, bound, &n, (ConsumeFcn)read_i32_leb128);
	if (out) {
		*out = (st8) (((tmp & 0x10000000) << 7) | (tmp & 0x7f));
	}
	return n;
}


static RBinWasmCodeEntry *parse_code_entry(RBinWasmObj *bin, ut64 bound, ut32 index) {
	RBuffer *b = bin->buf;
	RBinWasmCodeEntry *ptr = R_NEW0 (RBinWasmCodeEntry);
	if (!ptr) {
		return NULL;
	}
	ptr->sec_i = index;
	ptr->file_offset = r_buf_tell (b);
	if (!consume_u32_r (b, bound, &ptr->body_size)) {
		goto beach;
	}
	ut32 j = r_buf_tell (b);
	if (r_buf_tell (b) + ptr->body_size - 1 > bound) {
		goto beach;
	}
	if (!consume_u32_r (b, bound, &ptr->local_count)) {
		goto beach;
	}
	if (consume_locals_r (b, bound, ptr) < ptr->local_count) {
		goto beach;
	}
	ptr->code = r_buf_tell (b);
	ptr->len = ptr->body_size - ptr->code + j;
	r_buf_seek (b, ptr->len - 1, R_BUF_CUR); // consume bytecode
	ut8 end;
	r_buf_read (b, &end, 1);
	if (end != R_BIN_WASM_END_OF_CODE) {
		ut32 where = r_buf_tell (b) - 1;
		eprintf ("[wasm] Code entry at starting at 0x%x has ending byte 0x%x at 0x%x, should be 0x%x\n",
			(ut32)ptr->file_offset, end, where, R_BIN_WASM_END_OF_CODE);
		goto beach;
	}
	return ptr;

beach:
	free_code_entry (ptr);
	return NULL;
}


RBinWasmObj *r_bin_wasm_init(RBinFile *bf, RBuffer *buf) {
	RBinWasmObj *bin = R_NEW0 (RBinWasmObj);
	if (bin) {
		bin->g_start = UT32_MAX;
		bin->buf = r_buf_ref (buf);
		bin->size = (ut32)r_buf_size (bf->buf);
		bin->g_sections = r_bin_wasm_get_sections (bin);
		// TODO: recursive invocation more natural with streamed parsing
		// but dependency problems when sections are disordered (against spec)

		bin->g_types = r_bin_wasm_get_types (bin);
		bin->g_imports = r_bin_wasm_get_imports (bin);
		bin->g_funcs = r_bin_wasm_get_functions (bin);
		bin->g_tables = r_bin_wasm_get_tables (bin);
		bin->g_memories = r_bin_wasm_get_memories (bin);
		bin->g_globals = r_bin_wasm_get_globals (bin);
		bin->g_exports = r_bin_wasm_get_exports (bin);
		bin->g_codes = r_bin_wasm_get_codes (bin);
		bin->g_datas = r_bin_wasm_get_datas (bin);

		bin->g_names = r_bin_wasm_get_custom_names (bin);

		// entrypoint from Start section
		bin->entrypoint = r_bin_wasm_get_entrypoint (bin);
	}
	return bin;
}


static void wasm_custom_name_local_free(RBinWasmCustomNameLocalName *name) {
	if (name) {
		r_id_storage_free (name->names);
		R_FREE (name);
	}
}


RPVector *r_bin_wasm_get_tables(RBinWasmObj *bin) {
	r_return_val_if_fail (bin && bin->g_sections, NULL);
	return bin->g_tables? bin->g_tables: parse_unique_subsec_vec_by_id (bin, R_BIN_WASM_SECTION_TABLE);
}


const char *r_bin_wasm_get_function_name(RBinWasmObj *bin, ut32 idx) {
	if (!(bin && bin->g_names)) {
		return NULL;
	};

	RListIter *iter;
	RBinWasmCustomNameEntry *nam;
	r_list_foreach (bin->g_names, iter, nam) {
		if (nam->type == R_BIN_WASM_NAMETYPE_Function) {
			const char *n = r_id_storage_get (nam->func->names, idx);
			if (n) {
				return n;
			}
		}
	}

	return NULL;
}


static void free_type_entry(RBinWasmTypeEntry *ptr) {
	if (ptr) {
		free_type_vec (ptr->args);
		free_type_vec (ptr->rets);
		free (ptr->to_str);
		free (ptr);
	}
}


static void wasm_sec_free(RBinWasmSection *sec) {
	if (sec) {
		free (sec->name);
		free (sec);
	}
}


static ut32 consume_r(RBuffer *b, ut64 bound, size_t *n_out, ConsumeFcn consume_fcn) {
	r_return_val_if_fail (b && n_out && consume_fcn, 0);

	ut32 tmp;
	ut64 cur = r_buf_tell (b);
	if (bound >= r_buf_size (b) || cur > bound) {
		return 0;
	}
	// 16 bytes are enough to store 128bits values
	ut8 *buf = R_NEWS (ut8, 16);
	if (!buf) {
		return 0;
	}
	r_buf_read (b, buf, 16);
	size_t n = consume_fcn (buf, buf + bound + 1, &tmp);
	if (!n) {
		free (buf);
		return 0;
	}
	r_buf_seek (b, cur + n, R_BUF_SET);
	*n_out = n;
	free (buf);
	return tmp;
}


static RBinWasmElementEntry *parse_element_entry(RBinWasmObj *bin, ut64 bound, ut32 index) {
	RBuffer *b = bin->buf;
	RBinWasmElementEntry *elem = R_NEW0 (RBinWasmElementEntry);
	if (elem) {
		elem->sec_i = index;
		elem->file_offset = r_buf_tell (b);
		if (!consume_u32_r (b, bound, &elem->index)) {
			goto beach;
		}
		if (!consume_init_expr_r (b, bound, R_BIN_WASM_END_OF_CODE, NULL)) {
			goto beach;
		}
		if (!consume_u32_r (b, bound, &elem->num_elem)) {
			goto beach;
		}
		ut32 j = 0;
		while (r_buf_tell (b) <= bound && j < elem->num_elem) {
			// TODO: allocate space and fill entry
			if (!consume_u32_r (b, bound, NULL)) {
				goto beach;
			}
		}
	}
	return elem;

beach:
	free (elem);
	return NULL;
}


static void *parse_import_entry(RBinWasmObj *bin, ut64 bound, ut32 index) {
	RBuffer *b = bin->buf;
	RBinWasmImportEntry *ptr = R_NEW0 (RBinWasmImportEntry);
	if (!ptr) {
		return NULL;
	}
	ptr->sec_i = index;
	ptr->file_offset = r_buf_tell (b);

	if (!consume_encoded_name_new (b, bound, &ptr->module_len, &ptr->module_str)) {
		goto beach;
	}

	if (!consume_encoded_name_new (b, bound, &ptr->field_len, &ptr->field_str)) {
		goto beach;
	}

	if (!consume_u7_r (b, bound, &ptr->kind)) {
		goto beach;
	}
	switch (ptr->kind) {
	case 0: // Function
		if (!consume_u32_r (b, bound, &ptr->type_f)) {
			goto beach;
		}
		break;
	case 1: // Table
		if (!consume_s7_r (b, bound, (st8 *)&ptr->type_t.elem_type)) {
			goto beach;
		}
		if (!consume_limits_r (b, bound, &ptr->type_t.limits)) {
			goto beach;
		}
		break;
	case 2: // Memory
		if (!consume_limits_r (b, bound, &ptr->type_m.limits)) {
			goto beach;
		}
		break;
	case 3: // Global
		if (!consume_s7_r (b, bound, (st8 *)&ptr->type_g.content_type)) {
			goto beach;
		}
		if (!consume_u1_r (b, bound, (ut8 *)&ptr->type_g.mutability)) {
			goto beach;
		}
		break;
	default:
		goto beach;
	}
	return ptr;

beach:
	free (ptr);
	return NULL;
}


static RList *r_bin_wasm_get_sections_by_id(RList *sections, ut8 id) {
	RList *ret = r_list_newf (NULL);
	if (ret) {
		RBinWasmSection *sec;
		RListIter *iter;
		r_list_foreach (sections, iter, sec) {
			if (sec->id == id) {
				r_list_append (ret, sec);
			}
		}
	}
	return ret;
}


RPVector *r_bin_wasm_get_types(RBinWasmObj *bin) {
	r_return_val_if_fail (bin && bin->g_sections, NULL);
	return bin->g_types? bin->g_types: parse_unique_subsec_vec_by_id (bin, R_BIN_WASM_SECTION_TYPE);
}


static RList *r_bin_wasm_get_custom_name_entries(RBinWasmObj *bin, RBinWasmSection *sec) {
	RList *ret = r_list_newf ((RListFree)wasm_custom_name_free);

	RBuffer *buf = bin->buf;

	r_buf_seek (buf, sec->payload_data, R_BUF_SET);
	ut64 bound = sec->payload_data + sec->payload_len - 1;

	if (bound > r_buf_size (buf)) {
		goto beach;
	}

	while (r_buf_tell (buf) < bound) {
		RBinWasmCustomNameEntry *nam = parse_custom_name_entry (bin, bound);

		if (!nam) {
			break; // allow partial parsing of section
		}

		if (!r_list_append (ret, nam)) {
			goto beach;
		}
	}

	return ret;
beach:
	r_list_free (ret);
	return NULL;
}


static inline void wasm_custom_local_names_free(RBinWasmCustomNameLocalNames *local) {
	if (local) {
		r_list_free (local->locals);
		R_FREE (local);
	}
}


RPVector *r_bin_wasm_get_functions(RBinWasmObj *bin) {
	r_return_val_if_fail (bin && bin->g_sections, NULL);
	return bin->g_funcs? bin->g_funcs: parse_unique_subsec_vec_by_id (bin, R_BIN_WASM_SECTION_FUNCTION);
}


static ut32 r_bin_wasm_get_start(RBinWasmObj *bin) {
	if (bin->g_start == UT32_MAX) {
		RBinWasmSection *sec = section_by_id_unique (bin->g_sections, R_BIN_WASM_SECTION_START);
		if (sec) {
			RBuffer *b = bin->buf;
			r_buf_seek (b, sec->payload_data, R_BUF_SET);
			ut64 bound = r_buf_tell (b) + sec->payload_len - 1;
			if (!consume_u32_r (b, bound, &bin->g_start)) {
				bin->g_start = UT32_MAX;
			}
		}
	}
	return bin->g_start;
}


static RBinWasmExportEntry *parse_export_entry(RBinWasmObj *bin, ut64 bound, ut32 index) {
	RBuffer *b = bin->buf;
	RBinWasmExportEntry *export = R_NEW0 (RBinWasmExportEntry);
	if (export) {
		export->sec_i = index;
		export->file_offset = r_buf_tell (b);
		if (!consume_encoded_name_new (b, bound, &export->field_len, &export->field_str)) {
			goto beach;
		}
		if (!consume_u7_r (b, bound, &export->kind)) {
			goto beach;
		}
		if (!consume_u32_r (b, bound, &export->index)) {
			goto beach;
		}
	}
	return export;
beach:
	free_export_entry (export);
	return NULL;
}


static size_t consume_u7_r(RBuffer *b, ut64 bound, ut8 *out) {
	size_t n = 0;
	ut32 tmp = consume_r (b, bound, &n, read_u32_leb128);
	if (out) {
		*out = (ut8) (tmp & 0x7f);
	}
	return n;
}


static inline RBinWasmCustomNameLocalNames *parse_custom_names_local(RBuffer *b, ut64 bound) {
	RBinWasmCustomNameLocalNames *local = R_NEW0 (RBinWasmCustomNameLocalNames);
	if (!local) {
		return NULL;
	}
	if (!consume_u32_r (b, bound, &local->count)) {
		goto beach;
	}

	local->locals = r_list_newf ((RListFree)wasm_custom_name_local_free);
	if (local->locals) {
		size_t i;
		for (i = 0; i < local->count; i++) {
			RBinWasmCustomNameLocalName *local_name = parse_local_name (b, bound);
			if (!local_name || !r_list_append (local->locals, local_name)) {
				wasm_custom_name_local_free (local_name);
				goto beach;
			}
		}
		return local;
	}

beach:
	wasm_custom_local_names_free (local);
	return NULL;
}


static size_t consume_limits_r(RBuffer *b, ut64 bound, struct r_bin_wasm_resizable_limits_t *out) {
	r_return_val_if_fail (b && out, 0);
	if (bound >= r_buf_size (b) || r_buf_tell (b) > bound || !out) {
		return 0;
	}
	ut32 i = r_buf_tell (b);
	if (!consume_u7_r (b, bound, &out->flags)) {
		return 0;
	}
	if (!consume_u32_r (b, bound, &out->initial)) {
		return 0;
	}
	if (out->flags && !consume_u32_r (b, bound, &out->maximum)) {
		return 0;
	}
	int delta = r_buf_tell (b) - i;
	return (delta > 0)? delta: 0;
}


static bool consume_str_r(RBuffer *b, ut64 bound, size_t len, char *out) {
	r_return_val_if_fail (b && bound > 0 && bound < r_buf_size (b), 0);
	*out = 0;

	if (r_buf_tell (b) + len <= bound + 1) {
		if (r_buf_read (b, (ut8 *)out, len) == len) {
			out[len] = 0;
			return true;
		}
	}
	return false;
}


static size_t consume_u1_r(RBuffer *b, ut64 bound, ut8 *out) {
	size_t n = 0;
	ut32 tmp = consume_r (b, bound, &n, read_u32_leb128);
	if (out) {
		*out = (ut8) (tmp & 0x1);
	}
	return n;
}


static bool inline consume_str_new(RBuffer *b, ut64 bound, ut32 *len_out, char **str_out) {
	r_return_val_if_fail (str_out, false);
	*str_out = NULL;
	if (len_out) {
		*len_out = 0;
	}

	ut32 len = 0;
	// module_str
	if (consume_u32_r (b, bound, &len)) {
		char *str = (char *)malloc (len + 1);
		if (str && consume_str_r (b, bound, len, str)) {
			if (len_out) {
				*len_out = len;
			}
			*str_out = str;
			return true;
		}
		free (str);
	}
	return false;
}


static RBinWasmGlobalEntry *parse_global_entry(RBinWasmObj *bin, ut64 bound, ut32 index) {
	RBuffer *b = bin->buf;
	RBinWasmGlobalEntry *ptr = R_NEW0 (RBinWasmGlobalEntry);
	if (ptr) {
		ptr->sec_i = index;
		ptr->file_offset = r_buf_tell (b);
		if (!consume_u7_r (b, bound, (ut8 *)&ptr->content_type)) {
			goto beach;
		}
		if (!consume_u1_r (b, bound, &ptr->mutability)) {
			goto beach;
		}
		if (!consume_init_expr_r (b, bound, R_BIN_WASM_END_OF_CODE, NULL)) {
			goto beach;
		}
	}
	return ptr;

beach:
	free (ptr);
	return NULL;
}


void r_bin_wasm_destroy(RBinFile *bf) {
	if (bf && bf->o) {
		wasm_obj_free (bf->o->bin_obj);
		bf->o->bin_obj = NULL;
	}
}


static inline RBinWasmTypeVec *parse_type_vector(RBuffer *b, ut64 bound) {
	RBinWasmTypeVec *vec = R_NEW0 (RBinWasmTypeVec);
	// types are all ut8, so leb128 shouldn't be needed, we can reuse consume_str_new
	if (vec && !consume_str_new (b, bound, &vec->count, (char **)&vec->types)) {
		free_type_vec (vec);
		return NULL;
	}
	return vec;
}


RPVector *r_bin_wasm_get_codes(RBinWasmObj *bin) {
	r_return_val_if_fail (bin && bin->g_sections, NULL);
	return bin->g_codes? bin->g_codes: parse_unique_subsec_vec_by_id (bin, R_BIN_WASM_SECTION_CODE);
}


static const char *r_bin_wasm_type_entry_to_string(RBinWasmTypeEntry *type) {
	r_return_val_if_fail (type, NULL);
	if (type->to_str) {
		return type->to_str;
	}

	RStrBuf *sb = r_strbuf_new ("");
	if (!sb) {
		return NULL;
	}
	r_strbuf_reserve (sb, (type->args->count + type->rets->count) * 8);

	bool appended = strbuf_append_type_vec (sb, type->args);
	appended &= r_strbuf_append (sb, " -> ");
	appended &= append_rets (sb, type->rets);
	if (appended) {
		type->to_str = r_strbuf_drain (sb);
	} else {
		r_strbuf_free (sb);
	}
	return type->to_str;
}


static RBinWasmSection *section_by_id_unique(RList *sections, ut8 id) {
	RList *l = r_bin_wasm_get_sections_by_id (sections, id);
	RBinWasmSection *sec = NULL;
	if (l) {
		int len = r_list_length (l);
		if (len) {
			sec = r_list_first (l);
			if (sec && len > 1) {
				eprintf ("[wasm] Using first %s section of %d\n", sec->name, len);
			}
		}
	}
	r_list_free (l);
	return sec;
}


static RBinWasmTypeEntry *parse_type_entry(RBinWasmObj *bin, ut64 bound, ut32 index) {
	RBuffer *b = bin->buf;
	RBinWasmTypeEntry *type = R_NEW0 (RBinWasmTypeEntry);
	if (!type) {
		return NULL;
	}
	type->sec_i = index;
	type->file_offset = r_buf_tell (b);
	if (!consume_u7_r (b, bound, &type->form)) {
		goto beach;
	}
	if (type->form != R_BIN_WASM_VALUETYPE_FUNC) {
		R_LOG_WARN ("Halting types section parsing at invalid type 0x%02x at offset: 0x%" PFMTSZx "\n", type->form, type->file_offset);
		goto beach;
	}

	type->args = parse_type_vector (b, bound);
	if (!type->args) {
		goto beach;
	}

	type->rets = parse_type_vector (b, bound);
	if (!type->rets) {
		goto beach;
	}
	r_bin_wasm_type_entry_to_string (type);

	return type;

beach:
	free_type_entry (type);
	return NULL;
}


static RBinWasmTableEntry *parse_table_entry(RBinWasmObj *bin, ut64 bound, ut32 index) {
	RBuffer *b = bin->buf;
	RBinWasmTableEntry *table = R_NEW0 (RBinWasmTableEntry);
	if (table) {
		table->sec_i = index;
		table->file_offset = r_buf_tell (b);
		if (!consume_s7_r (b, bound, (st8 *)&table->element_type)) {
			goto beach;
		}
		if (!consume_limits_r (b, bound, &table->limits)) {
			goto beach;
		}
	}
	return table;

beach:
	free (table);
	return NULL;
}


RList *r_bin_wasm_get_custom_names(RBinWasmObj *bin) {
	RList *customs = NULL;

	r_return_val_if_fail (bin && bin->g_sections, NULL);

	if (bin->g_names) {
		return bin->g_names;
	}
	if (!(customs = r_bin_wasm_get_sections_by_id (bin->g_sections, R_BIN_WASM_SECTION_CUSTOM))) {
		return r_list_new ();
	}
	// support for multiple "name" sections against spec
	RBinWasmSection *cust = (RBinWasmSection *)r_list_first (customs);
	if (!cust || !cust->name) {
		r_list_free (customs);
		return r_list_new ();
	}
	if (strcmp (cust->name, "name")) {
		r_list_free (customs);
		return r_list_new ();
	}
	bin->g_names = r_bin_wasm_get_custom_name_entries (bin, cust);
	r_list_free (customs);
	return bin->g_names;
}