websocket: handling of PONG frames
No binary (build failed)
—
(log yok)
| @@ -108,6 +108,15 @@ struct ws_encoder { | |
| BIT(contfragment); /* set TRUE if the previous fragment sent was not final */ | |
| }; | |
| + | /* Control frames are allowed up to 125 characters, rfc6455, ch. 5.5 */ |
| + | #define WS_MAX_CNTRL_LEN 125 |
| + | |
| + | struct ws_cntrl_frame { |
| + | unsigned int type; |
| + | size_t payload_len; |
| + | unsigned char payload[WS_MAX_CNTRL_LEN]; |
| + | }; |
| + | |
| /* A websocket connection with en- and decoder that treat frames | |
| * and keep track of boundaries. */ | |
| struct websocket { | |
| @@ -117,6 +126,7 @@ struct websocket { | |
| struct bufq recvbuf; /* raw data from the server */ | |
| struct bufq sendbuf; /* raw data to be sent to the server */ | |
| struct curl_ws_frame recvframe; /* the current WS FRAME received */ | |
| + | struct ws_cntrl_frame pending; /* a control frame pending to be sent */ |
| size_t sendbuf_payload; /* number of payload bytes in sendbuf */ | |
| }; | |
| @@ -229,7 +239,7 @@ static CURLcode ws_frame_flags2firstbyte(struct Curl_easy *data, | |
| switch(flags & ~CURLWS_OFFSET) { | |
| case 0: | |
| if(contfragment) { | |
| - | infof(data, "[WS] no flags given; interpreting as continuation " |
| + | CURL_TRC_WS(data, "no flags given; interpreting as continuation " |
| "fragment for compatibility"); | |
| *pfirstbyte = (WSBIT_OPCODE_CONT | WSBIT_FIN); | |
| return CURLE_OK; | |
| @@ -316,12 +326,12 @@ static CURLcode ws_send_raw_blocking(struct Curl_easy *data, | |
| struct websocket *ws, | |
| const char *buffer, size_t buflen); | |
| - | typedef ssize_t ws_write_payload(const unsigned char *buf, size_t buflen, |
| - | int frame_age, int frame_flags, |
| - | curl_off_t payload_offset, |
| - | curl_off_t payload_len, |
| - | void *userp, |
| - | CURLcode *err); |
| + | typedef CURLcode ws_write_payload(const unsigned char *buf, size_t buflen, |
| + | int frame_age, int frame_flags, |
| + | curl_off_t payload_offset, |
| + | curl_off_t payload_len, |
| + | void *userp, |
| + | size_t *pnwritten); |
| static void ws_dec_next_frame(struct ws_decoder *dec) | |
| { | |
| @@ -390,21 +400,20 @@ static CURLcode ws_dec_read_head(struct ws_decoder *dec, | |
| ws_dec_reset(dec); | |
| return CURLE_RECV_ERROR; | |
| } | |
| - | if(dec->frame_flags & CURLWS_PING && dec->head[1] > 125) { |
| + | if(dec->frame_flags & CURLWS_PING && dec->head[1] > WS_MAX_CNTRL_LEN) { |
| /* The maximum valid size of PING frames is 125 bytes. | |
| Accepting overlong pings would mean sending equivalent pongs! */ | |
| failf(data, "[WS] received PING frame is too big"); | |
| ws_dec_reset(dec); | |
| return CURLE_RECV_ERROR; | |
| } | |
| - | if(dec->frame_flags & CURLWS_PONG && dec->head[1] > 125) { |
| + | if(dec->frame_flags & CURLWS_PONG && dec->head[1] > WS_MAX_CNTRL_LEN) { |
| /* The maximum valid size of PONG frames is 125 bytes. */ | |
| failf(data, "[WS] received PONG frame is too big"); | |
| ws_dec_reset(dec); | |
| return CURLE_RECV_ERROR; | |
| } | |
| - | if(dec->frame_flags & CURLWS_CLOSE && dec->head[1] > 125) { |
| - | /* The maximum valid size of CLOSE frames is 125 bytes. */ |
| + | if(dec->frame_flags & CURLWS_CLOSE && dec->head[1] > WS_MAX_CNTRL_LEN) { |
| failf(data, "[WS] received CLOSE frame is too big"); | |
| ws_dec_reset(dec); | |
| return CURLE_RECV_ERROR; | |
| @@ -479,7 +488,7 @@ static CURLcode ws_dec_pass_payload(struct ws_decoder *dec, | |
| { | |
| const unsigned char *inbuf; | |
| size_t inlen; | |
| - | ssize_t nwritten; |
| + | size_t nwritten; |
| CURLcode result; | |
| curl_off_t remain = dec->payload_len - dec->payload_offset; | |
| @@ -487,15 +496,15 @@ static CURLcode ws_dec_pass_payload(struct ws_decoder *dec, | |
| while(remain && Curl_bufq_peek(inraw, &inbuf, &inlen)) { | |
| if((curl_off_t)inlen > remain) | |
| inlen = (size_t)remain; | |
| - | nwritten = write_cb(inbuf, inlen, dec->frame_age, dec->frame_flags, |
| - | dec->payload_offset, dec->payload_len, |
| - | write_ctx, &result); |
| - | if(nwritten < 0) |
| + | result = write_cb(inbuf, inlen, dec->frame_age, dec->frame_flags, |
| + | dec->payload_offset, dec->payload_len, |
| + | write_ctx, &nwritten); |
| + | if(result) |
| return result; | |
| - | Curl_bufq_skip(inraw, (size_t)nwritten); |
| - | dec->payload_offset += (curl_off_t)nwritten; |
| + | Curl_bufq_skip(inraw, nwritten); |
| + | dec->payload_offset += nwritten; |
| remain = dec->payload_len - dec->payload_offset; | |
| - | CURL_TRC_WS(data, "passed %zd bytes payload, %" |
| + | CURL_TRC_WS(data, "passed %zu bytes payload, %" |
| FMT_OFF_T " remain", nwritten, remain); | |
| } | |
| @@ -532,12 +541,12 @@ static CURLcode ws_dec_pass(struct ws_decoder *dec, | |
| /* head parsing done */ | |
| dec->state = WS_DEC_PAYLOAD; | |
| if(dec->payload_len == 0) { | |
| - | ssize_t nwritten; |
| + | size_t nwritten; |
| const unsigned char tmp = '\0'; | |
| /* special case of a 0 length frame, need to write once */ | |
| - | nwritten = write_cb(&tmp, 0, dec->frame_age, dec->frame_flags, |
| - | 0, 0, write_ctx, &result); |
| - | if(nwritten < 0) |
| + | result = write_cb(&tmp, 0, dec->frame_age, dec->frame_flags, |
| + | 0, 0, write_ctx, &nwritten); |
| + | if(result) |
| return result; | |
| dec->state = WS_DEC_INIT; | |
| break; | |
| @@ -602,43 +611,82 @@ struct ws_cw_dec_ctx { | |
| int cw_type; | |
| }; | |
| - | static ssize_t ws_cw_dec_next(const unsigned char *buf, size_t buflen, |
| - | int frame_age, int frame_flags, |
| - | curl_off_t payload_offset, |
| - | curl_off_t payload_len, |
| - | void *user_data, |
| - | CURLcode *err) |
| + | static CURLcode ws_flush(struct Curl_easy *data, struct websocket *ws, |
| + | bool blocking); |
| + | static CURLcode ws_enc_send(struct Curl_easy *data, |
| + | struct websocket *ws, |
| + | const unsigned char *buffer, |
| + | size_t buflen, |
| + | curl_off_t fragsize, |
| + | unsigned int flags, |
| + | size_t *sent); |
| + | static CURLcode ws_enc_add_pending(struct Curl_easy *data, |
| + | struct websocket *ws); |
| + | |
| + | static CURLcode ws_enc_add_cntrl(struct Curl_easy *data, |
| + | struct websocket *ws, |
| + | const unsigned char *payload, |
| + | size_t plen, |
| + | unsigned int frame_type) |
| + | { |
| + | DEBUGASSERT(plen <= WS_MAX_CNTRL_LEN); |
| + | if(plen > WS_MAX_CNTRL_LEN) |
| + | return CURLE_BAD_FUNCTION_ARGUMENT; |
| + | |
| + | /* Overwrite any pending frame with the new one, we keep |
| + | * only one. */ |
| + | ws->pending.type = frame_type; |
| + | ws->pending.payload_len = plen; |
| + | memcpy(ws->pending.payload, payload, plen); |
| + | |
| + | if(!ws->enc.payload_remain) { /* not in the middle of another frame */ |
| + | CURLcode result = ws_enc_add_pending(data, ws); |
| + | if(!result) |
| + | (void)ws_flush(data, ws, Curl_is_in_callback(data)); |
| + | return result; |
| + | } |
| + | return CURLE_OK; |
| + | } |
| + | |
| + | static CURLcode ws_cw_dec_next(const unsigned char *buf, size_t buflen, |
| + | int frame_age, int frame_flags, |
| + | curl_off_t payload_offset, |
| + | curl_off_t payload_len, |
| + | void *user_data, |
| + | size_t *pnwritten) |
| { | |
| struct ws_cw_dec_ctx *ctx = user_data; | |
| struct Curl_easy *data = ctx->data; | |
| struct websocket *ws = ctx->ws; | |
| bool auto_pong = !data->set.ws_no_auto_pong; | |
| curl_off_t remain = (payload_len - (payload_offset + buflen)); | |
| + | CURLcode result; |
| (void)frame_age; | |
| + | *pnwritten = 0; |
| if(auto_pong && (frame_flags & CURLWS_PING) && !remain) { | |
| /* auto-respond to PINGs, only works for single-frame payloads atm */ | |
| - | size_t bytes; |
| - | infof(data, "[WS] auto-respond to PING with a PONG"); |
| + | CURL_TRC_WS(data, "auto PONG to [PING payload=%" FMT_OFF_T |
| + | "/%" FMT_OFF_T "]", payload_offset, payload_len); |
| /* send back the exact same content as a PONG */ | |
| - | *err = curl_ws_send(data, buf, buflen, &bytes, 0, CURLWS_PONG); |
| - | if(*err) |
| - | return -1; |
| + | result = ws_enc_add_cntrl(data, ws, buf, buflen, CURLWS_PONG); |
| + | if(result) |
| + | return result; |
| } | |
| else if(buflen || !remain) { | |
| /* forward the decoded frame to the next client writer. */ | |
| update_meta(ws, frame_age, frame_flags, payload_offset, | |
| payload_len, buflen); | |
| - | *err = Curl_cwriter_write(data, ctx->next_writer, |
| + | result = Curl_cwriter_write(data, ctx->next_writer, |
| (ctx->cw_type | CLIENTWRITE_0LEN), | |
| (const char *)buf, buflen); | |
| - | if(*err) |
| - | return -1; |
| + | if(result) |
| + | return result; |
| } | |
| - | *err = CURLE_OK; |
| - | return (ssize_t)buflen; |
| + | *pnwritten = buflen; |
| + | return CURLE_OK; |
| } | |
| static CURLcode ws_cw_write(struct Curl_easy *data, | |
| @@ -664,7 +712,7 @@ static CURLcode ws_cw_write(struct Curl_easy *data, | |
| result = Curl_bufq_write(&ctx->buf, (const unsigned char *)buf, | |
| nbytes, &nwritten); | |
| if(result) { | |
| - | infof(data, "WS: error adding data to buffer %d", result); |
| + | infof(data, "[WS] error adding data to buffer %d", result); |
| return result; | |
| } | |
| } | |
| @@ -753,7 +801,7 @@ static void ws_enc_init(struct ws_encoder *enc) | |
| +---------------------------------------------------------------+ | |
| */ | |
| - | static CURLcode ws_enc_write_head(struct Curl_easy *data, |
| + | static CURLcode ws_enc_add_frame(struct Curl_easy *data, |
| struct ws_encoder *enc, | |
| unsigned int flags, | |
| curl_off_t payload_len, | |
| @@ -787,18 +835,15 @@ static CURLcode ws_enc_write_head(struct Curl_easy *data, | |
| enc->contfragment = (flags & CURLWS_CONT) ? (bit)TRUE : (bit)FALSE; | |
| } | |
| - | if(flags & CURLWS_PING && payload_len > 125) { |
| - | /* The maximum valid size of PING frames is 125 bytes. */ |
| + | if(flags & CURLWS_PING && payload_len > WS_MAX_CNTRL_LEN) { |
| failf(data, "[WS] given PING frame is too big"); | |
| return CURLE_TOO_LARGE; | |
| } | |
| - | if(flags & CURLWS_PONG && payload_len > 125) { |
| - | /* The maximum valid size of PONG frames is 125 bytes. */ |
| + | if(flags & CURLWS_PONG && payload_len > WS_MAX_CNTRL_LEN) { |
| failf(data, "[WS] given PONG frame is too big"); | |
| return CURLE_TOO_LARGE; | |
| } | |
| - | if(flags & CURLWS_CLOSE && payload_len > 125) { |
| - | /* The maximum valid size of CLOSE frames is 125 bytes. */ |
| + | if(flags & CURLWS_CLOSE && payload_len > WS_MAX_CNTRL_LEN) { |
| failf(data, "[WS] given CLOSE frame is too big"); | |
| return CURLE_TOO_LARGE; | |
| } | |
| @@ -847,6 +892,23 @@ static CURLcode ws_enc_write_head(struct Curl_easy *data, | |
| return CURLE_OK; | |
| } | |
| + | static CURLcode ws_enc_write_head(struct Curl_easy *data, |
| + | struct websocket *ws, |
| + | struct ws_encoder *enc, |
| + | unsigned int flags, |
| + | curl_off_t payload_len, |
| + | struct bufq *out) |
| + | { |
| + | /* starting a new frame, we want a clean sendbuf. |
| + | * Any pending control frame we can add now as part of the flush. */ |
| + | if(ws->pending.type) { |
| + | CURLcode result = ws_enc_add_pending(data, ws); |
| + | if(result) |
| + | return result; |
| + | } |
| + | return ws_enc_add_frame(data, enc, flags, payload_len, out); |
| + | } |
| + | |
| static CURLcode ws_enc_write_payload(struct ws_encoder *enc, | |
| struct Curl_easy *data, | |
| const unsigned char *buf, size_t buflen, | |
| @@ -881,6 +943,139 @@ static CURLcode ws_enc_write_payload(struct ws_encoder *enc, | |
| return CURLE_OK; | |
| } | |
| + | static CURLcode ws_enc_add_pending(struct Curl_easy *data, |
| + | struct websocket *ws) |
| + | { |
| + | CURLcode result; |
| + | size_t n; |
| + | |
| + | if(!ws->pending.type) /* no pending frame here */ |
| + | return CURLE_OK; |
| + | if(ws->enc.payload_remain) /* in the middle of another frame */ |
| + | return CURLE_AGAIN; |
| + | |
| + | result = ws_enc_add_frame(data, &ws->enc, ws->pending.type, |
| + | (curl_off_t)ws->pending.payload_len, |
| + | &ws->sendbuf); |
| + | if(result) { |
| + | CURL_TRC_WS(data, "ws_enc_cntrl(), error addiong head: %d", |
| + | result); |
| + | goto out; |
| + | } |
| + | result = ws_enc_write_payload(&ws->enc, data, ws->pending.payload, |
| + | ws->pending.payload_len, |
| + | &ws->sendbuf, &n); |
| + | if(result) { |
| + | CURL_TRC_WS(data, "ws_enc_cntrl(), error adding payload: %d", |
| + | result); |
| + | goto out; |
| + | } |
| + | /* our buffer should always be able to take in a control frame */ |
| + | DEBUGASSERT(n == ws->pending.payload_len); |
| + | DEBUGASSERT(!ws->enc.payload_remain); |
| + | |
| + | out: |
| + | memset(&ws->pending, 0, sizeof(ws->pending)); |
| + | return result; |
| + | } |
| + | |
| + | static CURLcode ws_enc_send(struct Curl_easy *data, |
| + | struct websocket *ws, |
| + | const unsigned char *buffer, |
| + | size_t buflen, |
| + | curl_off_t fragsize, |
| + | unsigned int flags, |
| + | size_t *pnsent) |
| + | { |
| + | size_t n; |
| + | CURLcode result = CURLE_OK; |
| + | |
| + | DEBUGASSERT(!data->set.ws_raw_mode); |
| + | *pnsent = 0; |
| + | |
| + | if(ws->enc.payload_remain || !Curl_bufq_is_empty(&ws->sendbuf)) { |
| + | /* a frame is ongoing with payload buffered or more payload |
| + | * that needs to be encoded into the buffer */ |
| + | if(buflen < ws->sendbuf_payload) { |
| + | /* We have been called with LESS buffer data than before. This |
| + | * is not how it's supposed too work. */ |
| + | failf(data, "[WS] curl_ws_send() called with smaller 'buflen' than " |
| + | "bytes already buffered in previous call, %zu vs %zu", |
| + | buflen, ws->sendbuf_payload); |
| + | return CURLE_BAD_FUNCTION_ARGUMENT; |
| + | } |
| + | if((curl_off_t)buflen > |
| + | (ws->enc.payload_remain + (curl_off_t)ws->sendbuf_payload)) { |
| + | /* too large buflen beyond payload length of frame */ |
| + | failf(data, "[WS] unaligned frame size (sending %zu instead of %" |
| + | FMT_OFF_T ")", |
| + | buflen, ws->enc.payload_remain + ws->sendbuf_payload); |
| + | return CURLE_BAD_FUNCTION_ARGUMENT; |
| + | } |
| + | } |
| + | else { |
| + | result = ws_flush(data, ws, Curl_is_in_callback(data)); |
| + | if(result) |
| + | return result; |
| + | |
| + | result = ws_enc_write_head(data, ws, &ws->enc, flags, |
| + | (flags & CURLWS_OFFSET) ? |
| + | fragsize : (curl_off_t)buflen, |
| + | &ws->sendbuf); |
| + | if(result) { |
| + | CURL_TRC_WS(data, "curl_ws_send(), error writing frame head %d", result); |
| + | return result; |
| + | } |
| + | } |
| + | |
| + | /* While there is either sendbuf to flush OR more payload to encode... */ |
| + | while(!Curl_bufq_is_empty(&ws->sendbuf) || (buflen > ws->sendbuf_payload)) { |
| + | /* Try to add more payload to sendbuf */ |
| + | if(buflen > ws->sendbuf_payload) { |
| + | size_t prev_len = Curl_bufq_len(&ws->sendbuf); |
| + | result = ws_enc_write_payload(&ws->enc, data, |
| + | buffer + ws->sendbuf_payload, |
| + | buflen - ws->sendbuf_payload, |
| + | &ws->sendbuf, &n); |
| + | if(result && (result != CURLE_AGAIN)) |
| + | return result; |
| + | ws->sendbuf_payload += Curl_bufq_len(&ws->sendbuf) - prev_len; |
| + | if(!ws->sendbuf_payload) { |
| + | return CURLE_AGAIN; |
| + | } |
| + | } |
| + | |
| + | /* flush, blocking when in callback */ |
| + | result = ws_flush(data, ws, Curl_is_in_callback(data)); |
| + | if(!result && ws->sendbuf_payload > 0) { |
| + | *pnsent += ws->sendbuf_payload; |
| + | buffer += ws->sendbuf_payload; |
| + | buflen -= ws->sendbuf_payload; |
| + | ws->sendbuf_payload = 0; |
| + | } |
| + | else if(result == CURLE_AGAIN) { |
| + | if(ws->sendbuf_payload > Curl_bufq_len(&ws->sendbuf)) { |
| + | /* blocked, part of payload bytes remain, report length |
| + | * that we managed to send. */ |
| + | size_t flushed = (ws->sendbuf_payload - Curl_bufq_len(&ws->sendbuf)); |
| + | *pnsent += flushed; |
| + | ws->sendbuf_payload -= flushed; |
| + | return CURLE_OK; |
| + | } |
| + | else { |
| + | /* blocked before sending headers or 1st payload byte. We cannot report |
| + | * OK on 0-length send (caller counts only payload) and EAGAIN */ |
| + | CURL_TRC_WS(data, "EAGAIN flushing sendbuf, payload_encoded: %zu/%zu", |
| + | ws->sendbuf_payload, buflen); |
| + | DEBUGASSERT(*pnsent == 0); |
| + | return CURLE_AGAIN; |
| + | } |
| + | } |
| + | else |
| + | return result; /* real error sending the data */ |
| + | } |
| + | return CURLE_OK; |
| + | } |
| struct cr_ws_ctx { | |
| struct Curl_creader super; | |
| @@ -959,7 +1154,7 @@ static CURLcode cr_ws_read(struct Curl_easy *data, | |
| if(!ws->enc.payload_remain && Curl_bufq_is_empty(&ws->sendbuf)) { | |
| /* encode the data as a new BINARY frame */ | |
| - | result = ws_enc_write_head(data, &ws->enc, CURLWS_BINARY, nread, |
| + | result = ws_enc_write_head(data, ws, &ws->enc, CURLWS_BINARY, nread, |
| &ws->sendbuf); | |
| if(result) | |
| goto out; | |
| @@ -1229,6 +1424,7 @@ out: | |
| struct ws_collect { | |
| struct Curl_easy *data; | |
| + | struct websocket *ws; |
| unsigned char *buffer; | |
| size_t buflen; | |
| size_t bufidx; | |
| @@ -1239,19 +1435,20 @@ struct ws_collect { | |
| bool written; | |
| }; | |
| - | static ssize_t ws_client_collect(const unsigned char *buf, size_t buflen, |
| - | int frame_age, int frame_flags, |
| - | curl_off_t payload_offset, |
| - | curl_off_t payload_len, |
| - | void *userp, |
| - | CURLcode *err) |
| + | static CURLcode ws_client_collect(const unsigned char *buf, size_t buflen, |
| + | int frame_age, int frame_flags, |
| + | curl_off_t payload_offset, |
| + | curl_off_t payload_len, |
| + | void *userp, |
| + | size_t *pnwritten) |
| { | |
| struct ws_collect *ctx = userp; | |
| struct Curl_easy *data = ctx->data; | |
| bool auto_pong = !data->set.ws_no_auto_pong; | |
| - | size_t nwritten; |
| curl_off_t remain = (payload_len - (payload_offset + buflen)); | |
| + | CURLcode result = CURLE_OK; |
| + | *pnwritten = 0; |
| if(!ctx->bufidx) { | |
| /* first write */ | |
| ctx->frame_age = frame_age; | |
| @@ -1262,31 +1459,30 @@ static ssize_t ws_client_collect(const unsigned char *buf, size_t buflen, | |
| if(auto_pong && (frame_flags & CURLWS_PING) && !remain) { | |
| /* auto-respond to PINGs, only works for single-frame payloads atm */ | |
| - | size_t bytes; |
| - | infof(ctx->data, "[WS] auto-respond to PING with a PONG"); |
| + | CURL_TRC_WS(data, "auto PONG to [PING payload=%" FMT_OFF_T |
| + | "/%" FMT_OFF_T "]", payload_offset, payload_len); |
| /* send back the exact same content as a PONG */ | |
| - | *err = curl_ws_send(ctx->data, buf, buflen, &bytes, 0, CURLWS_PONG); |
| - | if(*err) |
| - | return -1; |
| - | nwritten = bytes; |
| + | result = ws_enc_add_cntrl(ctx->data, ctx->ws, buf, buflen, CURLWS_PONG); |
| + | if(result) |
| + | return result; |
| + | *pnwritten = buflen; |
| } | |
| else { | |
| + | size_t write_len; |
| + | |
| ctx->written = TRUE; | |
| DEBUGASSERT(ctx->buflen >= ctx->bufidx); | |
| - | nwritten = CURLMIN(buflen, ctx->buflen - ctx->bufidx); |
| - | if(!nwritten) { |
| - | if(!buflen) { /* 0 length write, we accept that */ |
| - | *err = CURLE_OK; |
| - | return 0; |
| - | } |
| - | *err = CURLE_AGAIN; /* no more space */ |
| - | return -1; |
| + | write_len = CURLMIN(buflen, ctx->buflen - ctx->bufidx); |
| + | if(!write_len) { |
| + | if(!buflen) /* 0 length write, we accept that */ |
| + | return CURLE_OK; |
| + | return CURLE_AGAIN; /* no more space */ |
| } | |
| - | *err = CURLE_OK; |
| - | memcpy(ctx->buffer + ctx->bufidx, buf, nwritten); |
| - | ctx->bufidx += nwritten; |
| + | memcpy(ctx->buffer + ctx->bufidx, buf, write_len); |
| + | ctx->bufidx += write_len; |
| + | *pnwritten = write_len; |
| } | |
| - | return nwritten; |
| + | return result; |
| } | |
| static CURLcode nw_in_recv(void *reader_ctx, | |
| @@ -1334,6 +1530,7 @@ CURLcode curl_ws_recv(CURL *d, void *buffer, | |
| memset(&ctx, 0, sizeof(ctx)); | |
| ctx.data = data; | |
| + | ctx.ws = ws; |
| ctx.buffer = buffer; | |
| ctx.buflen = buflen; | |
| @@ -1384,6 +1581,13 @@ CURLcode curl_ws_recv(CURL *d, void *buffer, | |
| FMT_OFF_T ", %" FMT_OFF_T " left)", | |
| buflen, *nread, ws->recvframe.offset, | |
| ws->recvframe.bytesleft); | |
| + | /* all's well, try to send any pending control. we do not know |
| + | * when the application will call `curl_ws_send()` again. */ |
| + | if(!data->set.ws_raw_mode && ws->pending.type) { |
| + | CURLcode r2 = ws_enc_add_pending(data, ws); |
| + | if(!r2) |
| + | (void)ws_flush(data, ws, Curl_is_in_callback(data)); |
| + | } |
| return CURLE_OK; | |
| } | |
| @@ -1531,9 +1735,10 @@ CURLcode curl_ws_send(CURL *d, const void *buffer_arg, | |
| { | |
| struct websocket *ws; | |
| const unsigned char *buffer = buffer_arg; | |
| - | size_t n; |
| CURLcode result = CURLE_OK; | |
| struct Curl_easy *data = d; | |
| + | size_t ndummy; |
| + | size_t *pnsent = sent ? sent : &ndummy; |
| if(!GOOD_EASY_HANDLE(data)) | |
| return CURLE_BAD_FUNCTION_ARGUMENT; | |
| @@ -1541,8 +1746,7 @@ CURLcode curl_ws_send(CURL *d, const void *buffer_arg, | |
| ", flags=%x), raw=%d", | |
| buflen, fragsize, flags, data->set.ws_raw_mode); | |
| - | if(sent) |
| - | *sent = 0; |
| + | *pnsent = 0; |
| if(!buffer && buflen) { | |
| failf(data, "[WS] buffer is NULL when buflen is not"); | |
| @@ -1586,107 +1790,18 @@ CURLcode curl_ws_send(CURL *d, const void *buffer_arg, | |
| failf(data, "[WS] fragsize and flags must be zero in raw mode"); | |
| return CURLE_BAD_FUNCTION_ARGUMENT; | |
| } | |
| - | result = ws_send_raw(data, buffer, buflen, sent); |
| + | result = ws_send_raw(data, buffer, buflen, pnsent); |
| goto out; | |
| } | |
| - | /* Not RAW mode, buf we do the frame encoding */ |
| - | |
| - | if(ws->enc.payload_remain || !Curl_bufq_is_empty(&ws->sendbuf)) { |
| - | /* a frame is ongoing with payload buffered or more payload |
| - | * that needs to be encoded into the buffer */ |
| - | if(buflen < ws->sendbuf_payload) { |
| - | /* We have been called with LESS buffer data than before. This |
| - | * is not how it's supposed too work. */ |
| - | failf(data, "[WS] curl_ws_send() called with smaller 'buflen' than " |
| - | "bytes already buffered in previous call, %zu vs %zu", |
| - | buflen, ws->sendbuf_payload); |
| - | result = CURLE_BAD_FUNCTION_ARGUMENT; |
| - | goto out; |
| - | } |
| - | if((curl_off_t)buflen > |
| - | (ws->enc.payload_remain + (curl_off_t)ws->sendbuf_payload)) { |
| - | /* too large buflen beyond payload length of frame */ |
| - | failf(data, "[WS] unaligned frame size (sending %zu instead of %" |
| - | FMT_OFF_T ")", |
| - | buflen, ws->enc.payload_remain + ws->sendbuf_payload); |
| - | result = CURLE_BAD_FUNCTION_ARGUMENT; |
| - | goto out; |
| - | } |
| - | } |
| - | else { |
| - | /* starting a new frame, we want a clean sendbuf */ |
| - | result = ws_flush(data, ws, Curl_is_in_callback(data)); |
| - | if(result) |
| - | goto out; |
| - | |
| - | result = ws_enc_write_head(data, &ws->enc, flags, |
| - | (flags & CURLWS_OFFSET) ? |
| - | fragsize : (curl_off_t)buflen, |
| - | &ws->sendbuf); |
| - | if(result) { |
| - | CURL_TRC_WS(data, "curl_ws_send(), error writing frame head %d", result); |
| - | goto out; |
| - | } |
| - | } |
| - | |
| - | /* While there is either sendbuf to flush OR more payload to encode... */ |
| - | while(!Curl_bufq_is_empty(&ws->sendbuf) || (buflen > ws->sendbuf_payload)) { |
| - | /* Try to add more payload to sendbuf */ |
| - | if(buflen > ws->sendbuf_payload) { |
| - | size_t prev_len = Curl_bufq_len(&ws->sendbuf); |
| - | result = ws_enc_write_payload(&ws->enc, data, |
| - | buffer + ws->sendbuf_payload, |
| - | buflen - ws->sendbuf_payload, |
| - | &ws->sendbuf, &n); |
| - | if(result && (result != CURLE_AGAIN)) |
| - | goto out; |
| - | ws->sendbuf_payload += Curl_bufq_len(&ws->sendbuf) - prev_len; |
| - | if(!ws->sendbuf_payload) { |
| - | result = CURLE_AGAIN; |
| - | goto out; |
| - | } |
| - | } |
| - | |
| - | /* flush, blocking when in callback */ |
| - | result = ws_flush(data, ws, Curl_is_in_callback(data)); |
| - | if(!result && ws->sendbuf_payload > 0) { |
| - | if(sent) |
| - | *sent += ws->sendbuf_payload; |
| - | buffer += ws->sendbuf_payload; |
| - | buflen -= ws->sendbuf_payload; |
| - | ws->sendbuf_payload = 0; |
| - | } |
| - | else if(result == CURLE_AGAIN) { |
| - | if(ws->sendbuf_payload > Curl_bufq_len(&ws->sendbuf)) { |
| - | /* blocked, part of payload bytes remain, report length |
| - | * that we managed to send. */ |
| - | size_t flushed = (ws->sendbuf_payload - Curl_bufq_len(&ws->sendbuf)); |
| - | if(sent) |
| - | *sent += flushed; |
| - | ws->sendbuf_payload -= flushed; |
| - | result = CURLE_OK; |
| - | goto out; |
| - | } |
| - | else { |
| - | /* blocked before sending headers or 1st payload byte. We cannot report |
| - | * OK on 0-length send (caller counts only payload) and EAGAIN */ |
| - | CURL_TRC_WS(data, "EAGAIN flushing sendbuf, payload_encoded: %zu/%zu", |
| - | ws->sendbuf_payload, buflen); |
| - | DEBUGASSERT(!sent || *sent == 0); |
| - | result = CURLE_AGAIN; |
| - | goto out; |
| - | } |
| - | } |
| - | else |
| - | goto out; /* real error sending the data */ |
| - | } |
| + | /* Not RAW mode, we do the frame encoding */ |
| + | result = ws_enc_send(data, ws, buffer, buflen, fragsize, flags, pnsent); |
| out: | |
| CURL_TRC_WS(data, "curl_ws_send(len=%zu, fragsize=%" FMT_OFF_T | |
| ", flags=%x, raw=%d) -> %d, %zu", | |
| buflen, fragsize, flags, data->set.ws_raw_mode, result, | |
| - | sent ? *sent : 0); |
| + | *pnsent); |
| return result; | |
| } | |
| @@ -1760,7 +1875,8 @@ CURL_EXTERN CURLcode curl_ws_start_frame(CURL *d, | |
| goto out; | |
| } | |
| - | result = ws_enc_write_head(data, &ws->enc, flags, frame_len, &ws->sendbuf); |
| + | result = ws_enc_write_head(data, ws, &ws->enc, flags, frame_len, |
| + | &ws->sendbuf); |
| if(result) | |
| CURL_TRC_WS(data, "curl_start_frame(), error adding frame head %d", | |
| result); | |