Commit 67df67f04b7d74e2cd655a1d47fa0094d642d154
1 parent
e40fba1e
Handle websocket close frames
We interpret the close frames as closed transport, and therefore set 'error' to 'disconnected'. See code comments.
Showing
4 changed files
with
45 additions
and
5 deletions
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 | +} |
utils.h
| @@ -122,5 +122,7 @@ template<typename ex> void checkWritableDir(const std::string &path) | @@ -122,5 +122,7 @@ template<typename ex> void checkWritableDir(const std::string &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 |