Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions wish/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ FetchContent_Declare(
wslay
GIT_REPOSITORY https://github.com/tatsuhiro-t/wslay.git
GIT_TAG master
PATCH_COMMAND git apply --reverse --check ${CMAKE_CURRENT_SOURCE_DIR}/patches/wslay-disable-mask.patch || git apply ${CMAKE_CURRENT_SOURCE_DIR}/patches/wslay-disable-mask.patch
)

# Disable OpenSSL and Mbed TLS. We use BoringSSL instead.
Expand Down
49 changes: 49 additions & 0 deletions wish/cpp/patches/wslay-disable-mask.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
diff --git a/lib/wslay_event.c b/lib/wslay_event.c
index 4c29fe4..93e9255 100644
--- a/lib/wslay_event.c
+++ b/lib/wslay_event.c
@@ -509,7 +509,7 @@ int wslay_event_recv(wslay_event_context_ptr ctx) {
(wslay_get_rsv1(iocb.rsv) &&
(wslay_is_ctrl_frame(iocb.opcode) ||
iocb.opcode == WSLAY_CONTINUATION_FRAME)) ||
- (ctx->server && !iocb.mask) || (!ctx->server && iocb.mask)) {
+ iocb.mask) {
if ((r = wslay_event_queue_close_wrapper(ctx, WSLAY_CODE_PROTOCOL_ERROR,
NULL, 0)) != 0) {
return (int)r;
@@ -775,7 +775,7 @@ int wslay_event_send(wslay_event_context_ptr ctx) {
iocb.fin = 1;
iocb.opcode = ctx->omsg->opcode;
iocb.rsv = ctx->omsg->rsv;
- iocb.mask = ctx->server ^ 1;
+ iocb.mask = 0;
iocb.data = ctx->omsg->data;
iocb.data_length = ctx->opayloadlen;
if (ctx->opayloadoff) {
@@ -835,7 +835,7 @@ int wslay_event_send(wslay_event_context_ptr ctx) {
iocb.fin = ctx->omsg->fin;
iocb.opcode = ctx->omsg->opcode;
iocb.rsv = ctx->omsg->rsv;
- iocb.mask = ctx->server ? 0 : 1;
+ iocb.mask = 0;
iocb.data = ctx->obufmark;
iocb.data_length = (size_t)(ctx->obuflimit - ctx->obufmark);
iocb.payload_length = ctx->opayloadlen;
@@ -908,7 +908,7 @@ ssize_t wslay_event_write(wslay_event_context_ptr ctx, uint8_t *buf,
iocb.fin = 1;
iocb.opcode = ctx->omsg->opcode;
iocb.rsv = ctx->omsg->rsv;
- iocb.mask = ctx->server ^ 1;
+ iocb.mask = 0;
iocb.data = ctx->omsg->data;
iocb.data_length = ctx->opayloadlen;
if (ctx->opayloadoff) {
@@ -971,7 +971,7 @@ ssize_t wslay_event_write(wslay_event_context_ptr ctx, uint8_t *buf,
iocb.fin = ctx->omsg->fin;
iocb.opcode = ctx->omsg->opcode;
iocb.rsv = ctx->omsg->rsv;
- iocb.mask = ctx->server ? 0 : 1;
+ iocb.mask = 0;
iocb.data = ctx->obufmark;
iocb.data_length = (size_t)(ctx->obuflimit - ctx->obufmark);
iocb.payload_length = ctx->opayloadlen;
135 changes: 115 additions & 20 deletions wish/cpp/src/wish_handler_test.cc
Original file line number Diff line number Diff line change
@@ -1,42 +1,38 @@
#include "wish_handler.h"
#include <gtest/gtest.h>

#include <event2/buffer.h>
#include <event2/bufferevent.h>
#include <event2/event.h>
#include <memory>
#include <gtest/gtest.h>

#include <cstring>
#include <string>

class WishHandlerTest : public ::testing::Test {
protected:
void SetUp() override {
base_ = event_base_new();
ASSERT_NE(base_, nullptr);

struct bufferevent* pair[2];
int res = bufferevent_pair_new(base_, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS, pair);
ASSERT_EQ(res, 0);

server_bev_ = pair[0];
client_bev_ = pair[1];
}

void TearDown() override {
event_base_free(base_);
}

struct event_base* base_ = nullptr;
struct bufferevent* server_bev_ = nullptr;
struct bufferevent* client_bev_ = nullptr;
};

TEST_F(WishHandlerTest, HandshakeAndSimpleExchange) {
// We allocate on heap because EventCallback might execute `delete handler`.
// To avoid this causing problems, we ensure we don't double delete.
WishHandler* server = new WishHandler(server_bev_, true /* is_server */);
WishHandler* client = new WishHandler(client_bev_, false /* is_server */);
struct bufferevent* pair[2];
int res = bufferevent_pair_new(
base_, BEV_OPT_CLOSE_ON_FREE | BEV_OPT_DEFER_CALLBACKS, pair);
ASSERT_EQ(res, 0);

WishHandler* server = new WishHandler(pair[0], true /* is_server */);
WishHandler* client = new WishHandler(pair[1], false /* is_server */);

bool server_opened = false;
bool client_opened = false;

std::string received_from_client;
std::string received_from_server;

Expand Down Expand Up @@ -73,13 +69,112 @@ TEST_F(WishHandlerTest, HandshakeAndSimpleExchange) {

EXPECT_TRUE(client_opened);
EXPECT_TRUE(server_opened);

EXPECT_EQ(received_from_client, "Hello, Server!");
EXPECT_EQ(received_from_server, "Hello, Client!");

// Clean up
// If connection is still alive, we can safely delete them.
// Their destructors will free the bufferevents and wslay contexts.
delete server;
delete client;
}

// Helper: drain all bytes from a bufferevent's input buffer.
static void DrainInput(struct bufferevent* bev) {
struct evbuffer* buf = bufferevent_get_input(bev);
evbuffer_drain(buf, evbuffer_get_length(buf));
}

// Tests that the client does NOT mask frames when sending.
// WiSH doesn't use masking (unlike WebSocket over TCP).
//
// Strategy: use a raw bufferevent on one end of a pair as a fake server.
// Inject a valid HTTP 200 response to move the WishHandler into OPEN state,
// then call SendText and inspect the raw bytes for the mask bit.
TEST_F(WishHandlerTest, ClientSendsUnmasked) {
// No DEFER_CALLBACKS: pair transfers data synchronously between loop ticks.
struct bufferevent* pair[2];
int res = bufferevent_pair_new(base_, BEV_OPT_CLOSE_ON_FREE, pair);
ASSERT_EQ(res, 0);
// pair[0]: WishHandler client bev
// pair[1]: raw observer (fake server)
bufferevent_enable(pair[1], EV_READ | EV_WRITE);

WishHandler* client = new WishHandler(pair[0], false /* is_server */);
// Start writes the HTTP POST request and enables read on pair[0].
client->Start();

// Let client's HTTP request flow to pair[1].
event_base_loop(base_, EVLOOP_NONBLOCK);
DrainInput(pair[1]);

// Fake server sends HTTP 200 OK back through pair[1] -> pair[0].
const char* resp =
"HTTP/1.1 200 OK\r\nContent-Type: application/web-stream\r\n\r\n";
bufferevent_write(pair[1], resp, strlen(resp));

// Let the client process the response and transition to OPEN state.
event_base_loop(base_, EVLOOP_NONBLOCK);
DrainInput(pair[1]);

// The client should now be OPEN. Send a text message.
client->SendText("Hello");
event_base_loop(base_, EVLOOP_NONBLOCK);

struct evbuffer* raw = bufferevent_get_input(pair[1]);
size_t len = evbuffer_get_length(raw);
ASSERT_GT(len, 0) << "No bytes received from client after handshake";

unsigned char* bytes = evbuffer_pullup(raw, len);

// First byte: FIN=1, RSV=0, opcode=TEXT(0x1) => 0x81
EXPECT_EQ(bytes[0], 0x81u);

// Second byte: mask bit (0x80) + payload length.
// WiSH doesn't use masking.
bool is_masked = (bytes[1] & 0x80) != 0;
EXPECT_FALSE(is_masked) << "Client sent a masked frame! WiSH must not use masking.";

// WishHandler destructor frees pair[0].
delete client;
// free pair[1] manually.
bufferevent_free(pair[1]);
}

// Tests that the server does NOT mask frames when sending.
TEST_F(WishHandlerTest, ServerSendsUnmasked) {
struct bufferevent* pair[2];
int res = bufferevent_pair_new(base_, BEV_OPT_CLOSE_ON_FREE, pair);
ASSERT_EQ(res, 0);
// pair[0]: WishHandler server bev
// pair[1]: raw observer (fake client)
bufferevent_enable(pair[1], EV_READ | EV_WRITE);

WishHandler* server = new WishHandler(pair[0], true /* is_server */);
server->Start();

// Fake client sends HTTP POST to open the connection.
const char* req =
"POST / HTTP/1.1\r\nHost: localhost\r\n"
"Content-Type: application/web-stream\r\n\r\n";
bufferevent_write(pair[1], req, strlen(req));

// Let server process the request, transition to OPEN, and send 200 OK.
event_base_loop(base_, EVLOOP_NONBLOCK);
DrainInput(pair[1]);

// Server is now OPEN. Send a text message.
server->SendText("Hello");
event_base_loop(base_, EVLOOP_NONBLOCK);

struct evbuffer* raw = bufferevent_get_input(pair[1]);
size_t len = evbuffer_get_length(raw);
ASSERT_GT(len, 0) << "No bytes received from server after handshake";

unsigned char* bytes = evbuffer_pullup(raw, len);

EXPECT_EQ(bytes[0], 0x81u);

bool is_masked = (bytes[1] & 0x80) != 0;
EXPECT_FALSE(is_masked) << "Server sent a masked frame! WiSH must not use masking.";

delete server;
bufferevent_free(pair[1]);
}