Commit c5921b51 authored by Samir Halilcevic's avatar Samir Halilcevic

Refactor and add additional tests for coverage

parent b1151251
......@@ -84,9 +84,6 @@ ptrdiff_t rfc6455::decode_header(const_byte_span data, header& hdr) {
bool masked = (byte2 & 0x80) != 0;
auto len_field = byte2 & 0x7F;
size_t header_length;
// control frames can only have payload up to 125 bytes
if (rfc6455::is_control_frame(hdr.opcode) && len_field > 125)
return -1;
if (len_field < 126) {
header_length = 2 + (masked ? 4 : 0);
hdr.payload_len = len_field;
......
......@@ -38,11 +38,29 @@ ptrdiff_t framing::consume(byte_span buffer, byte_span) {
// Wait for more input.
return 0;
}
// Make sure the entire frame (including header) fits into max_frame_size.
if (hdr.payload_len >= (max_frame_size - static_cast<size_t>(hdr_bytes))) {
CAF_LOG_DEBUG("WebSocket frame too large");
abort_and_shutdown(sec::protocol_error, "WebSocket frame too large");
return -1;
if (detail::rfc6455::is_control_frame(hdr.opcode)) {
// control frames can only have payload up to 125 bytes
if (hdr.payload_len > 125) {
CAF_LOG_DEBUG("WebSocket control frame payload exceeds allowed size");
abort_and_shutdown(sec::protocol_error, "WebSocket control frame payload "
"exceeds allowed size");
return -1;
}
} else {
// Make sure the entire frame (including header) fits into max_frame_size.
if (hdr.payload_len >= max_frame_size - static_cast<size_t>(hdr_bytes)) {
CAF_LOG_DEBUG("WebSocket frame too large");
abort_and_shutdown(sec::protocol_error, "WebSocket frame too large");
return -1;
}
// Reject any payload that exceeds max_frame_size. This covers assembled
// payloads as well by including payload_buf_.
if (payload_buf_.size() + hdr.payload_len > max_frame_size) {
CAF_LOG_DEBUG("fragmented WebSocket payload exceeds maximum size");
abort_and_shutdown(sec::protocol_error, "fragmented WebSocket payload "
"exceeds maximum size");
return -1;
}
}
// Wait for more data if necessary.
size_t frame_size = hdr_bytes + hdr.payload_len;
......@@ -67,14 +85,6 @@ ptrdiff_t framing::consume(byte_span buffer, byte_span) {
}
return handle(hdr.opcode, payload, frame_size);
}
// Reject any payload that exceeds max_frame_size. This covers assembled
// payloads as well by including payload_buf_.
if (payload_buf_.size() + payload_len > max_frame_size) {
CAF_LOG_DEBUG("fragmented WebSocket payload exceeds maximum size");
abort_and_shutdown(sec::protocol_error, "fragmented WebSocket payload "
"exceeds maximum size");
return -1;
}
if (hdr.fin) {
if (opcode_ == nil_code) {
// Call upper layer.
......@@ -183,6 +193,7 @@ bool framing::end_text_message() {
ptrdiff_t framing::handle(uint8_t opcode, byte_span payload,
size_t frame_size) {
// opcodes are checked for validity when decoding the header
switch (opcode) {
case detail::rfc6455::connection_close:
abort_and_shutdown(sec::connection_closed);
......@@ -201,12 +212,9 @@ ptrdiff_t framing::handle(uint8_t opcode, byte_span payload,
case detail::rfc6455::ping:
ship_pong(payload);
break;
case detail::rfc6455::pong:
default: // detail::rfc6455::pong
// nop
break;
default:
// error
return -1;
}
return static_cast<ptrdiff_t>(frame_size);
}
......
......@@ -6,7 +6,6 @@
#include "caf/net/web_socket/framing.hpp"
#include "caf/detail/rfc3629.hpp"
#include "net-test.hpp"
using namespace caf;
......@@ -39,6 +38,13 @@ byte_buffer make_test_data(size_t requested_size) {
return byte_buffer{requested_size, std::byte{0xFF}};
}
auto bytes(std::initializer_list<uint8_t> xs) {
byte_buffer result;
for (auto x : xs)
result.emplace_back(static_cast<std::byte>(x));
return result;
}
} // namespace
BEGIN_FIXTURE_SCOPE(fixture)
......@@ -379,4 +385,100 @@ SCENARIO("apps can return errors to shut down the framing layer") {
}
}
SCENARIO("the application receives a pong") {
GIVEN("a valid WebSocket connection") {
WHEN("the client sends a pong") {
reset();
byte_buffer input;
const auto data = make_test_data(10);
detail::rfc6455::assemble_frame(detail::rfc6455::pong, 0x0, data, input);
transport->push(input);
THEN("the application handles the frame without actual input") {
CHECK_EQ(transport->handle_input(),
static_cast<ptrdiff_t>(input.size()));
CHECK(app->text_input.empty());
CHECK(app->binary_input.empty());
}
AND("the application is still working") {
CHECK(!app->has_aborted());
}
}
}
}
SCENARIO("apps reject frames whose payload exceeds maximum allowed size") {
GIVEN("a WebSocket app accepting payloads up to INT32_MAX") {
WHEN("receiving a single large frame") {
reset();
// Faking a large frame without the actual payload
auto frame
= bytes({0x82, // FIN + binary frame opcode
0x7F, // NO MASK + 127 -> uint64 size
0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, // 2 ^ 31
0xFF, 0xFF, 0xFF, 0xFF}); // first 4 bytes
transport->push(frame);
THEN("the app closes the connection with a protocol error") {
CHECK_EQ(transport->handle_input(), 0);
CHECK_EQ(app->abort_reason, sec::protocol_error);
MESSAGE("Aborted with: " << app->abort_reason);
}
}
WHEN("receiving fragmented frames whose combined payload is too large") {
reset();
byte_buffer frame;
auto data = make_test_data(256);
detail::rfc6455::assemble_frame(detail::rfc6455::binary_frame, 0x0, data,
frame, 0);
transport->push(frame);
CHECK_EQ(transport->handle_input(), static_cast<ptrdiff_t>(frame.size()));
frame.clear();
frame = bytes({0x82, // FIN + continuation frame opcode
0x7F, // NO MASK + 127 -> uint64 size
0x00, 0x00, 0x00, 0x00, 0x7f, 0xff, 0xff,
0x00, // 2 ^ 31 - 256
0xFF, 0xFF, 0xFF, 0xFF}); // first 4 masked bytes
transport->push(frame);
THEN("the app closes the connection with a protocol error") {
CHECK_EQ(transport->handle_input(), 0);
CHECK_EQ(app->abort_reason, sec::protocol_error);
MESSAGE("Aborted with: " << app->abort_reason);
}
}
}
}
SCENARIO("the application shuts down on invalid frame fragments") {
GIVEN("a client that sends invalid fragmented frames") {
WHEN("the first fragment is a continuation frame") {
reset();
byte_buffer input;
const auto data = make_test_data(10);
detail::rfc6455::assemble_frame(detail::rfc6455::continuation_frame, 0x0,
data, input, 0);
transport->push(input);
THEN("the app closes the connection with a protocol error") {
CHECK_EQ(transport->handle_input(), 0);
CHECK_EQ(app->abort_reason, sec::protocol_error);
}
}
WHEN("two starting fragments are received") {
reset();
byte_buffer input;
const auto data = make_test_data(10);
detail::rfc6455::assemble_frame(detail::rfc6455::binary_frame, 0x0, data,
input, 0);
THEN("the app accepts the first frame") {
transport->push(input);
CHECK_EQ(transport->handle_input(),
static_cast<ptrdiff_t>(input.size()));
}
AND("the app closes the connection after the second frame") {
transport->push(input);
CHECK_EQ(transport->handle_input(), 0);
CHECK_EQ(app->abort_reason, sec::protocol_error);
}
}
}
}
END_FIXTURE_SCOPE();
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