Commit 0082cea1 authored by Dominik Charousset's avatar Dominik Charousset

Fix handling of fragmented WebSocket frames

parent b4416deb
...@@ -21,6 +21,9 @@ is based on [Keep a Changelog](https://keepachangelog.com). ...@@ -21,6 +21,9 @@ is based on [Keep a Changelog](https://keepachangelog.com).
- Add missing initialization code for the new caf-net module when using the - Add missing initialization code for the new caf-net module when using the
`CAF_MAIN` macro. This fixes the `WSANOTINITIALISED` error on Windows (#1409). `CAF_MAIN` macro. This fixes the `WSANOTINITIALISED` error on Windows (#1409).
- The WebSocket implementation now properly re-assembles fragmented frames.
Previously, a bug in the framing protocol implementation caused CAF to sever
the connection when encountering continuation frames (#1417).
## [0.19.1] - 2023-05-01 ## [0.19.1] - 2023-05-01
......
...@@ -38,6 +38,8 @@ struct CAF_NET_EXPORT rfc6455 { ...@@ -38,6 +38,8 @@ struct CAF_NET_EXPORT rfc6455 {
static constexpr uint8_t pong = 0x0A; static constexpr uint8_t pong = 0x0A;
static constexpr uint8_t fin_flag = 0x80;
// -- utility functions ------------------------------------------------------ // -- utility functions ------------------------------------------------------
static void mask_data(uint32_t key, span<char> data); static void mask_data(uint32_t key, span<char> data);
...@@ -51,7 +53,8 @@ struct CAF_NET_EXPORT rfc6455 { ...@@ -51,7 +53,8 @@ struct CAF_NET_EXPORT rfc6455 {
byte_buffer& out); byte_buffer& out);
static void assemble_frame(uint8_t opcode, uint32_t mask_key, static void assemble_frame(uint8_t opcode, uint32_t mask_key,
const_byte_span data, byte_buffer& out); const_byte_span data, byte_buffer& out,
uint8_t flags = fin_flag);
static ptrdiff_t decode_header(const_byte_span data, header& hdr); static ptrdiff_t decode_header(const_byte_span data, header& hdr);
}; };
......
...@@ -36,9 +36,10 @@ void rfc6455::assemble_frame(uint32_t mask_key, const_byte_span data, ...@@ -36,9 +36,10 @@ void rfc6455::assemble_frame(uint32_t mask_key, const_byte_span data,
} }
void rfc6455::assemble_frame(uint8_t opcode, uint32_t mask_key, void rfc6455::assemble_frame(uint8_t opcode, uint32_t mask_key,
const_byte_span data, byte_buffer& out) { const_byte_span data, byte_buffer& out,
// First 8 bits: FIN flag + opcode (we never fragment frames). uint8_t flags) {
out.push_back(std::byte{static_cast<uint8_t>(0x80 | opcode)}); // First 8 bits: flags + opcode
out.push_back(std::byte{static_cast<uint8_t>(flags | opcode)});
// Mask flag + payload length (7 bits, 7+16 bits, or 7+64 bits) // Mask flag + payload length (7 bits, 7+16 bits, or 7+64 bits)
auto mask_bit = std::byte{static_cast<uint8_t>(mask_key == 0 ? 0x00 : 0x80)}; auto mask_bit = std::byte{static_cast<uint8_t>(mask_key == 0 ? 0x00 : 0x80)};
if (data.size() < 126) { if (data.size() < 126) {
......
...@@ -82,7 +82,7 @@ ptrdiff_t framing::consume(byte_span buffer, byte_span) { ...@@ -82,7 +82,7 @@ ptrdiff_t framing::consume(byte_span buffer, byte_span) {
} else { } else {
// End of fragmented input. // End of fragmented input.
payload_buf_.insert(payload_buf_.end(), payload.begin(), payload.end()); payload_buf_.insert(payload_buf_.end(), payload.begin(), payload.end());
if (!handle(hdr.opcode, payload_buf_)) { if (!handle(opcode_, payload_buf_)) {
return -1; return -1;
} }
opcode_ = nil_code; opcode_ = nil_code;
...@@ -90,6 +90,8 @@ ptrdiff_t framing::consume(byte_span buffer, byte_span) { ...@@ -90,6 +90,8 @@ ptrdiff_t framing::consume(byte_span buffer, byte_span) {
} }
} }
} else { } else {
// The first frame must not be a continuation frame. Any frame that is not
// the first frame must be a continuation frame.
if (opcode_ == nil_code) { if (opcode_ == nil_code) {
if (hdr.opcode == detail::rfc6455::continuation_frame) { if (hdr.opcode == detail::rfc6455::continuation_frame) {
CAF_LOG_DEBUG("received WebSocket continuation " CAF_LOG_DEBUG("received WebSocket continuation "
...@@ -100,6 +102,11 @@ ptrdiff_t framing::consume(byte_span buffer, byte_span) { ...@@ -100,6 +102,11 @@ ptrdiff_t framing::consume(byte_span buffer, byte_span) {
return -1; return -1;
} }
opcode_ = hdr.opcode; opcode_ = hdr.opcode;
} else if (hdr.opcode != detail::rfc6455::continuation_frame) {
CAF_LOG_DEBUG("expected a continuation frame");
up_->abort(make_error(sec::protocol_error, //
"expected a continuation frame"));
return -1;
} else if (payload_buf_.size() + payload_len > max_frame_size) { } else if (payload_buf_.size() + payload_len > max_frame_size) {
// Reject assembled payloads that exceed max_frame_size. // Reject assembled payloads that exceed max_frame_size.
CAF_LOG_DEBUG("fragmented WebSocket payload exceeds maximum size"); CAF_LOG_DEBUG("fragmented WebSocket payload exceeds maximum size");
......
...@@ -89,11 +89,17 @@ struct fixture { ...@@ -89,11 +89,17 @@ struct fixture {
rng.seed(0xD3ADC0D3); rng.seed(0xD3ADC0D3);
} }
void rfc6455_append(uint8_t opcode, const_byte_span bytes, byte_buffer& out) { void rfc6455_append(uint8_t opcode, const_byte_span bytes, byte_buffer& out,
uint8_t flags = detail::rfc6455::fin_flag) {
byte_buffer payload{bytes.begin(), bytes.end()}; byte_buffer payload{bytes.begin(), bytes.end()};
auto key = static_cast<uint32_t>(rng()); auto key = static_cast<uint32_t>(rng());
detail::rfc6455::mask_data(key, payload); detail::rfc6455::mask_data(key, payload);
detail::rfc6455::assemble_frame(opcode, key, payload, out); detail::rfc6455::assemble_frame(opcode, key, payload, out, flags);
}
void rfc6455_append(uint8_t opcode, std::string_view text, byte_buffer& out,
uint8_t flags = detail::rfc6455::fin_flag) {
rfc6455_append(opcode, as_bytes(make_span(text)), out, flags);
} }
void rfc6455_append(const_byte_span bytes, byte_buffer& out) { void rfc6455_append(const_byte_span bytes, byte_buffer& out) {
...@@ -218,4 +224,19 @@ CAF_TEST(data may arrive later) { ...@@ -218,4 +224,19 @@ CAF_TEST(data may arrive later) {
CHECK_EQ(app->text_input, "Hello WebSocket!\nBye WebSocket!\n"); CHECK_EQ(app->text_input, "Hello WebSocket!\nBye WebSocket!\n");
} }
CAF_TEST(data may arrive fragmented) {
transport->push(opening_handshake);
CHECK_EQ(transport->handle_input(),
static_cast<ptrdiff_t>(opening_handshake.size()));
byte_buffer buf;
rfc6455_append(detail::rfc6455::text_frame, "Hello "sv, buf, 0);
rfc6455_append(detail::rfc6455::continuation_frame, "WebSocket!\n"sv, buf);
rfc6455_append(detail::rfc6455::text_frame, "Bye "sv, buf, 0);
rfc6455_append(detail::rfc6455::continuation_frame, "Web"sv, buf, 0);
rfc6455_append(detail::rfc6455::continuation_frame, "Socket!\n"sv, buf);
transport->push(buf);
CHECK_EQ(transport->handle_input(), static_cast<ptrdiff_t>(buf.size()));
CHECK_EQ(app->text_input, "Hello WebSocket!\nBye WebSocket!\n");
}
CAF_TEST_FIXTURE_SCOPE_END() CAF_TEST_FIXTURE_SCOPE_END()
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment