Commit 67df67f04b7d74e2cd655a1d47fa0094d642d154

Authored by Wiebe Cazemier
1 parent e40fba1e

Handle websocket close frames

We interpret the close frames as closed transport, and therefore set
'error' to 'disconnected'. See code comments.
iowrapper.cpp
@@ -406,11 +406,11 @@ ssize_t IoWrapper::readWebsocketAndOrSsl(int fd, void *buf, size_t nbytes, IoWra @@ -406,11 +406,11 @@ ssize_t IoWrapper::readWebsocketAndOrSsl(int fd, void *buf, size_t nbytes, IoWra
406 } 406 }
407 else 407 else
408 { 408 {
409 - n = websocketBytesToReadBuffer(buf, nbytes); 409 + n = websocketBytesToReadBuffer(buf, nbytes, error);
410 410
411 if (n > 0) 411 if (n > 0)
412 *error = IoWrapResult::Success; 412 *error = IoWrapResult::Success;
413 - else if (n == 0) 413 + else if (n == 0 && *error != IoWrapResult::Disconnected)
414 *error = IoWrapResult::Wouldblock; 414 *error = IoWrapResult::Wouldblock;
415 } 415 }
416 } 416 }
@@ -426,7 +426,7 @@ ssize_t IoWrapper::readWebsocketAndOrSsl(int fd, void *buf, size_t nbytes, IoWra @@ -426,7 +426,7 @@ ssize_t IoWrapper::readWebsocketAndOrSsl(int fd, void *buf, size_t nbytes, IoWra
426 * @param nbytes 426 * @param nbytes
427 * @return 427 * @return
428 */ 428 */
429 -ssize_t IoWrapper::websocketBytesToReadBuffer(void *buf, const size_t nbytes) 429 +ssize_t IoWrapper::websocketBytesToReadBuffer(void *buf, const size_t nbytes, IoWrapResult *error)
430 { 430 {
431 const ssize_t targetBufMaxSize = nbytes; 431 const ssize_t targetBufMaxSize = nbytes;
432 ssize_t nbytesRead = 0; 432 ssize_t nbytesRead = 0;
@@ -523,6 +523,8 @@ ssize_t IoWrapper::websocketBytesToReadBuffer(void *buf, const size_t nbytes) @@ -523,6 +523,8 @@ ssize_t IoWrapper::websocketBytesToReadBuffer(void *buf, const size_t nbytes)
523 523
524 if (incompleteWebsocketRead.frame_bytes_left <= websocketPendingBytes.usedBytes()) 524 if (incompleteWebsocketRead.frame_bytes_left <= websocketPendingBytes.usedBytes())
525 { 525 {
  526 + logger->logf(LOG_INFO, "Ponging websocket");
  527 +
526 // Constructing a new temporary buffer because I need the reponse in one frame for writeAsMuchOfBufAsWebsocketFrame(). 528 // Constructing a new temporary buffer because I need the reponse in one frame for writeAsMuchOfBufAsWebsocketFrame().
527 std::vector<char> response(incompleteWebsocketRead.frame_bytes_left); 529 std::vector<char> response(incompleteWebsocketRead.frame_bytes_left);
528 websocketPendingBytes.read(response.data(), response.size()); 530 websocketPendingBytes.read(response.data(), response.size());
@@ -532,11 +534,35 @@ ssize_t IoWrapper::websocketBytesToReadBuffer(void *buf, const size_t nbytes) @@ -532,11 +534,35 @@ ssize_t IoWrapper::websocketBytesToReadBuffer(void *buf, const size_t nbytes)
532 parentClient->setReadyForWriting(true); 534 parentClient->setReadyForWriting(true);
533 } 535 }
534 } 536 }
  537 + else if (incompleteWebsocketRead.opcode == WebsocketOpcode::Close)
  538 + {
  539 + // MUST be a 2-byte unsigned integer (in network byte order) representing a status code with value /code/ defined
  540 + if (incompleteWebsocketRead.frame_bytes_left <= websocketPendingBytes.usedBytes()
  541 + && incompleteWebsocketRead.frame_bytes_left >= 2)
  542 + {
  543 + const uint8_t msb = *websocketPendingBytes.tailPtr() ^ incompleteWebsocketRead.getNextMaskingByte();
  544 + websocketPendingBytes.advanceTail(1);
  545 + const uint8_t lsb = *websocketPendingBytes.tailPtr() ^ incompleteWebsocketRead.getNextMaskingByte();
  546 + websocketPendingBytes.advanceTail(1);
  547 +
  548 + const uint16_t code = msb << 8 | lsb;
  549 +
  550 + // An actual MQTT disconnect doesn't send websocket close frames, or perhaps after the MQTT
  551 + // disconnect when it doesn't matter anymore. So, when users close the tab or stuff like that,
  552 + // we can consider it a closed transport i.e. failed connection. This means will messages
  553 + // will be sent.
  554 + parentClient->setDisconnectReason(websocketCloseCodeToString(code));
  555 + *error = IoWrapResult::Disconnected;
  556 +
  557 + // There may be a UTF8 string with a reason in the packet still, but ignoring that for now.
  558 + incompleteWebsocketRead.reset();
  559 + }
  560 + }
535 else 561 else
536 { 562 {
537 // Specs: "MQTT Control Packets MUST be sent in WebSocket binary data frames. If any other type of data frame is 563 // Specs: "MQTT Control Packets MUST be sent in WebSocket binary data frames. If any other type of data frame is
538 // received the recipient MUST close the Network Connection [MQTT-6.0.0-1]". 564 // received the recipient MUST close the Network Connection [MQTT-6.0.0-1]".
539 - throw ProtocolError("Websocket frames must be 'binary' or 'ping'"); 565 + throw ProtocolError(formatString("Websocket frames must be 'binary' or 'ping'. Received: %d", incompleteWebsocketRead.opcode));
540 } 566 }
541 567
542 if (!incompleteWebsocketRead.sillWorkingOnFrame()) 568 if (!incompleteWebsocketRead.sillWorkingOnFrame())
iowrapper.h
@@ -115,7 +115,7 @@ class IoWrapper @@ -115,7 +115,7 @@ class IoWrapper
115 115
116 Logger *logger = Logger::getInstance(); 116 Logger *logger = Logger::getInstance();
117 117
118 - ssize_t websocketBytesToReadBuffer(void *buf, const size_t nbytes); 118 + ssize_t websocketBytesToReadBuffer(void *buf, const size_t nbytes, IoWrapResult *error);
119 ssize_t readOrSslRead(int fd, void *buf, size_t nbytes, IoWrapResult *error); 119 ssize_t readOrSslRead(int fd, void *buf, size_t nbytes, IoWrapResult *error);
120 ssize_t writeOrSslWrite(int fd, const void *buf, size_t nbytes, IoWrapResult *error); 120 ssize_t writeOrSslWrite(int fd, const void *buf, size_t nbytes, IoWrapResult *error);
121 ssize_t writeAsMuchOfBufAsWebsocketFrame(const void *buf, size_t nbytes, WebsocketOpcode opcode = WebsocketOpcode::Binary); 121 ssize_t writeAsMuchOfBufAsWebsocketFrame(const void *buf, size_t nbytes, WebsocketOpcode opcode = WebsocketOpcode::Binary);
utils.cpp
@@ -633,3 +633,15 @@ std::string sockaddrToString(sockaddr *addr) @@ -633,3 +633,15 @@ std::string sockaddrToString(sockaddr *addr)
633 633
634 return "[unknown address]"; 634 return "[unknown address]";
635 } 635 }
  636 +
  637 +const std::string websocketCloseCodeToString(uint16_t code)
  638 +{
  639 + switch (code) {
  640 + case 1000:
  641 + return "Normal websocket close";
  642 + case 1001:
  643 + return "Browser navigating away from page";
  644 + default:
  645 + return formatString("Websocket status code %d", code);
  646 + }
  647 +}
@@ -122,5 +122,7 @@ template&lt;typename ex&gt; void checkWritableDir(const std::string &amp;path) @@ -122,5 +122,7 @@ template&lt;typename ex&gt; void checkWritableDir(const std::string &amp;path)
122 } 122 }
123 } 123 }
124 124
  125 +const std::string websocketCloseCodeToString(uint16_t code);
  126 +
125 127
126 #endif // UTILS_H 128 #endif // UTILS_H