Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Fix client Websockets (broken) (#2749)
This PR addresses several issues discovered in the client websocket stack. A simple local websocket echo server has been added for testing, which can be run using `make wsserver`. **Memory leaks** Running `HttpServer_Websockets` sample with valgrind shows a memory leak (details below, fig 1). I can see from the logic of `WebsocketConnection::send` that there are multiple reasons the call could fail, but the `source` stream is only destroyed in one of them. **Failed connection** Testing with the local server failed with `websockets.exceptions.InvalidHeaderValue: invalid Sec-WebSocket-Key header`. The key was 17 bytes instead of 16. **utf-8 decoding errors** Turns out message was getting corrupted because mask value passed to XorStream is on stack, which then gets overwritten before message has been sent out. Fixed by taking a copy of the value. **CLOSE message not being sent** Tested with `Websocket_Client` sample (running local echo server) to confirm correct behaviour, noticed a `Streams without known size are not supported` message when closing the connection. This blocked sending 'CLOSE' notifications which have no payload. **Issues during CLOSE** The TCP socket was being closed too soon, causing additional errors. Increasing timeout to 2 seconds fixes this. Also included a status code in the CLOSE message indicating normal closure; this is optional, but seems like a good thing to do. RFC 6455 states: *The application MUST NOT send any more data frames after sending a Close frame. If an endpoint receives a Close frame and did not previously send a Close frame, the endpoint MUST send a Close frame in response.* Therefore, the `close()` logic has been changed so that a CLOSE message is *always* sent, either in response to a previous incoming request (in which case the received status is echoed back) or as notification that we're closing (status 1000 - normal closure). Checked server operation with `HttpServer_Websockets` sample **Simplify packet generation** It's not necessary to pre-calculate the packet length as it's never more than 14 bytes in length. **WebsocketConnection not getting destroyed** HttpConnection objects are not 'auto-released' so leaks memory every time a WebsocketConnection is destroyed (512+ bytes). Simplest fix for this is to add a `setAutoSelfDestruct` method to `TcpConnection` so this can be changed. **Messages not being received** Connection is activated OK, but `HttpClientConnection` then calls `init` in its `onConnected` handler which resets the new `receive` delegate. This causes incoming websocket frames to be passed to the http parser, instead of the WS parser, hence the `HTTP parser error: HPE_INVALID_CONSTANT` message. ==== Fig 1: Initial memory leak reported by valgrind ``` ==1291918== ==1291918== HEAP SUMMARY: ==1291918== in use at exit: 4,133 bytes in 16 blocks ==1291918== total heap usage: 573 allocs, 557 frees, 71,139 bytes allocated ==1291918== ==1291918== 64 bytes in 2 blocks are definitely lost in loss record 10 of 13 ==1291918== at 0x4041D7D: operator new(unsigned int) (vg_replace_malloc.c:476) ==1291918== by 0x8075BC5: WebsocketConnection::send(char const*, unsigned int, ws_frame_type_t) (WebsocketConnection.cpp:180) ==1291918== by 0x804EB65: send (WebsocketConnection.h:107) ==1291918== by 0x804EB65: sendString (WebsocketConnection.h:145) ==1291918== by 0x804EB65: wsCommandReceived(WebsocketConnection&, String const&) (application.cpp:88) ==1291918== by 0x807575D: operator() (std_function.h:591) ==1291918== by 0x807575D: WebsocketConnection::staticOnDataPayload(void*, char const*, unsigned int) (WebsocketConnection.cpp:128) ==1291918== by 0x8081E5C: ws_parser_execute (ws_parser.c:263) ==1291918== by 0x80756C6: WebsocketConnection::processFrame(TcpClient&, char*, int) (WebsocketConnection.cpp:103) ==1291918== by 0x8079305: operator() (std_function.h:591) ==1291918== by 0x8079305: TcpClient::onReceive(pbuf*) (TcpClient.cpp:150) ==1291918== by 0x8078A8E: TcpConnection::internalOnReceive(pbuf*, signed char) (TcpConnection.cpp:484) ==1291918== by 0x8058560: tcp_input (in /stripe/sandboxes/sming-dev/samples/HttpServer_WebSockets/out/Host/debug/firmware/app) ==1291918== by 0x80627F3: ip4_input (in /stripe/sandboxes/sming-dev/samples/HttpServer_WebSockets/out/Host/debug/firmware/app) ==1291918== by 0x8063C89: ethernet_input (in /stripe/sandboxes/sming-dev/samples/HttpServer_WebSockets/out/Host/debug/firmware/app) ==1291918== by 0x8064245: tapif_select (in /stripe/sandboxes/sming-dev/samples/HttpServer_WebSockets/out/Host/debug/firmware/app) ==1291918== ==1291918== LEAK SUMMARY: ==1291918== definitely lost: 64 bytes in 2 blocks ==1291918== indirectly lost: 0 bytes in 0 blocks ==1291918== possibly lost: 0 bytes in 0 blocks ==1291918== still reachable: 4,069 bytes in 14 blocks ==1291918== suppressed: 0 bytes in 0 blocks ==1291918== Reachable blocks (those to which a pointer was found) are not shown. ==1291918== To see them, rerun with: --leak-check=full --show-leak-kinds=all ==1291918== ==1291918== For lists of detected and suppressed errors, rerun with: -s ==1291918== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0) ```
- Loading branch information