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 406 }
407 407 else
408 408 {
409   - n = websocketBytesToReadBuffer(buf, nbytes);
  409 + n = websocketBytesToReadBuffer(buf, nbytes, error);
410 410  
411 411 if (n > 0)
412 412 *error = IoWrapResult::Success;
413   - else if (n == 0)
  413 + else if (n == 0 && *error != IoWrapResult::Disconnected)
414 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 426 * @param nbytes
427 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 431 const ssize_t targetBufMaxSize = nbytes;
432 432 ssize_t nbytesRead = 0;
... ... @@ -523,6 +523,8 @@ ssize_t IoWrapper::websocketBytesToReadBuffer(void *buf, const size_t nbytes)
523 523  
524 524 if (incompleteWebsocketRead.frame_bytes_left <= websocketPendingBytes.usedBytes())
525 525 {
  526 + logger->logf(LOG_INFO, "Ponging websocket");
  527 +
526 528 // Constructing a new temporary buffer because I need the reponse in one frame for writeAsMuchOfBufAsWebsocketFrame().
527 529 std::vector<char> response(incompleteWebsocketRead.frame_bytes_left);
528 530 websocketPendingBytes.read(response.data(), response.size());
... ... @@ -532,11 +534,35 @@ ssize_t IoWrapper::websocketBytesToReadBuffer(void *buf, const size_t nbytes)
532 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 561 else
536 562 {
537 563 // Specs: "MQTT Control Packets MUST be sent in WebSocket binary data frames. If any other type of data frame is
538 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 568 if (!incompleteWebsocketRead.sillWorkingOnFrame())
... ...
iowrapper.h
... ... @@ -115,7 +115,7 @@ class IoWrapper
115 115  
116 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 119 ssize_t readOrSslRead(int fd, void *buf, size_t nbytes, IoWrapResult *error);
120 120 ssize_t writeOrSslWrite(int fd, const void *buf, size_t nbytes, IoWrapResult *error);
121 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 633  
634 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 122 }
123 123 }
124 124  
  125 +const std::string websocketCloseCodeToString(uint16_t code);
  126 +
125 127  
126 128 #endif // UTILS_H
... ...