Open n1ywb opened 5 years ago
Isn't that what the userData element in the struct Websock is for?
Isn't that what the userData element in the struct Websock is for?
No, for a range of reasons:
userData
is provided but not used by ay of the libesphttpd code, its for the user to attach web socket specific data tolwip
(like other ip stacks) does not perform TCP segment reassemblyws->recvCb
is an application level function that does not need to know anything about the behaviour of the TCP stack or the websocket protocol layer and how the incoming data is segmented between packets or how it should be reassembledThe current implementation of cgiWebSocketRecv
identifies that the websocket payload has been segmented and flags it in the ws->priv
struct
https://github.com/chmorgan/libesphttpd/blob/master/util/cgiwebsocket.c#L272
but makes no attempt to reassemble the websocket payload when it receives the next part of the payload in the next TCP segment.
Reassembly can be non-trivial as a total websocket payload can be 2^63 bytes long, but this problem is not only exposed by big websocket payloads. It also manifests with small payloads when the client decides to transmit multiple WebSocket.send
's in a single TCP frame (which cannot by controlled by the frontend developer in modern browsers) and the combined size exceeds a single TCP frame (typically <1500 bytes). In this case reassembly should be simpler but still should be implemented in the websocket protocol layer.
The first case of large websocket payloads spanning many TCP frames is made complex because of memory constraints with a micro-controller, and a limit could be set using a configuration parameter for libesphttp
to make it manageable.
The second case of a websocket payload that has be segmented across two (2) TCP frames simply because of aggregation in TCP frames for transmission needs to be solved for even small payloads to work reliably in the libesphttpd websockets implementation.
I am going to try and fix the second case in the sort term while ignoring the first case as that is not currently the problem I am having. If I come up with something useful I will setup a PR with my working code.
Disclaimer: I have not used Websockets for anything serious, just played around around a bit with some examples.
The way I understand the mechanisms here, the websocket will provide a reliable, ordered stream of application level data. Like with Berkeley sockets, there is no guarantee that blobs of data will be received in a single operation, so any application has to be able to either process incoming fragmented data on the fly, or buffer the data across multiple reads until a complete payload unit has been received. Such a buffering mechanism can easily be implemented using the private userData pointer.
Assembly by the libesphttpd would be a very bad idea, since, as you pointed out, payloads can get quite big and the lib has absolutely no idea of what the contents should look like, so it would have to buffer all of it. The application, on the other hand, knows exactly how to handle the data, which parts can be processed on the fly or simply be discarded, and, crucially, detect corrupted or malicious payloads and then act immediately.
I do see a minor problem in the way the MORE flag is handled. If a final frame gets fragmented, the MORE flag should be kept set until the last fragment's data gets handed to the callback function. Line 253 should be something like this
if ((ws->priv->fr.flags & FLAG_FIN) == 0 || (ws->priv->fr.len > sl)){
flags |= WEBSOCK_FLAG_MORE;
}
instead of this
if ((ws->priv->fr.flags&FLAG_FIN)==0) flags|=WEBSOCK_FLAG_MORE;
Again, technically this is not strictly necessary, as the application should (must!) verify the received data and can therefore deduce that fragmentation has happened.
Tido
When it receives a websockets frame that spans multiple TCP segments, the receive callback gets called once with each segment, with no obvious way to reassemble them.
https://github.com/chmorgan/libesphttpd/blob/master/util/cgiwebsocket.c#L174
https://github.com/chmorgan/libesphttpd/blob/master/util/cgiwebsocket.c#L254 ^ this should get called once, with the whole frame
console log
browser log
wireshark decode