2018-08-24 20:54:23 -07:00
|
|
|
/*----------------------------------------------------------------------------*/
|
2019-05-31 13:43:32 -07:00
|
|
|
/* Copyright (c) 2018-2019 FIRST. All Rights Reserved. */
|
2018-08-24 20:54:23 -07:00
|
|
|
/* Open Source Software - may be modified and shared by FRC teams. The code */
|
|
|
|
|
/* must be accompanied by the FIRST BSD license file in the root directory of */
|
|
|
|
|
/* the project. */
|
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
|
|
|
|
|
|
#include "wpi/WebSocket.h" // NOLINT(build/include_order)
|
|
|
|
|
|
|
|
|
|
#include "WebSocketTest.h"
|
|
|
|
|
#include "wpi/Base64.h"
|
|
|
|
|
#include "wpi/HttpParser.h"
|
|
|
|
|
#include "wpi/SmallString.h"
|
|
|
|
|
#include "wpi/raw_uv_ostream.h"
|
|
|
|
|
#include "wpi/sha1.h"
|
|
|
|
|
|
|
|
|
|
namespace wpi {
|
|
|
|
|
|
|
|
|
|
class WebSocketServerTest : public WebSocketTest {
|
|
|
|
|
public:
|
|
|
|
|
WebSocketServerTest() {
|
|
|
|
|
resp.headersComplete.connect([this](bool) { headersDone = true; });
|
|
|
|
|
|
|
|
|
|
serverPipe->Listen([this]() {
|
|
|
|
|
auto conn = serverPipe->Accept();
|
|
|
|
|
ws = WebSocket::CreateServer(*conn, "foo", "13");
|
|
|
|
|
if (setupWebSocket) setupWebSocket();
|
|
|
|
|
});
|
|
|
|
|
clientPipe->Connect(pipeName, [this]() {
|
|
|
|
|
clientPipe->StartRead();
|
|
|
|
|
clientPipe->data.connect([this](uv::Buffer& buf, size_t size) {
|
|
|
|
|
StringRef data{buf.base, size};
|
|
|
|
|
if (!headersDone) {
|
|
|
|
|
data = resp.Execute(data);
|
|
|
|
|
if (resp.HasError()) Finish();
|
|
|
|
|
ASSERT_EQ(resp.GetError(), HPE_OK)
|
|
|
|
|
<< http_errno_name(resp.GetError());
|
|
|
|
|
if (data.empty()) return;
|
|
|
|
|
}
|
|
|
|
|
wireData.insert(wireData.end(), data.bytes_begin(), data.bytes_end());
|
|
|
|
|
if (handleData) handleData(data);
|
|
|
|
|
});
|
|
|
|
|
clientPipe->end.connect([this]() { Finish(); });
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
std::function<void()> setupWebSocket;
|
|
|
|
|
std::function<void(StringRef)> handleData;
|
|
|
|
|
std::vector<uint8_t> wireData;
|
|
|
|
|
std::shared_ptr<WebSocket> ws;
|
|
|
|
|
HttpParser resp{HttpParser::kResponse};
|
|
|
|
|
bool headersDone = false;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// Terminate closes the endpoint but doesn't send a close frame.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
TEST_F(WebSocketServerTest, Terminate) {
|
|
|
|
|
int gotClosed = 0;
|
|
|
|
|
setupWebSocket = [&] {
|
|
|
|
|
ws->open.connect([&](StringRef) { ws->Terminate(); });
|
|
|
|
|
ws->closed.connect([&](uint16_t code, StringRef reason) {
|
|
|
|
|
++gotClosed;
|
|
|
|
|
ASSERT_EQ(code, 1006) << "reason: " << reason;
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
loop->Run();
|
|
|
|
|
|
|
|
|
|
ASSERT_TRUE(wireData.empty()); // terminate doesn't send data
|
|
|
|
|
ASSERT_EQ(gotClosed, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_F(WebSocketServerTest, TerminateCode) {
|
|
|
|
|
int gotClosed = 0;
|
|
|
|
|
setupWebSocket = [&] {
|
|
|
|
|
ws->open.connect([&](StringRef) { ws->Terminate(1000); });
|
|
|
|
|
ws->closed.connect([&](uint16_t code, StringRef reason) {
|
|
|
|
|
++gotClosed;
|
|
|
|
|
ASSERT_EQ(code, 1000) << "reason: " << reason;
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
loop->Run();
|
|
|
|
|
|
|
|
|
|
ASSERT_TRUE(wireData.empty()); // terminate doesn't send data
|
|
|
|
|
ASSERT_EQ(gotClosed, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_F(WebSocketServerTest, TerminateReason) {
|
|
|
|
|
int gotClosed = 0;
|
|
|
|
|
setupWebSocket = [&] {
|
|
|
|
|
ws->open.connect([&](StringRef) { ws->Terminate(1000, "reason"); });
|
|
|
|
|
ws->closed.connect([&](uint16_t code, StringRef reason) {
|
|
|
|
|
++gotClosed;
|
|
|
|
|
ASSERT_EQ(code, 1000);
|
|
|
|
|
ASSERT_EQ(reason, "reason");
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
loop->Run();
|
|
|
|
|
|
|
|
|
|
ASSERT_TRUE(wireData.empty()); // terminate doesn't send data
|
|
|
|
|
ASSERT_EQ(gotClosed, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// Close() sends a close frame.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
TEST_F(WebSocketServerTest, CloseBasic) {
|
|
|
|
|
int gotClosed = 0;
|
|
|
|
|
setupWebSocket = [&] {
|
|
|
|
|
ws->open.connect([&](StringRef) { ws->Close(); });
|
|
|
|
|
ws->closed.connect([&](uint16_t code, StringRef reason) {
|
|
|
|
|
++gotClosed;
|
|
|
|
|
ASSERT_EQ(code, 1005) << "reason: " << reason;
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
// need to respond with close for server to finish shutdown
|
|
|
|
|
auto message = BuildMessage(0x08, true, true, {});
|
|
|
|
|
handleData = [&](StringRef) {
|
|
|
|
|
clientPipe->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
loop->Run();
|
|
|
|
|
|
|
|
|
|
auto expectData = BuildMessage(0x08, true, false, {});
|
|
|
|
|
ASSERT_EQ(wireData, expectData);
|
|
|
|
|
ASSERT_EQ(gotClosed, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_F(WebSocketServerTest, CloseCode) {
|
|
|
|
|
int gotClosed = 0;
|
|
|
|
|
setupWebSocket = [&] {
|
|
|
|
|
ws->open.connect([&](StringRef) { ws->Close(1000); });
|
|
|
|
|
ws->closed.connect([&](uint16_t code, StringRef reason) {
|
|
|
|
|
++gotClosed;
|
|
|
|
|
ASSERT_EQ(code, 1000) << "reason: " << reason;
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
// need to respond with close for server to finish shutdown
|
|
|
|
|
auto message = BuildMessage(0x08, true, true, {0x03u, 0xe8u});
|
|
|
|
|
handleData = [&](StringRef) {
|
|
|
|
|
clientPipe->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
loop->Run();
|
|
|
|
|
|
|
|
|
|
auto expectData = BuildMessage(0x08, true, false, {0x03u, 0xe8u});
|
|
|
|
|
ASSERT_EQ(wireData, expectData);
|
|
|
|
|
ASSERT_EQ(gotClosed, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_F(WebSocketServerTest, CloseReason) {
|
|
|
|
|
int gotClosed = 0;
|
|
|
|
|
setupWebSocket = [&] {
|
|
|
|
|
ws->open.connect([&](StringRef) { ws->Close(1000, "hangup"); });
|
|
|
|
|
ws->closed.connect([&](uint16_t code, StringRef reason) {
|
|
|
|
|
++gotClosed;
|
|
|
|
|
ASSERT_EQ(code, 1000);
|
|
|
|
|
ASSERT_EQ(reason, "hangup");
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
// need to respond with close for server to finish shutdown
|
|
|
|
|
auto message = BuildMessage(0x08, true, true,
|
|
|
|
|
{0x03u, 0xe8u, 'h', 'a', 'n', 'g', 'u', 'p'});
|
|
|
|
|
handleData = [&](StringRef) {
|
|
|
|
|
clientPipe->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
loop->Run();
|
|
|
|
|
|
|
|
|
|
auto expectData = BuildMessage(0x08, true, false,
|
|
|
|
|
{0x03u, 0xe8u, 'h', 'a', 'n', 'g', 'u', 'p'});
|
|
|
|
|
ASSERT_EQ(wireData, expectData);
|
|
|
|
|
ASSERT_EQ(gotClosed, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// Receiving a close frame results in closure and echoing the close frame.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
TEST_F(WebSocketServerTest, ReceiveCloseBasic) {
|
|
|
|
|
int gotClosed = 0;
|
|
|
|
|
setupWebSocket = [&] {
|
|
|
|
|
ws->closed.connect([&](uint16_t code, StringRef reason) {
|
|
|
|
|
++gotClosed;
|
|
|
|
|
ASSERT_EQ(code, 1005) << "reason: " << reason;
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
auto message = BuildMessage(0x08, true, true, {});
|
|
|
|
|
resp.headersComplete.connect([&](bool) {
|
|
|
|
|
clientPipe->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
loop->Run();
|
|
|
|
|
|
|
|
|
|
// the endpoint should echo the message
|
|
|
|
|
auto expectData = BuildMessage(0x08, true, false, {});
|
|
|
|
|
ASSERT_EQ(wireData, expectData);
|
|
|
|
|
ASSERT_EQ(gotClosed, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_F(WebSocketServerTest, ReceiveCloseCode) {
|
|
|
|
|
int gotClosed = 0;
|
|
|
|
|
setupWebSocket = [&] {
|
|
|
|
|
ws->closed.connect([&](uint16_t code, StringRef reason) {
|
|
|
|
|
++gotClosed;
|
|
|
|
|
ASSERT_EQ(code, 1000) << "reason: " << reason;
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
auto message = BuildMessage(0x08, true, true, {0x03u, 0xe8u});
|
|
|
|
|
resp.headersComplete.connect([&](bool) {
|
|
|
|
|
clientPipe->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
loop->Run();
|
|
|
|
|
|
|
|
|
|
// the endpoint should echo the message
|
|
|
|
|
auto expectData = BuildMessage(0x08, true, false, {0x03u, 0xe8u});
|
|
|
|
|
ASSERT_EQ(wireData, expectData);
|
|
|
|
|
ASSERT_EQ(gotClosed, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_F(WebSocketServerTest, ReceiveCloseReason) {
|
|
|
|
|
int gotClosed = 0;
|
|
|
|
|
setupWebSocket = [&] {
|
|
|
|
|
ws->closed.connect([&](uint16_t code, StringRef reason) {
|
|
|
|
|
++gotClosed;
|
|
|
|
|
ASSERT_EQ(code, 1000);
|
|
|
|
|
ASSERT_EQ(reason, "hangup");
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
auto message = BuildMessage(0x08, true, true,
|
|
|
|
|
{0x03u, 0xe8u, 'h', 'a', 'n', 'g', 'u', 'p'});
|
|
|
|
|
resp.headersComplete.connect([&](bool) {
|
|
|
|
|
clientPipe->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
loop->Run();
|
|
|
|
|
|
|
|
|
|
// the endpoint should echo the message
|
|
|
|
|
auto expectData = BuildMessage(0x08, true, false,
|
|
|
|
|
{0x03u, 0xe8u, 'h', 'a', 'n', 'g', 'u', 'p'});
|
|
|
|
|
ASSERT_EQ(wireData, expectData);
|
|
|
|
|
ASSERT_EQ(gotClosed, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// If an unknown opcode is received, the receiving endpoint MUST _Fail the
|
|
|
|
|
// WebSocket Connection_.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
class WebSocketServerBadOpcodeTest
|
|
|
|
|
: public WebSocketServerTest,
|
|
|
|
|
public ::testing::WithParamInterface<uint8_t> {};
|
|
|
|
|
|
2019-05-31 13:43:32 -07:00
|
|
|
INSTANTIATE_TEST_SUITE_P(WebSocketServerBadOpcodeTests,
|
|
|
|
|
WebSocketServerBadOpcodeTest,
|
|
|
|
|
::testing::Values(3, 4, 5, 6, 7, 0xb, 0xc, 0xd, 0xe,
|
|
|
|
|
0xf));
|
2018-08-24 20:54:23 -07:00
|
|
|
|
|
|
|
|
TEST_P(WebSocketServerBadOpcodeTest, Receive) {
|
|
|
|
|
int gotCallback = 0;
|
|
|
|
|
std::vector<uint8_t> data(4, 0x03);
|
|
|
|
|
setupWebSocket = [&] {
|
|
|
|
|
ws->closed.connect([&](uint16_t code, StringRef reason) {
|
|
|
|
|
++gotCallback;
|
|
|
|
|
ASSERT_EQ(code, 1002) << "reason: " << reason;
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
auto message = BuildMessage(GetParam(), true, true, data);
|
|
|
|
|
resp.headersComplete.connect([&](bool) {
|
|
|
|
|
clientPipe->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
loop->Run();
|
|
|
|
|
|
|
|
|
|
ASSERT_EQ(gotCallback, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// Control frames themselves MUST NOT be fragmented.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
class WebSocketServerControlFrameTest
|
|
|
|
|
: public WebSocketServerTest,
|
|
|
|
|
public ::testing::WithParamInterface<uint8_t> {};
|
|
|
|
|
|
2019-05-31 13:43:32 -07:00
|
|
|
INSTANTIATE_TEST_SUITE_P(WebSocketServerControlFrameTests,
|
|
|
|
|
WebSocketServerControlFrameTest,
|
|
|
|
|
::testing::Values(0x8, 0x9, 0xa));
|
2018-08-24 20:54:23 -07:00
|
|
|
|
|
|
|
|
TEST_P(WebSocketServerControlFrameTest, ReceiveFragment) {
|
|
|
|
|
int gotCallback = 0;
|
|
|
|
|
std::vector<uint8_t> data(4, 0x03);
|
|
|
|
|
setupWebSocket = [&] {
|
|
|
|
|
ws->closed.connect([&](uint16_t code, StringRef reason) {
|
|
|
|
|
++gotCallback;
|
|
|
|
|
ASSERT_EQ(code, 1002) << "reason: " << reason;
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
auto message = BuildMessage(GetParam(), false, true, data);
|
|
|
|
|
resp.headersComplete.connect([&](bool) {
|
|
|
|
|
clientPipe->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
loop->Run();
|
|
|
|
|
|
|
|
|
|
ASSERT_EQ(gotCallback, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// A fragmented message consists of a single frame with the FIN bit
|
|
|
|
|
// clear and an opcode other than 0, followed by zero or more frames
|
|
|
|
|
// with the FIN bit clear and the opcode set to 0, and terminated by
|
|
|
|
|
// a single frame with the FIN bit set and an opcode of 0.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
// No previous message
|
|
|
|
|
TEST_F(WebSocketServerTest, ReceiveFragmentInvalidNoPrevFrame) {
|
|
|
|
|
int gotCallback = 0;
|
|
|
|
|
std::vector<uint8_t> data(4, 0x03);
|
|
|
|
|
setupWebSocket = [&] {
|
|
|
|
|
ws->closed.connect([&](uint16_t code, StringRef reason) {
|
|
|
|
|
++gotCallback;
|
|
|
|
|
ASSERT_EQ(code, 1002) << "reason: " << reason;
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
auto message = BuildMessage(0x00, false, true, data);
|
|
|
|
|
resp.headersComplete.connect([&](bool) {
|
|
|
|
|
clientPipe->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
loop->Run();
|
|
|
|
|
|
|
|
|
|
ASSERT_EQ(gotCallback, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// No previous message with FIN=1.
|
|
|
|
|
TEST_F(WebSocketServerTest, ReceiveFragmentInvalidNoPrevFragment) {
|
|
|
|
|
int gotCallback = 0;
|
|
|
|
|
std::vector<uint8_t> data(4, 0x03);
|
|
|
|
|
setupWebSocket = [&] {
|
|
|
|
|
ws->closed.connect([&](uint16_t code, StringRef reason) {
|
|
|
|
|
++gotCallback;
|
|
|
|
|
ASSERT_EQ(code, 1002) << "reason: " << reason;
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
auto message = BuildMessage(0x01, true, true, {}); // FIN=1
|
|
|
|
|
auto message2 = BuildMessage(0x00, false, true, data);
|
|
|
|
|
resp.headersComplete.connect([&](bool) {
|
|
|
|
|
clientPipe->Write({uv::Buffer(message), uv::Buffer(message2)},
|
|
|
|
|
[&](auto bufs, uv::Error) {});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
loop->Run();
|
|
|
|
|
|
|
|
|
|
ASSERT_EQ(gotCallback, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Incomplete fragment
|
|
|
|
|
TEST_F(WebSocketServerTest, ReceiveFragmentInvalidIncomplete) {
|
|
|
|
|
int gotCallback = 0;
|
|
|
|
|
setupWebSocket = [&] {
|
|
|
|
|
ws->closed.connect([&](uint16_t code, StringRef reason) {
|
|
|
|
|
++gotCallback;
|
|
|
|
|
ASSERT_EQ(code, 1002) << "reason: " << reason;
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
auto message = BuildMessage(0x01, false, true, {});
|
|
|
|
|
auto message2 = BuildMessage(0x00, false, true, {});
|
|
|
|
|
auto message3 = BuildMessage(0x01, true, true, {});
|
|
|
|
|
resp.headersComplete.connect([&](bool) {
|
|
|
|
|
clientPipe->Write(
|
|
|
|
|
{uv::Buffer(message), uv::Buffer(message2), uv::Buffer(message3)},
|
|
|
|
|
[&](auto bufs, uv::Error) {});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
loop->Run();
|
|
|
|
|
|
|
|
|
|
ASSERT_EQ(gotCallback, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Normally fragments are combined into a single callback
|
|
|
|
|
TEST_F(WebSocketServerTest, ReceiveFragment) {
|
|
|
|
|
int gotCallback = 0;
|
|
|
|
|
|
|
|
|
|
std::vector<uint8_t> data(4, 0x03);
|
|
|
|
|
std::vector<uint8_t> data2(4, 0x04);
|
|
|
|
|
std::vector<uint8_t> data3(4, 0x05);
|
|
|
|
|
std::vector<uint8_t> combData{data};
|
|
|
|
|
combData.insert(combData.end(), data2.begin(), data2.end());
|
|
|
|
|
combData.insert(combData.end(), data3.begin(), data3.end());
|
|
|
|
|
|
|
|
|
|
setupWebSocket = [&] {
|
|
|
|
|
ws->binary.connect([&](ArrayRef<uint8_t> inData, bool fin) {
|
|
|
|
|
++gotCallback;
|
|
|
|
|
ws->Terminate();
|
|
|
|
|
ASSERT_TRUE(fin);
|
|
|
|
|
std::vector<uint8_t> recvData{inData.begin(), inData.end()};
|
|
|
|
|
ASSERT_EQ(combData, recvData);
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
auto message = BuildMessage(0x02, false, true, data);
|
|
|
|
|
auto message2 = BuildMessage(0x00, false, true, data2);
|
|
|
|
|
auto message3 = BuildMessage(0x00, true, true, data3);
|
|
|
|
|
resp.headersComplete.connect([&](bool) {
|
|
|
|
|
clientPipe->Write(
|
|
|
|
|
{uv::Buffer(message), uv::Buffer(message2), uv::Buffer(message3)},
|
|
|
|
|
[&](auto bufs, uv::Error) {});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
loop->Run();
|
|
|
|
|
|
|
|
|
|
ASSERT_EQ(gotCallback, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// But can be configured for multiple callbacks
|
|
|
|
|
TEST_F(WebSocketServerTest, ReceiveFragmentSeparate) {
|
|
|
|
|
int gotCallback = 0;
|
|
|
|
|
|
|
|
|
|
std::vector<uint8_t> data(4, 0x03);
|
|
|
|
|
std::vector<uint8_t> data2(4, 0x04);
|
|
|
|
|
std::vector<uint8_t> data3(4, 0x05);
|
|
|
|
|
std::vector<uint8_t> combData{data};
|
|
|
|
|
combData.insert(combData.end(), data2.begin(), data2.end());
|
|
|
|
|
combData.insert(combData.end(), data3.begin(), data3.end());
|
|
|
|
|
|
|
|
|
|
setupWebSocket = [&] {
|
|
|
|
|
ws->SetCombineFragments(false);
|
|
|
|
|
ws->binary.connect([&](ArrayRef<uint8_t> inData, bool fin) {
|
|
|
|
|
std::vector<uint8_t> recvData{inData.begin(), inData.end()};
|
|
|
|
|
switch (++gotCallback) {
|
|
|
|
|
case 1:
|
|
|
|
|
ASSERT_FALSE(fin);
|
|
|
|
|
ASSERT_EQ(data, recvData);
|
|
|
|
|
break;
|
|
|
|
|
case 2:
|
|
|
|
|
ASSERT_FALSE(fin);
|
|
|
|
|
ASSERT_EQ(data2, recvData);
|
|
|
|
|
break;
|
|
|
|
|
case 3:
|
|
|
|
|
ws->Terminate();
|
|
|
|
|
ASSERT_TRUE(fin);
|
|
|
|
|
ASSERT_EQ(data3, recvData);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
FAIL() << "too many callbacks";
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
auto message = BuildMessage(0x02, false, true, data);
|
|
|
|
|
auto message2 = BuildMessage(0x00, false, true, data2);
|
|
|
|
|
auto message3 = BuildMessage(0x00, true, true, data3);
|
|
|
|
|
resp.headersComplete.connect([&](bool) {
|
|
|
|
|
clientPipe->Write(
|
|
|
|
|
{uv::Buffer(message), uv::Buffer(message2), uv::Buffer(message3)},
|
|
|
|
|
[&](auto bufs, uv::Error) {});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
loop->Run();
|
|
|
|
|
|
|
|
|
|
ASSERT_EQ(gotCallback, 3);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// Maximum message size is limited.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
// Single message
|
|
|
|
|
TEST_F(WebSocketServerTest, ReceiveTooLarge) {
|
|
|
|
|
int gotCallback = 0;
|
|
|
|
|
std::vector<uint8_t> data(2048, 0x03u);
|
|
|
|
|
setupWebSocket = [&] {
|
|
|
|
|
ws->SetMaxMessageSize(1024);
|
|
|
|
|
ws->binary.connect([&](auto, bool) {
|
|
|
|
|
ws->Terminate();
|
|
|
|
|
FAIL() << "Should not have gotten unmasked message";
|
|
|
|
|
});
|
|
|
|
|
ws->closed.connect([&](uint16_t code, StringRef reason) {
|
|
|
|
|
++gotCallback;
|
|
|
|
|
ASSERT_EQ(code, 1009) << "reason: " << reason;
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
auto message = BuildMessage(0x01, true, true, data);
|
|
|
|
|
resp.headersComplete.connect([&](bool) {
|
|
|
|
|
clientPipe->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
loop->Run();
|
|
|
|
|
|
|
|
|
|
ASSERT_EQ(gotCallback, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Applied across fragments if combining
|
|
|
|
|
TEST_F(WebSocketServerTest, ReceiveTooLargeFragmented) {
|
|
|
|
|
int gotCallback = 0;
|
|
|
|
|
std::vector<uint8_t> data(768, 0x03u);
|
|
|
|
|
setupWebSocket = [&] {
|
|
|
|
|
ws->SetMaxMessageSize(1024);
|
|
|
|
|
ws->binary.connect([&](auto, bool) {
|
|
|
|
|
ws->Terminate();
|
|
|
|
|
FAIL() << "Should not have gotten unmasked message";
|
|
|
|
|
});
|
|
|
|
|
ws->closed.connect([&](uint16_t code, StringRef reason) {
|
|
|
|
|
++gotCallback;
|
|
|
|
|
ASSERT_EQ(code, 1009) << "reason: " << reason;
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
auto message = BuildMessage(0x01, false, true, data);
|
|
|
|
|
auto message2 = BuildMessage(0x00, true, true, data);
|
|
|
|
|
resp.headersComplete.connect([&](bool) {
|
|
|
|
|
clientPipe->Write({uv::Buffer(message), uv::Buffer(message2)},
|
|
|
|
|
[&](auto bufs, uv::Error) {});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
loop->Run();
|
|
|
|
|
|
|
|
|
|
ASSERT_EQ(gotCallback, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// Send and receive data.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
class WebSocketServerDataTest : public WebSocketServerTest,
|
|
|
|
|
public ::testing::WithParamInterface<size_t> {};
|
|
|
|
|
|
2019-05-31 13:43:32 -07:00
|
|
|
INSTANTIATE_TEST_SUITE_P(WebSocketServerDataTests, WebSocketServerDataTest,
|
|
|
|
|
::testing::Values(0, 1, 125, 126, 65535, 65536));
|
2018-08-24 20:54:23 -07:00
|
|
|
|
|
|
|
|
TEST_P(WebSocketServerDataTest, SendText) {
|
|
|
|
|
int gotCallback = 0;
|
|
|
|
|
std::vector<uint8_t> data(GetParam(), ' ');
|
|
|
|
|
setupWebSocket = [&] {
|
|
|
|
|
ws->open.connect([&](StringRef) {
|
|
|
|
|
ws->SendText(uv::Buffer(data), [&](auto bufs, uv::Error) {
|
|
|
|
|
++gotCallback;
|
|
|
|
|
ws->Terminate();
|
|
|
|
|
ASSERT_FALSE(bufs.empty());
|
|
|
|
|
ASSERT_EQ(bufs[0].base, reinterpret_cast<const char*>(data.data()));
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
loop->Run();
|
|
|
|
|
|
|
|
|
|
auto expectData = BuildMessage(0x01, true, false, data);
|
|
|
|
|
ASSERT_EQ(wireData, expectData);
|
|
|
|
|
ASSERT_EQ(gotCallback, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_P(WebSocketServerDataTest, SendBinary) {
|
|
|
|
|
int gotCallback = 0;
|
|
|
|
|
std::vector<uint8_t> data(GetParam(), 0x03u);
|
|
|
|
|
setupWebSocket = [&] {
|
|
|
|
|
ws->open.connect([&](StringRef) {
|
|
|
|
|
ws->SendBinary(uv::Buffer(data), [&](auto bufs, uv::Error) {
|
|
|
|
|
++gotCallback;
|
|
|
|
|
ws->Terminate();
|
|
|
|
|
ASSERT_FALSE(bufs.empty());
|
|
|
|
|
ASSERT_EQ(bufs[0].base, reinterpret_cast<const char*>(data.data()));
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
loop->Run();
|
|
|
|
|
|
|
|
|
|
auto expectData = BuildMessage(0x02, true, false, data);
|
|
|
|
|
ASSERT_EQ(wireData, expectData);
|
|
|
|
|
ASSERT_EQ(gotCallback, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_P(WebSocketServerDataTest, SendPing) {
|
|
|
|
|
int gotCallback = 0;
|
|
|
|
|
std::vector<uint8_t> data(GetParam(), 0x03u);
|
|
|
|
|
setupWebSocket = [&] {
|
|
|
|
|
ws->open.connect([&](StringRef) {
|
|
|
|
|
ws->SendPing(uv::Buffer(data), [&](auto bufs, uv::Error) {
|
|
|
|
|
++gotCallback;
|
|
|
|
|
ws->Terminate();
|
|
|
|
|
ASSERT_FALSE(bufs.empty());
|
|
|
|
|
ASSERT_EQ(bufs[0].base, reinterpret_cast<const char*>(data.data()));
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
loop->Run();
|
|
|
|
|
|
|
|
|
|
auto expectData = BuildMessage(0x09, true, false, data);
|
|
|
|
|
ASSERT_EQ(wireData, expectData);
|
|
|
|
|
ASSERT_EQ(gotCallback, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_P(WebSocketServerDataTest, SendPong) {
|
|
|
|
|
int gotCallback = 0;
|
|
|
|
|
std::vector<uint8_t> data(GetParam(), 0x03u);
|
|
|
|
|
setupWebSocket = [&] {
|
|
|
|
|
ws->open.connect([&](StringRef) {
|
|
|
|
|
ws->SendPong(uv::Buffer(data), [&](auto bufs, uv::Error) {
|
|
|
|
|
++gotCallback;
|
|
|
|
|
ws->Terminate();
|
|
|
|
|
ASSERT_FALSE(bufs.empty());
|
|
|
|
|
ASSERT_EQ(bufs[0].base, reinterpret_cast<const char*>(data.data()));
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
loop->Run();
|
|
|
|
|
|
|
|
|
|
auto expectData = BuildMessage(0x0a, true, false, data);
|
|
|
|
|
ASSERT_EQ(wireData, expectData);
|
|
|
|
|
ASSERT_EQ(gotCallback, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_P(WebSocketServerDataTest, ReceiveText) {
|
|
|
|
|
int gotCallback = 0;
|
|
|
|
|
std::vector<uint8_t> data(GetParam(), ' ');
|
|
|
|
|
setupWebSocket = [&] {
|
|
|
|
|
ws->text.connect([&](StringRef inData, bool fin) {
|
|
|
|
|
++gotCallback;
|
|
|
|
|
ws->Terminate();
|
|
|
|
|
ASSERT_TRUE(fin);
|
|
|
|
|
std::vector<uint8_t> recvData;
|
|
|
|
|
recvData.insert(recvData.end(), inData.bytes_begin(), inData.bytes_end());
|
|
|
|
|
ASSERT_EQ(data, recvData);
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
auto message = BuildMessage(0x01, true, true, data);
|
|
|
|
|
resp.headersComplete.connect([&](bool) {
|
|
|
|
|
clientPipe->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
loop->Run();
|
|
|
|
|
|
|
|
|
|
ASSERT_EQ(gotCallback, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_P(WebSocketServerDataTest, ReceiveBinary) {
|
|
|
|
|
int gotCallback = 0;
|
|
|
|
|
std::vector<uint8_t> data(GetParam(), 0x03u);
|
|
|
|
|
setupWebSocket = [&] {
|
|
|
|
|
ws->binary.connect([&](ArrayRef<uint8_t> inData, bool fin) {
|
|
|
|
|
++gotCallback;
|
|
|
|
|
ws->Terminate();
|
|
|
|
|
ASSERT_TRUE(fin);
|
|
|
|
|
std::vector<uint8_t> recvData{inData.begin(), inData.end()};
|
|
|
|
|
ASSERT_EQ(data, recvData);
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
auto message = BuildMessage(0x02, true, true, data);
|
|
|
|
|
resp.headersComplete.connect([&](bool) {
|
|
|
|
|
clientPipe->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
loop->Run();
|
|
|
|
|
|
|
|
|
|
ASSERT_EQ(gotCallback, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_P(WebSocketServerDataTest, ReceivePing) {
|
|
|
|
|
int gotCallback = 0;
|
|
|
|
|
std::vector<uint8_t> data(GetParam(), 0x03u);
|
|
|
|
|
setupWebSocket = [&] {
|
|
|
|
|
ws->ping.connect([&](ArrayRef<uint8_t> inData) {
|
|
|
|
|
++gotCallback;
|
|
|
|
|
ws->Terminate();
|
|
|
|
|
std::vector<uint8_t> recvData{inData.begin(), inData.end()};
|
|
|
|
|
ASSERT_EQ(data, recvData);
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
auto message = BuildMessage(0x09, true, true, data);
|
|
|
|
|
resp.headersComplete.connect([&](bool) {
|
|
|
|
|
clientPipe->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
loop->Run();
|
|
|
|
|
|
|
|
|
|
ASSERT_EQ(gotCallback, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
TEST_P(WebSocketServerDataTest, ReceivePong) {
|
|
|
|
|
int gotCallback = 0;
|
|
|
|
|
std::vector<uint8_t> data(GetParam(), 0x03u);
|
|
|
|
|
setupWebSocket = [&] {
|
|
|
|
|
ws->pong.connect([&](ArrayRef<uint8_t> inData) {
|
|
|
|
|
++gotCallback;
|
|
|
|
|
ws->Terminate();
|
|
|
|
|
std::vector<uint8_t> recvData{inData.begin(), inData.end()};
|
|
|
|
|
ASSERT_EQ(data, recvData);
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
auto message = BuildMessage(0x0a, true, true, data);
|
|
|
|
|
resp.headersComplete.connect([&](bool) {
|
|
|
|
|
clientPipe->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
loop->Run();
|
|
|
|
|
|
|
|
|
|
ASSERT_EQ(gotCallback, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//
|
|
|
|
|
// The server must close the connection if an unmasked frame is received.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
TEST_P(WebSocketServerDataTest, ReceiveUnmasked) {
|
|
|
|
|
int gotCallback = 0;
|
|
|
|
|
std::vector<uint8_t> data(GetParam(), ' ');
|
|
|
|
|
setupWebSocket = [&] {
|
|
|
|
|
ws->text.connect([&](StringRef, bool) {
|
|
|
|
|
ws->Terminate();
|
|
|
|
|
FAIL() << "Should not have gotten unmasked message";
|
|
|
|
|
});
|
|
|
|
|
ws->closed.connect([&](uint16_t code, StringRef reason) {
|
|
|
|
|
++gotCallback;
|
|
|
|
|
ASSERT_EQ(code, 1002) << "reason: " << reason;
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
auto message = BuildMessage(0x01, true, false, data);
|
|
|
|
|
resp.headersComplete.connect([&](bool) {
|
|
|
|
|
clientPipe->Write(uv::Buffer(message), [&](auto bufs, uv::Error) {});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
loop->Run();
|
|
|
|
|
|
|
|
|
|
ASSERT_EQ(gotCallback, 1);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
} // namespace wpi
|