示例#1
0
int pbnc_fsm(struct pubnub_ *pb)
{
    enum pubnub_res pbrslt;
    int i;

    PUBNUB_LOG_TRACE("pbnc_fsm()\t");

next_state:
    WATCH_ENUM_ONCHANGE(pb->state);
    switch (pb->state) {
    case PBS_NULL:
        break;
    case PBS_IDLE:
    {
        enum pbpal_resolv_n_connect_result rslv = pbpal_resolv_and_connect(pb);
        WATCH_ENUM(rslv);
        switch (rslv) {
        case pbpal_resolv_send_wouldblock:
            return 0;
        case pbpal_resolv_sent:
        case pbpal_resolv_rcv_wouldblock:
            pb->state = PBS_WAIT_DNS;
            break;
        case pbpal_connect_wouldblock:
            pb->state = PBS_WAIT_CONNECT;
            break;
        case pbpal_connect_success:
            pb->state = PBS_CONNECTED;
            break;
        default:
            pb->core.last_result = PNR_ADDR_RESOLUTION_FAILED;
            pbntf_trans_outcome(pb);
            return 0;
        }
        i = pbntf_got_socket(pb, pb->pal.socket);
        if (0 == i) {
            goto next_state;
        }
        else if (i < 0) {
            pb->core.last_result = PNR_CONNECT_FAILED;
            pbntf_trans_outcome(pb);
        }
        break;
    }
    case PBS_WAIT_DNS:
    {
        enum pbpal_resolv_n_connect_result rslv = pbpal_check_resolv_and_connect(pb);
        WATCH_ENUM(rslv);
        switch (rslv) {
        case pbpal_resolv_send_wouldblock:
        case pbpal_resolv_sent:
            pb->core.last_result = PNR_INTERNAL_ERROR;
            pbntf_trans_outcome(pb);
            break;
        case pbpal_resolv_rcv_wouldblock:
            break;
        case pbpal_connect_wouldblock:
            pbntf_update_socket(pb, pb->pal.socket);
            pb->state = PBS_WAIT_CONNECT;
            break;
        case pbpal_connect_success:
            pb->state = PBS_CONNECTED;
            goto next_state;
        default:
            pb->core.last_result = PNR_ADDR_RESOLUTION_FAILED;
            pbntf_trans_outcome(pb);
            break;
        }
        break;
    }
    case PBS_WAIT_CONNECT:
    {
        enum pbpal_resolv_n_connect_result rslv = pbpal_connected(pb);
        WATCH_ENUM(rslv);
        switch (rslv) {
        case pbpal_resolv_resource_failure:
        case pbpal_connect_resource_failure:
        case pbpal_connect_failed:
            pb->core.last_result = PNR_ADDR_RESOLUTION_FAILED;
            pbntf_trans_outcome(pb);
            return 0;
        case pbpal_connect_success:
            pb->state = PBS_CONNECTED;
            goto next_state;
        case pbpal_connect_wouldblock:
        default:
            pbntf_update_socket(pb, pb->pal.socket);
            break;
        }
        break;
    }
    case PBS_CONNECTED:
        pbpal_send_literal_str(pb, "GET ");
        pb->state = PBS_TX_GET;
        goto next_state;
    case PBS_TX_GET:
        i = pbpal_send_status(pb);
        if (i <= 0) {
            pb->state = PBS_TX_PATH;
            if ((i < 0) || (-1 == pbpal_send_str(pb, pb->core.http_buf))) {
                outcome_detected(pb, PNR_IO_ERROR);
                break;
            }
            goto next_state;
        }
        break;
    case PBS_TX_PATH:
        i = pbpal_send_status(pb);
        if (i < 0) {
            outcome_detected(pb, PNR_IO_ERROR);
        }
        else if (0 == i) {
            pbpal_send_literal_str(pb, " HTTP/1.1\r\nHost: ");
            pb->state = PBS_TX_VER;
            goto next_state;
        }
        break;
    case PBS_TX_VER:
        i = pbpal_send_status(pb);
        if (i <= 0) {
            char const* o = PUBNUB_ORIGIN_SETTABLE ? pb->origin : PUBNUB_ORIGIN;
            pb->state = PBS_TX_ORIGIN;
            if ((i < 0) || (-1 == pbpal_send_str(pb, o))) {
                outcome_detected(pb, PNR_IO_ERROR);
                break;
            }
            goto next_state;
        }
        break;
    case PBS_TX_ORIGIN:
        i = pbpal_send_status(pb);
        if (i < 0) {
            outcome_detected(pb, PNR_IO_ERROR);
        }
        else if (0 == i) {
            pbpal_send_literal_str(pb, "\r\nUser-Agent: PubNub-C-core/2.1\r\nConnection: Keep-Alive\r\n\r\n");
            pb->state = PBS_TX_FIN_HEAD;
            goto next_state;
        }
        break;
    case PBS_TX_FIN_HEAD:
        i = pbpal_send_status(pb);
        if (i < 0) {
            outcome_detected(pb, PNR_IO_ERROR);
        }
        else if (0 == i) {
            pbpal_start_read_line(pb);
            pb->state = PBS_RX_HTTP_VER;
            goto next_state;
        }
        break;
    case PBS_RX_HTTP_VER:
        pbrslt = pbpal_line_read_status(pb);
        switch (pbrslt) {
        case PNR_IN_PROGRESS:
            break;
        case PNR_OK:
            if (strncmp(pb->core.http_buf, "HTTP/1.", 7) != 0) {
                outcome_detected(pb, PNR_IO_ERROR);
                break;
            }
            pb->http_code = atoi(pb->core.http_buf + 9);
            WATCH_USHORT(pb->http_code);
            pb->core.http_content_len = 0;
            pb->http_chunked = false;
            pb->state = PBS_RX_HEADERS;
            goto next_state;
        default:
            outcome_detected(pb, pbrslt);
            break;
        }
        break;
    case PBS_RX_HEADERS:
        PUBNUB_LOG_TRACE("PBS_RX_HEADERS\n");
        pbpal_start_read_line(pb);
        pb->state = PBS_RX_HEADER_LINE;
        goto next_state;
    case PBS_RX_HEADER_LINE:
        PUBNUB_LOG_TRACE("PBS_RX_HEADER_LINE\n");
        pbrslt = pbpal_line_read_status(pb);
        switch (pbrslt) {
        case PNR_IN_PROGRESS:
            break;
        case PNR_OK:
        {
            char h_chunked[] = "Transfer-Encoding: chunked";
            char h_length[] = "Content-Length: ";
            int read_len = pbpal_read_len(pb);
            PUBNUB_LOG_TRACE("header line was read: %.*s\n", read_len, pb->core.http_buf);
            WATCH_INT(read_len);
            if (read_len <= 2) {
                pb->core.http_buf_len = 0;
                if (!pb->http_chunked) {
                    if (0 == pb->core.http_content_len) {
                        outcome_detected(pb, PNR_IO_ERROR);
                        break;
                    }
                    pb->state = PBS_RX_BODY;
                }
                else {
                    pb->state = PBS_RX_CHUNK_LEN;
                }
                goto next_state;
            }
            if (strncmp(pb->core.http_buf, h_chunked, sizeof h_chunked - 1) == 0) {
                pb->http_chunked = true;
            }
            else if (strncmp(pb->core.http_buf, h_length, sizeof h_length - 1) == 0) {
                size_t len = atoi(pb->core.http_buf + sizeof h_length - 1);
                if (0 != pbcc_realloc_reply_buffer(&pb->core, len)) {
                    outcome_detected(pb, PNR_REPLY_TOO_BIG);
                    break;
                }
                pb->core.http_content_len = len;
            }
            pb->state = PBS_RX_HEADERS;
            goto next_state;
        }
        default:
            outcome_detected(pb, pbrslt);
            break;
        }
        break;
    case PBS_RX_BODY:
        PUBNUB_LOG_TRACE("PBS_RX_BODY\n");
        if (pb->core.http_buf_len < pb->core.http_content_len) {
            pbpal_start_read(pb, pb->core.http_content_len - pb->core.http_buf_len);
            pb->state = PBS_RX_BODY_WAIT;
            goto next_state;
        }
        else {
            finish(pb);
        }
        break;
    case PBS_RX_BODY_WAIT:
        PUBNUB_LOG_TRACE("PBS_RX_BODY_WAIT\n");
        if (pbpal_read_over(pb)) {
            unsigned len = pbpal_read_len(pb);
            WATCH_UINT(len);
            WATCH_UINT(pb->core.http_buf_len);
            memcpy(
                pb->core.http_reply + pb->core.http_buf_len,
                pb->core.http_buf,
                len
                );
            pb->core.http_buf_len += len;
            pb->state = PBS_RX_BODY;
            goto next_state;
        }
        break;
    case PBS_RX_CHUNK_LEN:
        PUBNUB_LOG_TRACE("PBS_RX_CHUNK_LEN\n");
        pbpal_start_read_line(pb);
        pb->state = PBS_RX_CHUNK_LEN_LINE;
        goto next_state;
    case PBS_RX_CHUNK_LEN_LINE:
        pbrslt = pbpal_line_read_status(pb);
        PUBNUB_LOG_TRACE("PBS_RX_CHUNK_LEN_LINE: pbrslt=%d\n", pbrslt);
        switch (pbrslt) {
        case PNR_IN_PROGRESS:
            break;
        case PNR_OK:
        {
            unsigned chunk_length = strtoul(pb->core.http_buf, NULL, 16);

            PUBNUB_LOG_TRACE("About to read a chunk w/length: %d\n", chunk_length);
            if (chunk_length == 0) {
                finish(pb);
            }
            else if (chunk_length > sizeof pb->core.http_buf) {
                outcome_detected(pb, PNR_IO_ERROR);
            }
            else if (0 != pbcc_realloc_reply_buffer(&pb->core, pb->core.http_buf_len + chunk_length)) {
                outcome_detected(pb, PNR_REPLY_TOO_BIG);
            }
            else {
                pb->core.http_content_len = chunk_length + 2;
                pb->state = PBS_RX_BODY_CHUNK;
                goto next_state;
            }
            break;
        }
        default:
            outcome_detected(pb, pbrslt);
            break;
        }
        break;
    case PBS_RX_BODY_CHUNK:
        PUBNUB_LOG_TRACE("PBS_RX_BODY_CHUNK\n");
        if (pb->core.http_content_len > 0) {
            pbpal_start_read(pb, pb->core.http_content_len);
            pb->state = PBS_RX_BODY_CHUNK_WAIT;
        }
        else {
            pb->state = PBS_RX_CHUNK_LEN;
        }
        goto next_state;
    case PBS_RX_BODY_CHUNK_WAIT:
        PUBNUB_LOG_TRACE("PBS_RX_BODY_CHUNK_WAIT\n");
        if (pbpal_read_over(pb)) {
            unsigned len = pbpal_read_len(pb);

            PUBNUB_ASSERT_OPT(pb->core.http_content_len >= len);
            PUBNUB_ASSERT_OPT(len > 0);

            if (pb->core.http_content_len > 2) {
                unsigned to_copy = pb->core.http_content_len - 2;
                if (len < to_copy) {
                    to_copy = len;
                }
                memcpy(
                    pb->core.http_reply + pb->core.http_buf_len,
                    pb->core.http_buf,
                    to_copy
                    );
                pb->core.http_buf_len += to_copy;
            }
            pb->core.http_content_len -= len;
            pb->state = PBS_RX_BODY_CHUNK;
            goto next_state;
        }
        break;
    case PBS_WAIT_CLOSE:
        if (pbpal_closed(pb)) {
            pbpal_forget(pb);
            pbntf_trans_outcome(pb);
        }
        break;
    case PBS_WAIT_CANCEL:
        pb->state = PBS_WAIT_CANCEL_CLOSE;
        if (pbpal_close(pb) <= 0) {
            goto next_state;
        }
        break;
    case PBS_WAIT_CANCEL_CLOSE:
        if (pbpal_closed(pb)) {
            pbpal_forget(pb);
            pb->core.msg_ofs = pb->core.msg_end = 0;
            pbntf_trans_outcome(pb);
        }
        break;
    }
    return 0;
}
示例#2
0
int pbnc_fsm(struct pubnub_ *pb)
{
    enum pubnub_res pbrslt;
    int i;

    PUBNUB_LOG_TRACE("pbnc_fsm()\t");

next_state:
    WATCH_ENUM(pb->state);
    switch (pb->state) {
    case PBS_NULL:
        break;
    case PBS_IDLE:
        pb->retry_after_close = false;
        pb->state = PBS_READY;
        switch (pbntf_enqueue_for_processing(pb)) {
        case -1:
            pb->core.last_result = PNR_INTERNAL_ERROR;
            pbntf_trans_outcome(pb);
            return 0;
        case 0:
            goto next_state;
        case +1:
            break;
        }
        break;
    case PBS_READY:
    {
        enum pbpal_resolv_n_connect_result rslv = pbpal_resolv_and_connect(pb);
        WATCH_ENUM(rslv);
        switch (rslv) {
        case pbpal_resolv_send_wouldblock:
            pb->state = PBS_WAIT_DNS_SEND;
            break;
        case pbpal_resolv_sent:
        case pbpal_resolv_rcv_wouldblock:
            pb->state = PBS_WAIT_DNS_RCV;
            pbntf_watch_in_events(pb);
            break;
        case pbpal_connect_wouldblock:
            pb->state = PBS_WAIT_CONNECT;
            break;
        case pbpal_connect_success:
            pb->state = PBS_CONNECTED;
            break;
        default:
            pb->core.last_result = PNR_ADDR_RESOLUTION_FAILED;
            pbntf_trans_outcome(pb);
            return 0;
        }
        i = pbntf_got_socket(pb, pb->pal.socket);
        if (0 == i) {
            goto next_state;
        }
        else if (i < 0) {
            pb->core.last_result = PNR_CONNECT_FAILED;
            pbntf_trans_outcome(pb);
        }
        break;
    }
    case PBS_WAIT_DNS_SEND:
    {
        enum pbpal_resolv_n_connect_result rslv = pbpal_check_resolv_and_connect(pb);
        WATCH_ENUM(rslv);
        switch (rslv) {
        case pbpal_resolv_send_wouldblock:
            break;
        case pbpal_resolv_sent:
        case pbpal_resolv_rcv_wouldblock:
            pbntf_update_socket(pb, pb->pal.socket);
            pb->state = PBS_WAIT_DNS_RCV;
            pbntf_watch_in_events(pb);
            break;
        case pbpal_connect_wouldblock:
            pbntf_update_socket(pb, pb->pal.socket);
            pb->state = PBS_WAIT_CONNECT;
            break;
        case pbpal_connect_success:
            pb->state = PBS_CONNECTED;
            goto next_state;
        default:
            outcome_detected(pb, PNR_ADDR_RESOLUTION_FAILED);
            break;
        }
        break;
    }
    case PBS_WAIT_DNS_RCV:
    {
        enum pbpal_resolv_n_connect_result rslv = pbpal_check_resolv_and_connect(pb);
        WATCH_ENUM(rslv);
        switch (rslv) {
        case pbpal_resolv_send_wouldblock:
        case pbpal_resolv_sent:
            outcome_detected(pb, PNR_INTERNAL_ERROR);
            break;
        case pbpal_resolv_rcv_wouldblock:
            break;
        case pbpal_connect_wouldblock:
            pbntf_update_socket(pb, pb->pal.socket);
            pb->state = PBS_WAIT_CONNECT;
            pbntf_watch_out_events(pb);
            break;
        case pbpal_connect_success:
            pb->state = PBS_CONNECTED;
            pbntf_watch_out_events(pb);
            goto next_state;
        default:
            outcome_detected(pb, PNR_ADDR_RESOLUTION_FAILED);
            break;
        }
        break;
    }
    case PBS_WAIT_CONNECT:
    {
        enum pbpal_resolv_n_connect_result rslv = pbpal_check_connect(pb);
        WATCH_ENUM(rslv);
        switch (rslv) {
        case pbpal_resolv_send_wouldblock:
        case pbpal_resolv_sent:
        case pbpal_resolv_rcv_wouldblock:
            pb->core.last_result = PNR_INTERNAL_ERROR;
            pbntf_trans_outcome(pb);
            break;
        case pbpal_connect_wouldblock:
            break;
        case pbpal_connect_success:
            pb->state = PBS_CONNECTED;
            goto next_state;
        default:
            outcome_detected(pb, PNR_CONNECT_FAILED);
            break;
        }
        break;
    }
    case PBS_CONNECTED:
#if PUBNUB_PROXY_API
        if ((pb->proxy_type == pbproxyHTTP_CONNECT) && (!pb->proxy_tunnel_established)) {
            pbpal_send_literal_str(pb, "CONNECT ");
        }
        else {
            pbpal_send_literal_str(pb, "GET ");
        }
#else
        pbpal_send_literal_str(pb, "GET ");
#endif
        pb->state = PBS_TX_GET;
        goto next_state;
    case PBS_TX_GET:
        i = pbpal_send_status(pb);
        if (i <= 0) {
#if PUBNUB_PROXY_API
            switch (pb->proxy_type) {
            case pbproxyHTTP_GET:
                pb->state = PBS_TX_SCHEME;
                if (i < 0) {
                    outcome_detected(pb, PNR_IO_ERROR);
                    break;
                }
                strcpy(pb->proxy_saved_path, pb->core.http_buf);
                pbpal_send_literal_str(pb, "http://");
                break;
            case pbproxyHTTP_CONNECT:
                pb->state = PBS_TX_SCHEME;
                if (i < 0) {
                    outcome_detected(pb, PNR_IO_ERROR);
                    break;
                }
                if (!pb->proxy_tunnel_established) {
                    strcpy(pb->proxy_saved_path, pb->core.http_buf);
                }
                else {
                    strcpy(pb->core.http_buf, pb->proxy_saved_path);
                }
                break;
            case pbproxyNONE:
                pb->state = PBS_TX_PATH;
                if ((i < 0) || (-1 == pbpal_send_str(pb, pb->core.http_buf))) {
                    outcome_detected(pb, PNR_IO_ERROR);
                }
                break;
            default:
                outcome_detected(pb, PNR_INTERNAL_ERROR);
                break;
            }
#else
            pb->state = PBS_TX_PATH;
            if ((i < 0) || (-1 == pbpal_send_str(pb, pb->core.http_buf))) {
                outcome_detected(pb, PNR_IO_ERROR);
                break;
            }
#endif /* PUBNUB_PROXY_API */
            goto next_state;
        }
        break;
#if PUBNUB_PROXY_API
    case PBS_TX_SCHEME:
        i = pbpal_send_status(pb);
        if (i <= 0) {
            if ((pb->proxy_type == pbproxyHTTP_CONNECT) && pb->proxy_tunnel_established) {
                pb->state = PBS_TX_HOST;
            }
            else {
                char const* o = PUBNUB_ORIGIN_SETTABLE ? pb->origin : PUBNUB_ORIGIN;
                pb->state = PBS_TX_HOST;
                if ((i < 0) || (-1 == pbpal_send_str(pb, o))) {
                    outcome_detected(pb, PNR_IO_ERROR);
                    break;
                }
            }
            goto next_state;
        }
        break;
    case PBS_TX_HOST:
        i = pbpal_send_status(pb);
        if (i <= 0) {
            if ((pb->proxy_type == pbproxyHTTP_CONNECT) && !pb->proxy_tunnel_established) {
                char port_num[20];
                snprintf(port_num, sizeof port_num, ":%d", 80);
                pbpal_send_str(pb, port_num);
                pb->state = PBS_TX_PORT_NUM;
                goto next_state;
            }
            else {
                pb->state = PBS_TX_PATH;
                if ((i < 0) || (-1 == pbpal_send_str(pb, pb->core.http_buf))) {
                    outcome_detected(pb, PNR_IO_ERROR);
                    break;
                }
            }
            goto next_state;
        }
        break;
    case PBS_TX_PORT_NUM:
        i = pbpal_send_status(pb);
        if (i <= 0) {
            pb->state = PBS_TX_PATH;
            if (i < 0) {
                outcome_detected(pb, PNR_IO_ERROR);
                break;
            }
            goto next_state;
        }
        break;
#endif /* PUBNUB_PROXY_API */
    case PBS_TX_PATH:
        i = pbpal_send_status(pb);
        if (i < 0) {
            outcome_detected(pb, PNR_IO_ERROR);
        }
        else if (0 == i) {
            pbpal_send_literal_str(pb, " HTTP/1.1\r\nHost: ");
            pb->state = PBS_TX_VER;
            goto next_state;
        }
        break;
    case PBS_TX_VER:
        i = pbpal_send_status(pb);
        if (i <= 0) {
            char const* o = PUBNUB_ORIGIN_SETTABLE ? pb->origin : PUBNUB_ORIGIN;
            pb->state = PBS_TX_ORIGIN;
            if ((i < 0) || (-1 == pbpal_send_str(pb, o))) {
                outcome_detected(pb, PNR_IO_ERROR);
                break;
            }
            goto next_state;
        }
        break;
    case PBS_TX_ORIGIN:
        i = pbpal_send_status(pb);
        if (i < 0) {
            outcome_detected(pb, PNR_IO_ERROR);
        }
        else if (0 == i) {
#if PUBNUB_PROXY_API
            char header_to_send[1024] = "\r\n";
            if (0 == pbproxy_http_header_to_send(pb, header_to_send+2, sizeof header_to_send-2)) {
                PUBNUB_LOG_TRACE("Sending HTTP proxy header: '%s'\n", header_to_send);
                pb->state = PBS_TX_PROXY_AUTHORIZATION;
                if ((i < 0) || (-1 == pbpal_send_str(pb, header_to_send))) {
                    outcome_detected(pb, PNR_IO_ERROR);
                    break;
                }
            }
            else {
                pbpal_send_literal_str(pb, "\r\nUser-Agent: PubNub-C-core/2.2\r\nConnection: Keep-Alive\r\n\r\n");
                pb->state = PBS_TX_FIN_HEAD;
            }
#else
            pbpal_send_literal_str(pb, "\r\nUser-Agent: PubNub-C-core/2.2\r\nConnection: Keep-Alive\r\n\r\n");
            pb->state = PBS_TX_FIN_HEAD;
#endif
            goto next_state;
        }
        break;
    case PBS_TX_PROXY_AUTHORIZATION:
        i = pbpal_send_status(pb);
        if (i < 0) {
            outcome_detected(pb, PNR_IO_ERROR);
        }
        else if (0 == i) {
            pbpal_send_literal_str(pb, "\r\nUser-Agent: PubNub-C-core/2.2\r\nConnection: Keep-Alive\r\n\r\n");
            pb->state = PBS_TX_FIN_HEAD;
            goto next_state;
        }
        break;
    case PBS_TX_FIN_HEAD:
        i = pbpal_send_status(pb);
        if (i < 0) {
            outcome_detected(pb, PNR_IO_ERROR);
        }
        else if (0 == i) {
            pbpal_start_read_line(pb);
            pb->state = PBS_RX_HTTP_VER;
            pbntf_watch_in_events(pb);
            goto next_state;
        }
        break;
    case PBS_RX_HTTP_VER:
        pbrslt = pbpal_line_read_status(pb);
        switch (pbrslt) {
        case PNR_IN_PROGRESS:
            break;
        case PNR_OK:
            if (strncmp(pb->core.http_buf, "HTTP/1.", 7) != 0) {
                outcome_detected(pb, PNR_IO_ERROR);
                break;
            }
            pb->http_code = atoi(pb->core.http_buf + 9);
            WATCH_USHORT(pb->http_code);
            pb->core.http_content_len = 0;
            pb->http_chunked = false;
            pb->state = PBS_RX_HEADERS;
            goto next_state;
        default:
            outcome_detected(pb, pbrslt);
            break;
        }
        break;
    case PBS_RX_HEADERS:
        PUBNUB_LOG_TRACE("PBS_RX_HEADERS\n");
        pbpal_start_read_line(pb);
        pb->state = PBS_RX_HEADER_LINE;
        goto next_state;
    case PBS_RX_HEADER_LINE:
        PUBNUB_LOG_TRACE("PBS_RX_HEADER_LINE\n");
        pbrslt = pbpal_line_read_status(pb);
        switch (pbrslt) {
        case PNR_IN_PROGRESS:
            break;
        case PNR_OK:
        {
            char h_chunked[] = "Transfer-Encoding: chunked";
            char h_length[] = "Content-Length: ";
            int read_len = pbpal_read_len(pb);
            PUBNUB_LOG_TRACE("header line was read: %.*s\n", read_len, pb->core.http_buf);
            WATCH_INT(read_len);
            if (read_len <= 2) {
                pb->core.http_buf_len = 0;
                if (!pb->http_chunked) {
                    if (0 == pb->core.http_content_len) {
#if PUBNUB_PROXY_API
                        if ((pb->proxy_type == pbproxyHTTP_CONNECT) && !pb->proxy_tunnel_established) {
                            finish(pb);
                            if (pb->retry_after_close) {
                                goto next_state;
                            }
                            break;
                        }
#endif
                        outcome_detected(pb, PNR_IO_ERROR);
                        break;
                    }
                    pb->state = PBS_RX_BODY;
                }
                else {
                    pb->state = PBS_RX_CHUNK_LEN;
                }
                goto next_state;
            }
            if (strncmp(pb->core.http_buf, h_chunked, sizeof h_chunked - 1) == 0) {
                pb->http_chunked = true;
            }
            else if (strncmp(pb->core.http_buf, h_length, sizeof h_length - 1) == 0) {
                size_t len = atoi(pb->core.http_buf + sizeof h_length - 1);
                if (0 != pbcc_realloc_reply_buffer(&pb->core, len)) {
                    outcome_detected(pb, PNR_REPLY_TOO_BIG);
                    break;
                }
                pb->core.http_content_len = len;
            }
            else {
                pbproxy_handle_http_header(pb, pb->core.http_buf);
            }
            pb->state = PBS_RX_HEADERS;
            goto next_state;
        }
        default:
            outcome_detected(pb, pbrslt);
            break;
        }
        break;
    case PBS_RX_BODY:
        PUBNUB_LOG_TRACE("PBS_RX_BODY\n");
        if (pb->core.http_buf_len < pb->core.http_content_len) {
            pbpal_start_read(pb, pb->core.http_content_len - pb->core.http_buf_len);
            pb->state = PBS_RX_BODY_WAIT;
            goto next_state;
        }
        else {
            finish(pb);
            if (pb->retry_after_close) {
                goto next_state;
            }
        }
        break;
    case PBS_RX_BODY_WAIT:
        PUBNUB_LOG_TRACE("PBS_RX_BODY_WAIT\n");
        if (pbpal_read_over(pb)) {
            unsigned len = pbpal_read_len(pb);
            WATCH_UINT(len);
            WATCH_UINT(pb->core.http_buf_len);
            memcpy(
                pb->core.http_reply + pb->core.http_buf_len,
                pb->core.http_buf,
                len
                );
            pb->core.http_buf_len += len;
            pb->state = PBS_RX_BODY;
            goto next_state;
        }
        break;
    case PBS_RX_CHUNK_LEN:
        PUBNUB_LOG_TRACE("PBS_RX_CHUNK_LEN\n");
        pbpal_start_read_line(pb);
        pb->state = PBS_RX_CHUNK_LEN_LINE;
        goto next_state;
    case PBS_RX_CHUNK_LEN_LINE:
        pbrslt = pbpal_line_read_status(pb);
        PUBNUB_LOG_TRACE("PBS_RX_CHUNK_LEN_LINE: pbrslt=%d\n", pbrslt);
        switch (pbrslt) {
        case PNR_IN_PROGRESS:
            break;
        case PNR_OK:
        {
            unsigned chunk_length = strtoul(pb->core.http_buf, NULL, 16);

            PUBNUB_LOG_TRACE("About to read a chunk w/length: %d\n", chunk_length);
            if (chunk_length == 0) {
                finish(pb);
                if (pb->retry_after_close) {
                    goto next_state;
                }
            }
            else if (chunk_length > sizeof pb->core.http_buf) {
                outcome_detected(pb, PNR_IO_ERROR);
            }
            else if (0 != pbcc_realloc_reply_buffer(&pb->core, pb->core.http_buf_len + chunk_length)) {
                outcome_detected(pb, PNR_REPLY_TOO_BIG);
            }
            else {
                pb->core.http_content_len = chunk_length + 2;
                pb->state = PBS_RX_BODY_CHUNK;
                goto next_state;
            }
            break;
        }
        default:
            outcome_detected(pb, pbrslt);
            break;
        }
        break;
    case PBS_RX_BODY_CHUNK:
        PUBNUB_LOG_TRACE("PBS_RX_BODY_CHUNK\n");
        if (pb->core.http_content_len > 0) {
            pbpal_start_read(pb, pb->core.http_content_len);
            pb->state = PBS_RX_BODY_CHUNK_WAIT;
        }
        else {
            pb->state = PBS_RX_CHUNK_LEN;
        }
        goto next_state;
    case PBS_RX_BODY_CHUNK_WAIT:
        PUBNUB_LOG_TRACE("PBS_RX_BODY_CHUNK_WAIT\n");
        if (pbpal_read_over(pb)) {
            unsigned len = pbpal_read_len(pb);

            PUBNUB_ASSERT_OPT(pb->core.http_content_len >= len);
            PUBNUB_ASSERT_OPT(len > 0);

            if (pb->core.http_content_len > 2) {
                unsigned to_copy = pb->core.http_content_len - 2;
                if (len < to_copy) {
                    to_copy = len;
                }
                memcpy(
                    pb->core.http_reply + pb->core.http_buf_len,
                    pb->core.http_buf,
                    to_copy
                    );
                pb->core.http_buf_len += to_copy;
            }
            pb->core.http_content_len -= len;
            pb->state = PBS_RX_BODY_CHUNK;
            goto next_state;
        }
        break;
    case PBS_WAIT_CLOSE:
        if (pbpal_closed(pb)) {
            if (pb->retry_after_close) {
                pb->state = PBS_IDLE;
            }
            else {
                pbpal_forget(pb);
                pbntf_trans_outcome(pb);
            }
        }
        break;
    case PBS_WAIT_CANCEL:
        pb->state = PBS_WAIT_CANCEL_CLOSE;
        if (pbpal_close(pb) <= 0) {
            goto next_state;
        }
        break;
    case PBS_WAIT_CANCEL_CLOSE:
        if (pbpal_closed(pb)) {
            if (pb->retry_after_close) {
                pb->state = PBS_IDLE;
            }
            else {
                pbpal_forget(pb);
                pb->core.msg_ofs = pb->core.msg_end = 0;
                pbntf_trans_outcome(pb);
            }
        }
        break;
    }
    return 0;
}