Unverified Commit 1fee050e authored by Dominik Charousset's avatar Dominik Charousset Committed by GitHub

Merge pull request #1422

Fix handling of fragmented WebSocket frames
parents 1674ccd4 0082cea1
......@@ -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
`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).
### Added
......
......@@ -38,6 +38,8 @@ struct CAF_NET_EXPORT rfc6455 {
static constexpr uint8_t pong = 0x0A;
static constexpr uint8_t fin_flag = 0x80;
// -- utility functions ------------------------------------------------------
static void mask_data(uint32_t key, span<char> data);
......@@ -51,7 +53,8 @@ struct CAF_NET_EXPORT rfc6455 {
byte_buffer& out);
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);
};
......
......@@ -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,
const_byte_span data, byte_buffer& out) {
// First 8 bits: FIN flag + opcode (we never fragment frames).
out.push_back(std::byte{static_cast<uint8_t>(0x80 | opcode)});
const_byte_span data, byte_buffer& out,
uint8_t flags) {
// 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)
auto mask_bit = std::byte{static_cast<uint8_t>(mask_key == 0 ? 0x00 : 0x80)};
if (data.size() < 126) {
......
......@@ -82,7 +82,7 @@ ptrdiff_t framing::consume(byte_span buffer, byte_span) {
} else {
// End of fragmented input.
payload_buf_.insert(payload_buf_.end(), payload.begin(), payload.end());
if (!handle(hdr.opcode, payload_buf_)) {
if (!handle(opcode_, payload_buf_)) {
return -1;
}
opcode_ = nil_code;
......@@ -90,6 +90,8 @@ ptrdiff_t framing::consume(byte_span buffer, byte_span) {
}
}
} 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 (hdr.opcode == detail::rfc6455::continuation_frame) {
CAF_LOG_DEBUG("received WebSocket continuation "
......@@ -100,6 +102,11 @@ ptrdiff_t framing::consume(byte_span buffer, byte_span) {
return -1;
}
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) {
// Reject assembled payloads that exceed max_frame_size.
CAF_LOG_DEBUG("fragmented WebSocket payload exceeds maximum size");
......
......@@ -89,11 +89,17 @@ struct fixture {
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()};
auto key = static_cast<uint32_t>(rng());
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) {
......@@ -218,4 +224,19 @@ CAF_TEST(data may arrive later) {
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()
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