Closed
Description
Description
Current curl_ws_recv implementation (ws.c) works properly if a message (frame) size is less than 16K and buffer size is equal or greater than a message size, so curl_ws_recv reads entire message in one call. In other cases it returns incomplete data or hangs.
To make it working, wsp->frame.bytesleft needs to be initialized not with oleft ("bytes yet to come (for this frame)"), but with datalen + oleft ("total payload size"):
if(!wsp->frame.bytesleft) {
size_t headlen;
curl_off_t oleft;
/* detect new frame */
result = ws_decode(data, (unsigned char *)wsp->stillb, wsp->stillblen,
&headlen, &datalen, &oleft, &recvflags);
if(result == CURLE_AGAIN)
/* a packet fragment only */
break;
else if(result)
return result;
wsp->stillb += headlen;
wsp->stillblen -= headlen;
wsp->frame.offset = 0;
// wsp->frame.bytesleft = oleft; <-- this is wrong, next line is a fix
wsp->frame.bytesleft = datalen + oleft; /* total payload size */
wsp->frame.flags = recvflags;
}
I tested modified code, it works as expected.
I expected the following
curl/libcurl version
curl 7.87.1-DEV (i386-pc-win32) libcurl/7.87.1-DEV Schannel WinIDN
operating system
Windows 11
Activity
dfandrich commentedon Feb 7, 2023
That would explain my confused comment in #10347. That failing assert may have been pointing to this.
bagder commentedon Feb 8, 2023
I don't think that is correct. The
frame
struct contains metadata about this frame, not about pending data in general as we provide a pointer to that struct withcurl_ws_meta()
.frame.bytesleft
should therefor not have a larger value than number of bytes left that belongs to this frame.dfandrich commentedon Feb 8, 2023
Clearly, I haven't puzzled my way through the websockets code sufficiently yet...
bagder commentedon Feb 8, 2023
BTW, this really shows we should improve the test suite for WebSocket. It's too "bare bones" right now.
mikeduglas commentedon Feb 8, 2023
@bagder What is a frame?
Suppose the server sent 20K message as a single frame, with FIN bit set. libcurl receives first 16K, processes it, then receives final 4K, processes it. But both 16K and 4K fragments are the same frame from my point of view. So it is naturally that bytesLeft has the value of 20K (not 4K which it is in current code).
bagder commentedon Feb 8, 2023
I mean frame as in a WebSocket frame as defined in RFC 6455.
If they are indeed a single 20K WebSocket frame, then the two data pieces are indeed just two parts of the same frame. The libcurl API can provide the frame to the application in two parts though.
Bytesleft should then be
(20K - size of the current fragment)
, yes. Number of bytes left of this frame after this piece has been delivered. If this is 16K and the entire frame is 20K, then there is 4K left.mikeduglas commentedon Feb 8, 2023
Suppose I pass 1K buffer to curl_ws_recv. If Bytesleft is initally 4K, then I will get 4K data in 4 curl_ws_recv calls, bytesLeft will decreased to 0 (4-1-1-1-1) and game over. If I call curl_ws_recv 5th time for some reason then new 4K fragment will be read, accidently decoded and bytesLeft will have random value.
bagder commentedon Feb 8, 2023
Sorry, I don't follow. I understand that there is an issue here, I just think that the fix needs to be different than what you proposed above.
Why? When
curl_ws_recv
is called with bytesleft == 0, then does it not reach this code block?curl/lib/ws.c
Lines 431 to 446 in ead2b2d
mikeduglas commentedon Feb 8, 2023
Oops sorry you're right, in the case of bytesleft == 0 ws_decode block is not called, datalen is assigned to 0 and we get an endless loop.
I believe that my bad English does not allow me to convince you. :-)
mikeduglas commentedon Feb 8, 2023
Hence wsp->frame.bytesleft should contain bytes left in the frame to be delivered to a caller. Initially (right after ws_decode call) it is 20K (full frame size), 1K of these 20K are memcopied to the 1K user's buffer, at the same time bytesleft is reduced by 1K, so the user gets back 1K in *nread and 19K in meta.bytesleft after 1st curl_ws_recv call. Next call gets a user 1K in *nread and 18K in bytesleft and so on.
mikeduglas commentedon Feb 8, 2023
Actually you aren't right, the condition is "!wsp->frame.bytesleft" so when bytesleft == 0 then the condition is true and the block is reached. But it makes little difference, in any case when bytesleft is initially set to 4K, the code fails.
By the way, in struct websocket (ws.h) there is a member oleft:
which is never used. In my example that 4K should be assigned to this wsp->oleft.
bagder commentedon Feb 8, 2023
The fix is bigger than what has been discussed here. I am working on it.
8 remaining items