[wpinet] Move network portions of wpiutil into new wpinet library (#4077)

This commit is contained in:
Peter Johnson
2022-05-07 10:54:14 -07:00
committed by GitHub
parent b33715db15
commit d673ead481
327 changed files with 1783 additions and 1179 deletions

View File

@@ -1,206 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/HttpParser.h" // NOLINT(build/include_order)
#include "gtest/gtest.h"
namespace wpi {
TEST(HttpParserTest, UrlMethodHeadersComplete) {
HttpParser p{HttpParser::kRequest};
int callbacks = 0;
p.url.connect([&](std::string_view path) {
ASSERT_EQ(path, "/foo/bar");
ASSERT_EQ(p.GetUrl(), "/foo/bar");
++callbacks;
});
p.Execute("GET /foo");
p.Execute("/bar");
ASSERT_EQ(callbacks, 0);
p.Execute(" HTTP/1.1\r\n\r\n");
ASSERT_EQ(callbacks, 1);
ASSERT_EQ(p.GetUrl(), "/foo/bar");
ASSERT_EQ(p.GetMethod(), HTTP_GET);
ASSERT_FALSE(p.HasError());
}
TEST(HttpParserTest, UrlMethodHeader) {
HttpParser p{HttpParser::kRequest};
int callbacks = 0;
p.url.connect([&](std::string_view path) {
ASSERT_EQ(path, "/foo/bar");
ASSERT_EQ(p.GetUrl(), "/foo/bar");
++callbacks;
});
p.Execute("GET /foo");
p.Execute("/bar");
ASSERT_EQ(callbacks, 0);
p.Execute(" HTTP/1.1\r\n");
ASSERT_EQ(callbacks, 0);
p.Execute("F");
ASSERT_EQ(callbacks, 1);
ASSERT_EQ(p.GetUrl(), "/foo/bar");
ASSERT_EQ(p.GetMethod(), HTTP_GET);
ASSERT_FALSE(p.HasError());
}
TEST(HttpParserTest, StatusHeadersComplete) {
HttpParser p{HttpParser::kResponse};
int callbacks = 0;
p.status.connect([&](std::string_view status) {
ASSERT_EQ(status, "OK");
ASSERT_EQ(p.GetStatusCode(), 200u);
++callbacks;
});
p.Execute("HTTP/1.1 200");
p.Execute(" OK");
ASSERT_EQ(callbacks, 0);
p.Execute("\r\n\r\n");
ASSERT_EQ(callbacks, 1);
ASSERT_EQ(p.GetStatusCode(), 200u);
ASSERT_FALSE(p.HasError());
}
TEST(HttpParserTest, StatusHeader) {
HttpParser p{HttpParser::kResponse};
int callbacks = 0;
p.status.connect([&](std::string_view status) {
ASSERT_EQ(status, "OK");
ASSERT_EQ(p.GetStatusCode(), 200u);
++callbacks;
});
p.Execute("HTTP/1.1 200");
p.Execute(" OK\r\n");
ASSERT_EQ(callbacks, 0);
p.Execute("F");
ASSERT_EQ(callbacks, 1);
ASSERT_EQ(p.GetStatusCode(), 200u);
ASSERT_FALSE(p.HasError());
}
TEST(HttpParserTest, HeaderFieldComplete) {
HttpParser p{HttpParser::kRequest};
int callbacks = 0;
p.header.connect([&](std::string_view name, std::string_view value) {
ASSERT_EQ(name, "Foo");
ASSERT_EQ(value, "Bar");
++callbacks;
});
p.Execute("GET / HTTP/1.1\r\n");
ASSERT_EQ(callbacks, 0);
p.Execute("Fo");
ASSERT_EQ(callbacks, 0);
p.Execute("o: ");
ASSERT_EQ(callbacks, 0);
p.Execute("Bar");
ASSERT_EQ(callbacks, 0);
p.Execute("\r\n");
ASSERT_EQ(callbacks, 0);
p.Execute("\r\n");
ASSERT_EQ(callbacks, 1);
ASSERT_FALSE(p.HasError());
}
TEST(HttpParserTest, HeaderFieldNext) {
HttpParser p{HttpParser::kRequest};
int callbacks = 0;
p.header.connect([&](std::string_view name, std::string_view value) {
ASSERT_EQ(name, "Foo");
ASSERT_EQ(value, "Bar");
++callbacks;
});
p.Execute("GET / HTTP/1.1\r\n");
ASSERT_EQ(callbacks, 0);
p.Execute("Fo");
ASSERT_EQ(callbacks, 0);
p.Execute("o: ");
ASSERT_EQ(callbacks, 0);
p.Execute("Bar");
ASSERT_EQ(callbacks, 0);
p.Execute("\r\n");
ASSERT_EQ(callbacks, 0);
p.Execute("F");
ASSERT_EQ(callbacks, 1);
ASSERT_FALSE(p.HasError());
}
TEST(HttpParserTest, HeadersComplete) {
HttpParser p{HttpParser::kRequest};
int callbacks = 0;
p.headersComplete.connect([&](bool keepAlive) {
ASSERT_EQ(keepAlive, false);
++callbacks;
});
p.Execute("GET / HTTP/1.0\r\n");
ASSERT_EQ(callbacks, 0);
p.Execute("\r\n");
ASSERT_EQ(callbacks, 1);
ASSERT_FALSE(p.HasError());
}
TEST(HttpParserTest, HeadersCompleteHTTP11) {
HttpParser p{HttpParser::kRequest};
int callbacks = 0;
p.headersComplete.connect([&](bool keepAlive) {
ASSERT_EQ(keepAlive, true);
++callbacks;
});
p.Execute("GET / HTTP/1.1\r\n");
ASSERT_EQ(callbacks, 0);
p.Execute("\r\n");
ASSERT_EQ(callbacks, 1);
ASSERT_FALSE(p.HasError());
}
TEST(HttpParserTest, HeadersCompleteKeepAlive) {
HttpParser p{HttpParser::kRequest};
int callbacks = 0;
p.headersComplete.connect([&](bool keepAlive) {
ASSERT_EQ(keepAlive, true);
++callbacks;
});
p.Execute("GET / HTTP/1.0\r\n");
ASSERT_EQ(callbacks, 0);
p.Execute("Connection: Keep-Alive\r\n");
ASSERT_EQ(callbacks, 0);
p.Execute("\r\n");
ASSERT_EQ(callbacks, 1);
ASSERT_FALSE(p.HasError());
}
TEST(HttpParserTest, HeadersCompleteUpgrade) {
HttpParser p{HttpParser::kRequest};
int callbacks = 0;
p.headersComplete.connect([&](bool) {
ASSERT_TRUE(p.IsUpgrade());
++callbacks;
});
p.Execute("GET / HTTP/1.0\r\n");
ASSERT_EQ(callbacks, 0);
p.Execute("Connection: Upgrade\r\n");
p.Execute("Upgrade: websocket\r\n");
ASSERT_EQ(callbacks, 0);
p.Execute("\r\n");
ASSERT_EQ(callbacks, 1);
ASSERT_FALSE(p.HasError());
}
TEST(HttpParserTest, Reset) {
HttpParser p{HttpParser::kRequest};
int callbacks = 0;
p.headersComplete.connect([&](bool) { ++callbacks; });
p.Execute("GET / HTTP/1.1\r\n");
ASSERT_EQ(callbacks, 0);
p.Execute("\r\n");
ASSERT_EQ(callbacks, 1);
p.Reset(HttpParser::kRequest);
p.Execute("GET / HTTP/1.1\r\n");
ASSERT_EQ(callbacks, 1);
p.Execute("\r\n");
ASSERT_EQ(callbacks, 2);
ASSERT_FALSE(p.HasError());
}
} // namespace wpi

View File

@@ -1,102 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/HttpUtil.h" // NOLINT(build/include_order)
#include "gtest/gtest.h"
namespace wpi {
TEST(HttpMultipartScannerTest, ExecuteExact) {
HttpMultipartScanner scanner("foo");
EXPECT_TRUE(scanner.Execute("abcdefg---\r\n--foo\r\n").empty());
EXPECT_TRUE(scanner.IsDone());
EXPECT_TRUE(scanner.GetSkipped().empty());
}
TEST(HttpMultipartScannerTest, ExecutePartial) {
HttpMultipartScanner scanner("foo");
EXPECT_TRUE(scanner.Execute("abcdefg--").empty());
EXPECT_FALSE(scanner.IsDone());
EXPECT_TRUE(scanner.Execute("-\r\n").empty());
EXPECT_FALSE(scanner.IsDone());
EXPECT_TRUE(scanner.Execute("--foo\r").empty());
EXPECT_FALSE(scanner.IsDone());
EXPECT_TRUE(scanner.Execute("\n").empty());
EXPECT_TRUE(scanner.IsDone());
}
TEST(HttpMultipartScannerTest, ExecuteTrailing) {
HttpMultipartScanner scanner("foo");
EXPECT_EQ(scanner.Execute("abcdefg---\r\n--foo\r\nxyz"), "xyz");
}
TEST(HttpMultipartScannerTest, ExecutePadding) {
HttpMultipartScanner scanner("foo");
EXPECT_EQ(scanner.Execute("abcdefg---\r\n--foo \r\nxyz"), "xyz");
EXPECT_TRUE(scanner.IsDone());
}
TEST(HttpMultipartScannerTest, SaveSkipped) {
HttpMultipartScanner scanner("foo", true);
scanner.Execute("abcdefg---\r\n--foo\r\n");
EXPECT_EQ(scanner.GetSkipped(), "abcdefg---\r\n--foo\r\n");
}
TEST(HttpMultipartScannerTest, Reset) {
HttpMultipartScanner scanner("foo", true);
scanner.Execute("abcdefg---\r\n--foo\r\n");
EXPECT_TRUE(scanner.IsDone());
EXPECT_EQ(scanner.GetSkipped(), "abcdefg---\r\n--foo\r\n");
scanner.Reset(true);
EXPECT_FALSE(scanner.IsDone());
scanner.SetBoundary("bar");
scanner.Execute("--foo\r\n--bar\r\n");
EXPECT_TRUE(scanner.IsDone());
EXPECT_EQ(scanner.GetSkipped(), "--foo\r\n--bar\r\n");
}
TEST(HttpMultipartScannerTest, WithoutDashes) {
HttpMultipartScanner scanner("foo", true);
EXPECT_TRUE(scanner.Execute("--\r\nfoo\r\n").empty());
EXPECT_TRUE(scanner.IsDone());
}
TEST(HttpMultipartScannerTest, SeqDashesDashes) {
HttpMultipartScanner scanner("foo", true);
EXPECT_TRUE(scanner.Execute("\r\n--foo\r\n").empty());
EXPECT_TRUE(scanner.IsDone());
EXPECT_TRUE(scanner.Execute("\r\n--foo\r\n").empty());
EXPECT_TRUE(scanner.IsDone());
}
TEST(HttpMultipartScannerTest, SeqDashesNoDashes) {
HttpMultipartScanner scanner("foo", true);
EXPECT_TRUE(scanner.Execute("\r\n--foo\r\n").empty());
EXPECT_TRUE(scanner.IsDone());
EXPECT_TRUE(scanner.Execute("\r\nfoo\r\n").empty());
EXPECT_FALSE(scanner.IsDone());
}
TEST(HttpMultipartScannerTest, SeqNoDashesDashes) {
HttpMultipartScanner scanner("foo", true);
EXPECT_TRUE(scanner.Execute("\r\nfoo\r\n").empty());
EXPECT_TRUE(scanner.IsDone());
EXPECT_TRUE(scanner.Execute("\r\n--foo\r\n").empty());
EXPECT_FALSE(scanner.IsDone());
}
TEST(HttpMultipartScannerTest, SeqNoDashesNoDashes) {
HttpMultipartScanner scanner("foo", true);
EXPECT_TRUE(scanner.Execute("\r\nfoo\r\n").empty());
EXPECT_TRUE(scanner.IsDone());
EXPECT_TRUE(scanner.Execute("\r\nfoo\r\n").empty());
EXPECT_TRUE(scanner.IsDone());
}
} // namespace wpi

View File

@@ -1,25 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/HttpWebSocketServerConnection.h" // NOLINT(build/include_order)
#include <gtest/gtest.h>
namespace wpi {
class HttpWebSocketServerConnectionTest
: public HttpWebSocketServerConnection<HttpWebSocketServerConnectionTest> {
public:
HttpWebSocketServerConnectionTest(std::shared_ptr<uv::Stream> stream,
span<const std::string_view> protocols)
: HttpWebSocketServerConnection{stream, protocols} {}
void ProcessRequest() override { ++gotRequest; }
void ProcessWsUpgrade() override { ++gotUpgrade; }
int gotRequest = 0;
int gotUpgrade = 0;
};
} // namespace wpi

View File

@@ -1,319 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this 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/StringExtras.h"
#include "wpi/raw_uv_ostream.h"
#include "wpi/sha1.h"
namespace wpi {
class WebSocketClientTest : public WebSocketTest {
public:
WebSocketClientTest() {
// Bare bones server
req.header.connect([this](std::string_view name, std::string_view value) {
// save key (required for valid response)
if (equals_lower(name, "sec-websocket-key")) {
clientKey = value;
}
});
req.headersComplete.connect([this](bool) {
// send response
SmallVector<uv::Buffer, 4> bufs;
raw_uv_ostream os{bufs, 4096};
os << "HTTP/1.1 101 Switching Protocols\r\n";
os << "Upgrade: websocket\r\n";
os << "Connection: Upgrade\r\n";
// accept hash
SHA1 hash;
hash.Update(clientKey);
hash.Update("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
if (mockBadAccept) {
hash.Update("1");
}
SmallString<64> hashBuf;
SmallString<64> acceptBuf;
os << "Sec-WebSocket-Accept: "
<< Base64Encode(hash.RawFinal(hashBuf), acceptBuf) << "\r\n";
if (!mockProtocol.empty()) {
os << "Sec-WebSocket-Protocol: " << mockProtocol << "\r\n";
}
os << "\r\n";
conn->Write(bufs, [](auto bufs, uv::Error) {
for (auto& buf : bufs) {
buf.Deallocate();
}
});
serverHeadersDone = true;
if (connected) {
connected();
}
});
serverPipe->Listen([this] {
conn = serverPipe->Accept();
conn->StartRead();
conn->data.connect([this](uv::Buffer& buf, size_t size) {
std::string_view data{buf.base, size};
if (!serverHeadersDone) {
data = req.Execute(data);
if (req.HasError()) {
Finish();
}
ASSERT_EQ(req.GetError(), HPE_OK) << http_errno_name(req.GetError());
if (data.empty()) {
return;
}
}
wireData.insert(wireData.end(), data.begin(), data.end());
});
conn->end.connect([this] { Finish(); });
});
}
bool mockBadAccept = false;
std::vector<uint8_t> wireData;
std::shared_ptr<uv::Pipe> conn;
HttpParser req{HttpParser::kRequest};
SmallString<64> clientKey;
std::string mockProtocol;
bool serverHeadersDone = false;
std::function<void()> connected;
};
TEST_F(WebSocketClientTest, Open) {
int gotOpen = 0;
clientPipe->Connect(pipeName, [&] {
auto ws = WebSocket::CreateClient(*clientPipe, "/test", pipeName);
ws->closed.connect([&](uint16_t code, std::string_view reason) {
Finish();
if (code != 1005 && code != 1006) {
FAIL() << "Code: " << code << " Reason: " << reason;
}
});
ws->open.connect([&](std::string_view protocol) {
++gotOpen;
Finish();
ASSERT_TRUE(protocol.empty());
});
});
loop->Run();
if (HasFatalFailure()) {
return;
}
ASSERT_EQ(gotOpen, 1);
}
TEST_F(WebSocketClientTest, BadAccept) {
int gotClosed = 0;
mockBadAccept = true;
clientPipe->Connect(pipeName, [&] {
auto ws = WebSocket::CreateClient(*clientPipe, "/test", pipeName);
ws->closed.connect([&](uint16_t code, std::string_view msg) {
Finish();
++gotClosed;
ASSERT_EQ(code, 1002) << "Message: " << msg;
});
ws->open.connect([&](std::string_view protocol) {
Finish();
FAIL() << "Got open";
});
});
loop->Run();
if (HasFatalFailure()) {
return;
}
ASSERT_EQ(gotClosed, 1);
}
TEST_F(WebSocketClientTest, ProtocolGood) {
int gotOpen = 0;
mockProtocol = "myProtocol";
clientPipe->Connect(pipeName, [&] {
auto ws = WebSocket::CreateClient(*clientPipe, "/test", pipeName,
{"myProtocol", "myProtocol2"});
ws->closed.connect([&](uint16_t code, std::string_view msg) {
Finish();
if (code != 1005 && code != 1006) {
FAIL() << "Code: " << code << "Message: " << msg;
}
});
ws->open.connect([&](std::string_view protocol) {
++gotOpen;
Finish();
ASSERT_EQ(protocol, "myProtocol");
});
});
loop->Run();
if (HasFatalFailure()) {
return;
}
ASSERT_EQ(gotOpen, 1);
}
TEST_F(WebSocketClientTest, ProtocolRespNotReq) {
int gotClosed = 0;
mockProtocol = "myProtocol";
clientPipe->Connect(pipeName, [&] {
auto ws = WebSocket::CreateClient(*clientPipe, "/test", pipeName);
ws->closed.connect([&](uint16_t code, std::string_view msg) {
Finish();
++gotClosed;
ASSERT_EQ(code, 1003) << "Message: " << msg;
});
ws->open.connect([&](std::string_view protocol) {
Finish();
FAIL() << "Got open";
});
});
loop->Run();
if (HasFatalFailure()) {
return;
}
ASSERT_EQ(gotClosed, 1);
}
TEST_F(WebSocketClientTest, ProtocolReqNotResp) {
int gotClosed = 0;
clientPipe->Connect(pipeName, [&] {
auto ws = WebSocket::CreateClient(*clientPipe, "/test", pipeName,
{{"myProtocol"}});
ws->closed.connect([&](uint16_t code, std::string_view msg) {
Finish();
++gotClosed;
ASSERT_EQ(code, 1002) << "Message: " << msg;
});
ws->open.connect([&](std::string_view protocol) {
Finish();
FAIL() << "Got open";
});
});
loop->Run();
if (HasFatalFailure()) {
return;
}
ASSERT_EQ(gotClosed, 1);
}
//
// Send and receive data. Most of these cases are tested in
// WebSocketServerTest, so only spot check differences like masking.
//
class WebSocketClientDataTest : public WebSocketClientTest,
public ::testing::WithParamInterface<size_t> {
public:
WebSocketClientDataTest() {
clientPipe->Connect(pipeName, [&] {
ws = WebSocket::CreateClient(*clientPipe, "/test", pipeName);
if (setupWebSocket) {
setupWebSocket();
}
});
}
std::function<void()> setupWebSocket;
std::shared_ptr<WebSocket> ws;
};
INSTANTIATE_TEST_SUITE_P(WebSocketClientDataTests, WebSocketClientDataTest,
::testing::Values(0, 1, 125, 126, 65535, 65536));
TEST_P(WebSocketClientDataTest, SendBinary) {
int gotCallback = 0;
std::vector<uint8_t> data(GetParam(), 0x03u);
setupWebSocket = [&] {
ws->open.connect([&](std::string_view) {
ws->SendBinary({{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, true, data);
AdjustMasking(wireData);
ASSERT_EQ(wireData, expectData);
ASSERT_EQ(gotCallback, 1);
}
TEST_P(WebSocketClientDataTest, ReceiveBinary) {
int gotCallback = 0;
std::vector<uint8_t> data(GetParam(), 0x03u);
setupWebSocket = [&] {
ws->binary.connect([&](auto 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, false, data);
connected = [&] { conn->Write({{message}}, [&](auto bufs, uv::Error) {}); };
loop->Run();
ASSERT_EQ(gotCallback, 1);
}
//
// The client must close the connection if a masked frame is received.
//
TEST_P(WebSocketClientDataTest, ReceiveMasked) {
int gotCallback = 0;
std::vector<uint8_t> data(GetParam(), ' ');
setupWebSocket = [&] {
ws->text.connect([&](std::string_view, bool) {
ws->Terminate();
FAIL() << "Should not have gotten masked message";
});
ws->closed.connect([&](uint16_t code, std::string_view reason) {
++gotCallback;
ASSERT_EQ(code, 1002) << "reason: " << reason;
});
};
auto message = BuildMessage(0x01, true, true, data);
connected = [&] { conn->Write({{message}}, [&](auto bufs, uv::Error) {}); };
loop->Run();
ASSERT_EQ(gotCallback, 1);
}
} // namespace wpi

View File

@@ -1,149 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/WebSocketServer.h" // NOLINT(build/include_order)
#include "WebSocketTest.h"
#include "wpi/HttpParser.h"
#include "wpi/SmallString.h"
namespace wpi {
class WebSocketIntegrationTest : public WebSocketTest {};
TEST_F(WebSocketIntegrationTest, Open) {
int gotServerOpen = 0;
int gotClientOpen = 0;
serverPipe->Listen([&]() {
auto conn = serverPipe->Accept();
auto server = WebSocketServer::Create(*conn);
server->connected.connect([&](std::string_view url, WebSocket&) {
++gotServerOpen;
ASSERT_EQ(url, "/test");
});
});
clientPipe->Connect(pipeName, [&] {
auto ws = WebSocket::CreateClient(*clientPipe, "/test", pipeName);
ws->closed.connect([&](uint16_t code, std::string_view reason) {
Finish();
if (code != 1005 && code != 1006) {
FAIL() << "Code: " << code << " Reason: " << reason;
}
});
ws->open.connect([&, s = ws.get()](std::string_view) {
++gotClientOpen;
s->Close();
});
});
loop->Run();
ASSERT_EQ(gotServerOpen, 1);
ASSERT_EQ(gotClientOpen, 1);
}
TEST_F(WebSocketIntegrationTest, Protocol) {
int gotServerOpen = 0;
int gotClientOpen = 0;
serverPipe->Listen([&]() {
auto conn = serverPipe->Accept();
auto server = WebSocketServer::Create(*conn, {"proto1", "proto2"});
server->connected.connect([&](std::string_view, WebSocket& ws) {
++gotServerOpen;
ASSERT_EQ(ws.GetProtocol(), "proto1");
});
});
clientPipe->Connect(pipeName, [&] {
auto ws =
WebSocket::CreateClient(*clientPipe, "/test", pipeName, {"proto1"});
ws->closed.connect([&](uint16_t code, std::string_view reason) {
Finish();
if (code != 1005 && code != 1006) {
FAIL() << "Code: " << code << " Reason: " << reason;
}
});
ws->open.connect([&, s = ws.get()](std::string_view protocol) {
++gotClientOpen;
s->Close();
ASSERT_EQ(protocol, "proto1");
});
});
loop->Run();
ASSERT_EQ(gotServerOpen, 1);
ASSERT_EQ(gotClientOpen, 1);
}
TEST_F(WebSocketIntegrationTest, ServerSendBinary) {
int gotData = 0;
serverPipe->Listen([&]() {
auto conn = serverPipe->Accept();
auto server = WebSocketServer::Create(*conn);
server->connected.connect([&](std::string_view, WebSocket& ws) {
ws.SendBinary({uv::Buffer{"\x03\x04", 2}}, [&](auto, uv::Error) {});
ws.Close();
});
});
clientPipe->Connect(pipeName, [&] {
auto ws = WebSocket::CreateClient(*clientPipe, "/test", pipeName);
ws->closed.connect([&](uint16_t code, std::string_view reason) {
Finish();
if (code != 1005 && code != 1006) {
FAIL() << "Code: " << code << " Reason: " << reason;
}
});
ws->binary.connect([&](auto data, bool) {
++gotData;
std::vector<uint8_t> recvData{data.begin(), data.end()};
std::vector<uint8_t> expectData{0x03, 0x04};
ASSERT_EQ(recvData, expectData);
});
});
loop->Run();
ASSERT_EQ(gotData, 1);
}
TEST_F(WebSocketIntegrationTest, ClientSendText) {
int gotData = 0;
serverPipe->Listen([&]() {
auto conn = serverPipe->Accept();
auto server = WebSocketServer::Create(*conn);
server->connected.connect([&](std::string_view, WebSocket& ws) {
ws.text.connect([&](std::string_view data, bool) {
++gotData;
ASSERT_EQ(data, "hello");
});
});
});
clientPipe->Connect(pipeName, [&] {
auto ws = WebSocket::CreateClient(*clientPipe, "/test", pipeName);
ws->closed.connect([&](uint16_t code, std::string_view reason) {
Finish();
if (code != 1005 && code != 1006) {
FAIL() << "Code: " << code << " Reason: " << reason;
}
});
ws->open.connect([&, s = ws.get()](std::string_view) {
s->SendText({{"hello"}}, [&](auto, uv::Error) {});
s->Close();
});
});
loop->Run();
ASSERT_EQ(gotData, 1);
}
} // namespace wpi

View File

@@ -1,736 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this 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) {
std::string_view 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.begin(), data.end());
if (handleData) {
handleData(data);
}
});
clientPipe->end.connect([this]() { Finish(); });
});
}
std::function<void()> setupWebSocket;
std::function<void(std::string_view)> 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([&](std::string_view) { ws->Terminate(); });
ws->closed.connect([&](uint16_t code, std::string_view 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([&](std::string_view) { ws->Terminate(1000); });
ws->closed.connect([&](uint16_t code, std::string_view 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([&](std::string_view) { ws->Terminate(1000, "reason"); });
ws->closed.connect([&](uint16_t code, std::string_view 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([&](std::string_view) { ws->Close(); });
ws->closed.connect([&](uint16_t code, std::string_view 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 = [&](std::string_view) {
clientPipe->Write({{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([&](std::string_view) { ws->Close(1000); });
ws->closed.connect([&](uint16_t code, std::string_view reason) {
++gotClosed;
ASSERT_EQ(code, 1000) << "reason: " << reason;
});
};
// need to respond with close for server to finish shutdown
const uint8_t contents[] = {0x03u, 0xe8u};
auto message = BuildMessage(0x08, true, true, contents);
handleData = [&](std::string_view) {
clientPipe->Write({{message}}, [&](auto bufs, uv::Error) {});
};
loop->Run();
auto expectData = BuildMessage(0x08, true, false, contents);
ASSERT_EQ(wireData, expectData);
ASSERT_EQ(gotClosed, 1);
}
TEST_F(WebSocketServerTest, CloseReason) {
int gotClosed = 0;
setupWebSocket = [&] {
ws->open.connect([&](std::string_view) { ws->Close(1000, "hangup"); });
ws->closed.connect([&](uint16_t code, std::string_view reason) {
++gotClosed;
ASSERT_EQ(code, 1000);
ASSERT_EQ(reason, "hangup");
});
};
// need to respond with close for server to finish shutdown
const uint8_t contents[] = {0x03u, 0xe8u, 'h', 'a', 'n', 'g', 'u', 'p'};
auto message = BuildMessage(0x08, true, true, contents);
handleData = [&](std::string_view) {
clientPipe->Write({{message}}, [&](auto bufs, uv::Error) {});
};
loop->Run();
auto expectData = BuildMessage(0x08, true, false, contents);
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, std::string_view reason) {
++gotClosed;
ASSERT_EQ(code, 1005) << "reason: " << reason;
});
};
auto message = BuildMessage(0x08, true, true, {});
resp.headersComplete.connect([&](bool) {
clientPipe->Write({{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, std::string_view reason) {
++gotClosed;
ASSERT_EQ(code, 1000) << "reason: " << reason;
});
};
const uint8_t contents[] = {0x03u, 0xe8u};
auto message = BuildMessage(0x08, true, true, contents);
resp.headersComplete.connect([&](bool) {
clientPipe->Write({{message}}, [&](auto bufs, uv::Error) {});
});
loop->Run();
// the endpoint should echo the message
auto expectData = BuildMessage(0x08, true, false, contents);
ASSERT_EQ(wireData, expectData);
ASSERT_EQ(gotClosed, 1);
}
TEST_F(WebSocketServerTest, ReceiveCloseReason) {
int gotClosed = 0;
setupWebSocket = [&] {
ws->closed.connect([&](uint16_t code, std::string_view reason) {
++gotClosed;
ASSERT_EQ(code, 1000);
ASSERT_EQ(reason, "hangup");
});
};
const uint8_t contents[] = {0x03u, 0xe8u, 'h', 'a', 'n', 'g', 'u', 'p'};
auto message = BuildMessage(0x08, true, true, contents);
resp.headersComplete.connect([&](bool) {
clientPipe->Write({{message}}, [&](auto bufs, uv::Error) {});
});
loop->Run();
// the endpoint should echo the message
auto expectData = BuildMessage(0x08, true, false, contents);
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> {};
INSTANTIATE_TEST_SUITE_P(WebSocketServerBadOpcodeTests,
WebSocketServerBadOpcodeTest,
::testing::Values(3, 4, 5, 6, 7, 0xb, 0xc, 0xd, 0xe,
0xf));
TEST_P(WebSocketServerBadOpcodeTest, Receive) {
int gotCallback = 0;
std::vector<uint8_t> data(4, 0x03);
setupWebSocket = [&] {
ws->closed.connect([&](uint16_t code, std::string_view reason) {
++gotCallback;
ASSERT_EQ(code, 1002) << "reason: " << reason;
});
};
auto message = BuildMessage(GetParam(), true, true, data);
resp.headersComplete.connect([&](bool) {
clientPipe->Write({{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> {};
INSTANTIATE_TEST_SUITE_P(WebSocketServerControlFrameTests,
WebSocketServerControlFrameTest,
::testing::Values(0x8, 0x9, 0xa));
TEST_P(WebSocketServerControlFrameTest, ReceiveFragment) {
int gotCallback = 0;
std::vector<uint8_t> data(4, 0x03);
setupWebSocket = [&] {
ws->closed.connect([&](uint16_t code, std::string_view reason) {
++gotCallback;
ASSERT_EQ(code, 1002) << "reason: " << reason;
});
};
auto message = BuildMessage(GetParam(), false, true, data);
resp.headersComplete.connect([&](bool) {
clientPipe->Write({{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, std::string_view reason) {
++gotCallback;
ASSERT_EQ(code, 1002) << "reason: " << reason;
});
};
auto message = BuildMessage(0x00, false, true, data);
resp.headersComplete.connect([&](bool) {
clientPipe->Write({{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, std::string_view 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({{message}, {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, std::string_view 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({{message}, {message2}, {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([&](auto 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({{message}, {message2}, {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([&](auto 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({{message}, {message2}, {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, std::string_view reason) {
++gotCallback;
ASSERT_EQ(code, 1009) << "reason: " << reason;
});
};
auto message = BuildMessage(0x01, true, true, data);
resp.headersComplete.connect([&](bool) {
clientPipe->Write({{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, std::string_view 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({{message}, {message2}}, [&](auto bufs, uv::Error) {});
});
loop->Run();
ASSERT_EQ(gotCallback, 1);
}
//
// Send and receive data.
//
class WebSocketServerDataTest : public WebSocketServerTest,
public ::testing::WithParamInterface<size_t> {};
INSTANTIATE_TEST_SUITE_P(WebSocketServerDataTests, WebSocketServerDataTest,
::testing::Values(0, 1, 125, 126, 65535, 65536));
TEST_P(WebSocketServerDataTest, SendText) {
int gotCallback = 0;
std::vector<uint8_t> data(GetParam(), ' ');
setupWebSocket = [&] {
ws->open.connect([&](std::string_view) {
ws->SendText({{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([&](std::string_view) {
ws->SendBinary({{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([&](std::string_view) {
ws->SendPing({{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([&](std::string_view) {
ws->SendPong({{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([&](std::string_view inData, bool fin) {
++gotCallback;
ws->Terminate();
ASSERT_TRUE(fin);
std::vector<uint8_t> recvData;
recvData.insert(recvData.end(), inData.begin(), inData.end());
ASSERT_EQ(data, recvData);
});
};
auto message = BuildMessage(0x01, true, true, data);
resp.headersComplete.connect([&](bool) {
clientPipe->Write({{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([&](auto 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({{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([&](auto 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({{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([&](auto 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({{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([&](std::string_view, bool) {
ws->Terminate();
FAIL() << "Should not have gotten unmasked message";
});
ws->closed.connect([&](uint16_t code, std::string_view reason) {
++gotCallback;
ASSERT_EQ(code, 1002) << "reason: " << reason;
});
};
auto message = BuildMessage(0x01, true, false, data);
resp.headersComplete.connect([&](bool) {
clientPipe->Write({{message}}, [&](auto bufs, uv::Error) {});
});
loop->Run();
ASSERT_EQ(gotCallback, 1);
}
} // namespace wpi

View File

@@ -1,379 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/WebSocket.h" // NOLINT(build/include_order)
#include "WebSocketTest.h"
#include "wpi/HttpParser.h"
#include "wpi/StringExtras.h"
namespace wpi {
#ifdef _WIN32
const char* WebSocketTest::pipeName = "\\\\.\\pipe\\websocket-unit-test";
#else
const char* WebSocketTest::pipeName = "/tmp/websocket-unit-test";
#endif
const uint8_t WebSocketTest::testMask[4] = {0x11, 0x22, 0x33, 0x44};
void WebSocketTest::SetUpTestCase() {
#ifndef _WIN32
unlink(pipeName);
#endif
}
std::vector<uint8_t> WebSocketTest::BuildHeader(uint8_t opcode, bool fin,
bool masking, uint64_t len) {
std::vector<uint8_t> data;
data.push_back(opcode | (fin ? 0x80u : 0x00u));
if (len < 126) {
data.push_back(len | (masking ? 0x80 : 0x00u));
} else if (len < 65536) {
data.push_back(126u | (masking ? 0x80 : 0x00u));
data.push_back(len >> 8);
data.push_back(len & 0xff);
} else {
data.push_back(127u | (masking ? 0x80u : 0x00u));
for (int i = 56; i >= 0; i -= 8) {
data.push_back((len >> i) & 0xff);
}
}
if (masking) {
data.insert(data.end(), &testMask[0], &testMask[4]);
}
return data;
}
std::vector<uint8_t> WebSocketTest::BuildMessage(uint8_t opcode, bool fin,
bool masking,
span<const uint8_t> data) {
auto finalData = BuildHeader(opcode, fin, masking, data.size());
size_t headerSize = finalData.size();
finalData.insert(finalData.end(), data.begin(), data.end());
if (masking) {
uint8_t mask[4] = {finalData[headerSize - 4], finalData[headerSize - 3],
finalData[headerSize - 2], finalData[headerSize - 1]};
int n = 0;
for (size_t i = headerSize, end = finalData.size(); i < end; ++i) {
finalData[i] ^= mask[n++];
if (n >= 4) {
n = 0;
}
}
}
return finalData;
}
// If the message is masked, changes the mask to match the mask set by
// BuildHeader() by unmasking and remasking.
void WebSocketTest::AdjustMasking(span<uint8_t> message) {
if (message.size() < 2) {
return;
}
if ((message[1] & 0x80) == 0) {
return; // not masked
}
size_t maskPos;
uint8_t len = message[1] & 0x7f;
if (len == 126) {
maskPos = 4;
} else if (len == 127) {
maskPos = 10;
} else {
maskPos = 2;
}
uint8_t mask[4] = {message[maskPos], message[maskPos + 1],
message[maskPos + 2], message[maskPos + 3]};
message[maskPos] = testMask[0];
message[maskPos + 1] = testMask[1];
message[maskPos + 2] = testMask[2];
message[maskPos + 3] = testMask[3];
int n = 0;
for (auto& ch : message.subspan(maskPos + 4)) {
ch ^= mask[n] ^ testMask[n];
if (++n >= 4) {
n = 0;
}
}
}
TEST_F(WebSocketTest, CreateClientBasic) {
int gotHost = 0;
int gotUpgrade = 0;
int gotConnection = 0;
int gotKey = 0;
int gotVersion = 0;
HttpParser req{HttpParser::kRequest};
req.url.connect([](std::string_view url) { ASSERT_EQ(url, "/test"); });
req.header.connect([&](std::string_view name, std::string_view value) {
if (equals_lower(name, "host")) {
ASSERT_EQ(value, pipeName);
++gotHost;
} else if (equals_lower(name, "upgrade")) {
ASSERT_EQ(value, "websocket");
++gotUpgrade;
} else if (equals_lower(name, "connection")) {
ASSERT_EQ(value, "Upgrade");
++gotConnection;
} else if (equals_lower(name, "sec-websocket-key")) {
++gotKey;
} else if (equals_lower(name, "sec-websocket-version")) {
ASSERT_EQ(value, "13");
++gotVersion;
} else {
FAIL() << "unexpected header " << name;
}
});
req.headersComplete.connect([&](bool) { Finish(); });
serverPipe->Listen([&]() {
auto conn = serverPipe->Accept();
conn->StartRead();
conn->data.connect([&](uv::Buffer& buf, size_t size) {
req.Execute(std::string_view{buf.base, size});
if (req.HasError()) {
Finish();
}
ASSERT_EQ(req.GetError(), HPE_OK) << http_errno_name(req.GetError());
});
});
clientPipe->Connect(pipeName, [&]() {
auto ws = WebSocket::CreateClient(*clientPipe, "/test", pipeName);
});
loop->Run();
if (HasFatalFailure()) {
return;
}
ASSERT_EQ(gotHost, 1);
ASSERT_EQ(gotUpgrade, 1);
ASSERT_EQ(gotConnection, 1);
ASSERT_EQ(gotKey, 1);
ASSERT_EQ(gotVersion, 1);
}
TEST_F(WebSocketTest, CreateClientExtraHeaders) {
int gotExtra1 = 0;
int gotExtra2 = 0;
HttpParser req{HttpParser::kRequest};
req.header.connect([&](std::string_view name, std::string_view value) {
if (equals(name, "Extra1")) {
ASSERT_EQ(value, "Data1");
++gotExtra1;
} else if (equals(name, "Extra2")) {
ASSERT_EQ(value, "Data2");
++gotExtra2;
}
});
req.headersComplete.connect([&](bool) { Finish(); });
serverPipe->Listen([&]() {
auto conn = serverPipe->Accept();
conn->StartRead();
conn->data.connect([&](uv::Buffer& buf, size_t size) {
req.Execute(std::string_view{buf.base, size});
if (req.HasError()) {
Finish();
}
ASSERT_EQ(req.GetError(), HPE_OK) << http_errno_name(req.GetError());
});
});
clientPipe->Connect(pipeName, [&]() {
WebSocket::ClientOptions options;
SmallVector<std::pair<std::string_view, std::string_view>, 4> extraHeaders;
extraHeaders.emplace_back("Extra1", "Data1");
extraHeaders.emplace_back("Extra2", "Data2");
options.extraHeaders = extraHeaders;
auto ws =
WebSocket::CreateClient(*clientPipe, "/test", pipeName, {}, options);
});
loop->Run();
if (HasFatalFailure()) {
return;
}
ASSERT_EQ(gotExtra1, 1);
ASSERT_EQ(gotExtra2, 1);
}
TEST_F(WebSocketTest, CreateClientTimeout) {
int gotClosed = 0;
serverPipe->Listen([&]() { auto conn = serverPipe->Accept(); });
clientPipe->Connect(pipeName, [&]() {
WebSocket::ClientOptions options;
options.handshakeTimeout = uv::Timer::Time{100};
auto ws =
WebSocket::CreateClient(*clientPipe, "/test", pipeName, {}, options);
ws->closed.connect([&](uint16_t code, std::string_view) {
Finish();
++gotClosed;
ASSERT_EQ(code, 1006);
});
});
loop->Run();
if (HasFatalFailure()) {
return;
}
ASSERT_EQ(gotClosed, 1);
}
TEST_F(WebSocketTest, CreateServerBasic) {
int gotStatus = 0;
int gotUpgrade = 0;
int gotConnection = 0;
int gotAccept = 0;
int gotOpen = 0;
HttpParser resp{HttpParser::kResponse};
resp.status.connect([&](std::string_view status) {
++gotStatus;
ASSERT_EQ(resp.GetStatusCode(), 101u) << "status: " << status;
});
resp.header.connect([&](std::string_view name, std::string_view value) {
if (equals_lower(name, "upgrade")) {
ASSERT_EQ(value, "websocket");
++gotUpgrade;
} else if (equals_lower(name, "connection")) {
ASSERT_EQ(value, "Upgrade");
++gotConnection;
} else if (equals_lower(name, "sec-websocket-accept")) {
ASSERT_EQ(value, "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=");
++gotAccept;
} else {
FAIL() << "unexpected header " << name;
}
});
resp.headersComplete.connect([&](bool) { Finish(); });
serverPipe->Listen([&]() {
auto conn = serverPipe->Accept();
auto ws = WebSocket::CreateServer(*conn, "dGhlIHNhbXBsZSBub25jZQ==", "13");
ws->open.connect([&](std::string_view protocol) {
++gotOpen;
ASSERT_TRUE(protocol.empty());
});
});
clientPipe->Connect(pipeName, [&] {
clientPipe->StartRead();
clientPipe->data.connect([&](uv::Buffer& buf, size_t size) {
resp.Execute(std::string_view{buf.base, size});
if (resp.HasError()) {
Finish();
}
ASSERT_EQ(resp.GetError(), HPE_OK) << http_errno_name(resp.GetError());
});
});
loop->Run();
if (HasFatalFailure()) {
return;
}
ASSERT_EQ(gotStatus, 1);
ASSERT_EQ(gotUpgrade, 1);
ASSERT_EQ(gotConnection, 1);
ASSERT_EQ(gotAccept, 1);
ASSERT_EQ(gotOpen, 1);
}
TEST_F(WebSocketTest, CreateServerProtocol) {
int gotProtocol = 0;
int gotOpen = 0;
HttpParser resp{HttpParser::kResponse};
resp.header.connect([&](std::string_view name, std::string_view value) {
if (equals_lower(name, "sec-websocket-protocol")) {
++gotProtocol;
ASSERT_EQ(value, "myProtocol");
}
});
resp.headersComplete.connect([&](bool) { Finish(); });
serverPipe->Listen([&]() {
auto conn = serverPipe->Accept();
auto ws = WebSocket::CreateServer(*conn, "foo", "13", "myProtocol");
ws->open.connect([&](std::string_view protocol) {
++gotOpen;
ASSERT_EQ(protocol, "myProtocol");
});
});
clientPipe->Connect(pipeName, [&] {
clientPipe->StartRead();
clientPipe->data.connect([&](uv::Buffer& buf, size_t size) {
resp.Execute(std::string_view{buf.base, size});
if (resp.HasError()) {
Finish();
}
ASSERT_EQ(resp.GetError(), HPE_OK) << http_errno_name(resp.GetError());
});
});
loop->Run();
if (HasFatalFailure()) {
return;
}
ASSERT_EQ(gotProtocol, 1);
ASSERT_EQ(gotOpen, 1);
}
TEST_F(WebSocketTest, CreateServerBadVersion) {
int gotStatus = 0;
int gotVersion = 0;
int gotUpgrade = 0;
HttpParser resp{HttpParser::kResponse};
resp.status.connect([&](std::string_view status) {
++gotStatus;
ASSERT_EQ(resp.GetStatusCode(), 426u) << "status: " << status;
});
resp.header.connect([&](std::string_view name, std::string_view value) {
if (equals_lower(name, "sec-websocket-version")) {
++gotVersion;
ASSERT_EQ(value, "13");
} else if (equals_lower(name, "upgrade")) {
++gotUpgrade;
ASSERT_EQ(value, "WebSocket");
} else {
FAIL() << "unexpected header " << name;
}
});
resp.headersComplete.connect([&](bool) { Finish(); });
serverPipe->Listen([&] {
auto conn = serverPipe->Accept();
auto ws = WebSocket::CreateServer(*conn, "foo", "14");
ws->open.connect([&](std::string_view) {
Finish();
FAIL();
});
});
clientPipe->Connect(pipeName, [&] {
clientPipe->StartRead();
clientPipe->data.connect([&](uv::Buffer& buf, size_t size) {
resp.Execute(std::string_view{buf.base, size});
if (resp.HasError()) {
Finish();
}
ASSERT_EQ(resp.GetError(), HPE_OK) << http_errno_name(resp.GetError());
});
});
loop->Run();
if (HasFatalFailure()) {
return;
}
ASSERT_EQ(gotStatus, 1);
ASSERT_EQ(gotVersion, 1);
ASSERT_EQ(gotUpgrade, 1);
}
} // namespace wpi

View File

@@ -1,70 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#pragma once
#include <cstdio>
#include <memory>
#include <vector>
#include "gtest/gtest.h"
#include "wpi/span.h"
#include "wpi/uv/Loop.h"
#include "wpi/uv/Pipe.h"
#include "wpi/uv/Timer.h"
namespace wpi {
class WebSocketTest : public ::testing::Test {
public:
static const char* pipeName;
static void SetUpTestCase();
WebSocketTest() {
loop = uv::Loop::Create();
clientPipe = uv::Pipe::Create(loop);
serverPipe = uv::Pipe::Create(loop);
serverPipe->Bind(pipeName);
#if 0
auto debugTimer = uv::Timer::Create(loop);
debugTimer->timeout.connect([this] {
std::printf("Active handles:\n");
uv_print_active_handles(loop->GetRaw(), stdout);
});
debugTimer->Start(uv::Timer::Time{100}, uv::Timer::Time{100});
debugTimer->Unreference();
#endif
auto failTimer = uv::Timer::Create(loop);
failTimer->timeout.connect([this] {
loop->Stop();
FAIL() << "loop failed to terminate";
});
failTimer->Start(uv::Timer::Time{1000});
failTimer->Unreference();
}
~WebSocketTest() override { Finish(); }
void Finish() {
loop->Walk([](uv::Handle& it) { it.Close(); });
}
static std::vector<uint8_t> BuildHeader(uint8_t opcode, bool fin,
bool masking, uint64_t len);
static std::vector<uint8_t> BuildMessage(uint8_t opcode, bool fin,
bool masking,
span<const uint8_t> data);
static void AdjustMasking(span<uint8_t> message);
static const uint8_t testMask[4];
std::shared_ptr<uv::Loop> loop;
std::shared_ptr<uv::Pipe> clientPipe;
std::shared_ptr<uv::Pipe> serverPipe;
};
} // namespace wpi

View File

@@ -1,70 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/WorkerThread.h" // NOLINT(build/include_order)
#include "gtest/gtest.h" // NOLINT(build/include_order)
#include <thread>
#include "wpi/uv/Loop.h"
namespace wpi {
TEST(WorkerThreadTest, Future) {
WorkerThread<int(bool)> worker;
future<int> f =
worker.QueueWork([](bool v) -> int { return v ? 1 : 2; }, true);
ASSERT_EQ(f.get(), 1);
}
TEST(WorkerThreadTest, FutureVoid) {
int callbacks = 0;
WorkerThread<void(int)> worker;
future<void> f = worker.QueueWork(
[&](int v) {
++callbacks;
ASSERT_EQ(v, 3);
},
3);
f.get();
ASSERT_EQ(callbacks, 1);
}
TEST(WorkerThreadTest, Loop) {
int callbacks = 0;
WorkerThread<int(bool)> worker;
auto loop = uv::Loop::Create();
worker.SetLoop(*loop);
worker.QueueWorkThen([](bool v) -> int { return v ? 1 : 2; },
[&](int v2) {
++callbacks;
loop->Stop();
ASSERT_EQ(v2, 1);
},
true);
auto f = worker.QueueWork([](bool) -> int { return 2; }, true);
ASSERT_EQ(f.get(), 2);
loop->Run();
ASSERT_EQ(callbacks, 1);
}
TEST(WorkerThreadTest, LoopVoid) {
int callbacks = 0;
WorkerThread<void(bool)> worker;
auto loop = uv::Loop::Create();
worker.SetLoop(*loop);
worker.QueueWorkThen([](bool) {},
[&]() {
++callbacks;
loop->Stop();
},
true);
auto f = worker.QueueWork([](bool) {}, true);
f.get();
loop->Run();
ASSERT_EQ(callbacks, 1);
}
} // namespace wpi

View File

@@ -1,23 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/hostname.h"
#include "gtest/gtest.h"
#include "wpi/SmallString.h"
#include "wpi/SmallVector.h"
namespace wpi {
TEST(HostNameTest, HostNameNotEmpty) {
ASSERT_NE(GetHostname(), "");
}
TEST(HostNameTest, HostNameNotEmptySmallVector) {
SmallVector<char, 256> name;
ASSERT_NE(GetHostname(name), "");
}
TEST(HostNameTest, HostNameEq) {
SmallVector<char, 256> nameBuf;
ASSERT_EQ(GetHostname(nameBuf), GetHostname());
}
} // namespace wpi

View File

@@ -1,71 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/raw_uv_ostream.h" // NOLINT(build/include_order)
#include "gtest/gtest.h"
namespace wpi {
TEST(RawUvStreamTest, BasicWrite) {
SmallVector<uv::Buffer, 4> bufs;
raw_uv_ostream os(bufs, 1024);
os << "12";
os << "34";
ASSERT_EQ(bufs.size(), 1u);
ASSERT_EQ(bufs[0].len, 4u);
ASSERT_EQ(bufs[0].base[0], '1');
ASSERT_EQ(bufs[0].base[1], '2');
ASSERT_EQ(bufs[0].base[2], '3');
ASSERT_EQ(bufs[0].base[3], '4');
for (auto& buf : bufs) {
buf.Deallocate();
}
}
TEST(RawUvStreamTest, BoundaryWrite) {
SmallVector<uv::Buffer, 4> bufs;
raw_uv_ostream os(bufs, 4);
ASSERT_EQ(bufs.size(), 0u);
os << "12";
ASSERT_EQ(bufs.size(), 1u);
os << "34";
ASSERT_EQ(bufs.size(), 1u);
os << "56";
ASSERT_EQ(bufs.size(), 2u);
for (auto& buf : bufs) {
buf.Deallocate();
}
}
TEST(RawUvStreamTest, LargeWrite) {
SmallVector<uv::Buffer, 4> bufs;
raw_uv_ostream os(bufs, 4);
os << "123456";
ASSERT_EQ(bufs.size(), 2u);
ASSERT_EQ(bufs[1].len, 2u);
ASSERT_EQ(bufs[1].base[0], '5');
for (auto& buf : bufs) {
buf.Deallocate();
}
}
TEST(RawUvStreamTest, PrevDataWrite) {
SmallVector<uv::Buffer, 4> bufs;
bufs.emplace_back(uv::Buffer::Allocate(1024));
raw_uv_ostream os(bufs, 1024);
os << "1234";
ASSERT_EQ(bufs.size(), 2u);
ASSERT_EQ(bufs[0].len, 1024u);
ASSERT_EQ(bufs[1].len, 4u);
for (auto& buf : bufs) {
buf.Deallocate();
}
}
} // namespace wpi

View File

@@ -1,264 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/uv/AsyncFunction.h" // NOLINT(build/include_order)
#include "gtest/gtest.h" // NOLINT(build/include_order)
#include <thread>
#include "wpi/uv/Loop.h"
#include "wpi/uv/Prepare.h"
namespace wpi::uv {
TEST(UvAsyncFunctionTest, Basic) {
int prepare_cb_called = 0;
int async_cb_called[2] = {0, 0};
int close_cb_called = 0;
std::thread theThread;
auto loop = Loop::Create();
auto async = AsyncFunction<int(int)>::Create(loop);
auto prepare = Prepare::Create(loop);
loop->error.connect([](Error) { FAIL(); });
prepare->error.connect([](Error) { FAIL(); });
prepare->prepare.connect([&] {
if (prepare_cb_called++) {
return;
}
theThread = std::thread([&] {
auto call0 = async->Call(0);
auto call1 = async->Call(1);
ASSERT_EQ(call0.get(), 1);
ASSERT_EQ(call1.get(), 2);
});
});
prepare->Start();
async->error.connect([](Error) { FAIL(); });
async->closed.connect([&] { close_cb_called++; });
async->wakeup = [&](promise<int> out, int v) {
++async_cb_called[v];
if (v == 1) {
async->Close();
prepare->Close();
}
out.set_value(v + 1);
};
loop->Run();
ASSERT_EQ(async_cb_called[0], 1);
ASSERT_EQ(async_cb_called[1], 1);
ASSERT_EQ(close_cb_called, 1);
if (theThread.joinable()) {
theThread.join();
}
}
TEST(UvAsyncFunctionTest, Ref) {
int prepare_cb_called = 0;
int val = 0;
std::thread theThread;
auto loop = Loop::Create();
auto async = AsyncFunction<int(int, int&)>::Create(loop);
auto prepare = Prepare::Create(loop);
prepare->prepare.connect([&] {
if (prepare_cb_called++) {
return;
}
theThread = std::thread([&] { ASSERT_EQ(async->Call(1, val).get(), 2); });
});
prepare->Start();
async->wakeup = [&](promise<int> out, int v, int& r) {
r = v;
async->Close();
prepare->Close();
out.set_value(v + 1);
};
loop->Run();
ASSERT_EQ(val, 1);
if (theThread.joinable()) {
theThread.join();
}
}
TEST(UvAsyncFunctionTest, Movable) {
int prepare_cb_called = 0;
std::thread theThread;
auto loop = Loop::Create();
auto async =
AsyncFunction<std::unique_ptr<int>(std::unique_ptr<int>)>::Create(loop);
auto prepare = Prepare::Create(loop);
prepare->prepare.connect([&] {
if (prepare_cb_called++) {
return;
}
theThread = std::thread([&] {
auto val = std::make_unique<int>(1);
auto val2 = async->Call(std::move(val)).get();
ASSERT_NE(val2, nullptr);
ASSERT_EQ(*val2, 1);
});
});
prepare->Start();
async->wakeup = [&](promise<std::unique_ptr<int>> out,
std::unique_ptr<int> v) {
async->Close();
prepare->Close();
out.set_value(std::move(v));
};
loop->Run();
if (theThread.joinable()) {
theThread.join();
}
}
TEST(UvAsyncFunctionTest, CallIgnoreResult) {
int prepare_cb_called = 0;
std::thread theThread;
auto loop = Loop::Create();
auto async =
AsyncFunction<std::unique_ptr<int>(std::unique_ptr<int>)>::Create(loop);
auto prepare = Prepare::Create(loop);
prepare->prepare.connect([&] {
if (prepare_cb_called++) {
return;
}
theThread = std::thread([&] { async->Call(std::make_unique<int>(1)); });
});
prepare->Start();
async->wakeup = [&](promise<std::unique_ptr<int>> out,
std::unique_ptr<int> v) {
async->Close();
prepare->Close();
out.set_value(std::move(v));
};
loop->Run();
if (theThread.joinable()) {
theThread.join();
}
}
TEST(UvAsyncFunctionTest, VoidCall) {
int prepare_cb_called = 0;
std::thread theThread;
auto loop = Loop::Create();
auto async = AsyncFunction<void()>::Create(loop);
auto prepare = Prepare::Create(loop);
prepare->prepare.connect([&] {
if (prepare_cb_called++) {
return;
}
theThread = std::thread([&] { async->Call(); });
});
prepare->Start();
async->wakeup = [&](promise<void> out) {
async->Close();
prepare->Close();
out.set_value();
};
loop->Run();
if (theThread.joinable()) {
theThread.join();
}
}
TEST(UvAsyncFunctionTest, WaitFor) {
int prepare_cb_called = 0;
std::thread theThread;
auto loop = Loop::Create();
auto async = AsyncFunction<int()>::Create(loop);
auto prepare = Prepare::Create(loop);
prepare->prepare.connect([&] {
if (prepare_cb_called++) {
return;
}
theThread = std::thread([&] {
ASSERT_FALSE(async->Call().wait_for(std::chrono::milliseconds(10)));
});
});
prepare->Start();
async->wakeup = [&](promise<int> out) {
async->Close();
prepare->Close();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
out.set_value(1);
};
loop->Run();
if (theThread.joinable()) {
theThread.join();
}
}
TEST(UvAsyncFunctionTest, VoidWaitFor) {
int prepare_cb_called = 0;
std::thread theThread;
auto loop = Loop::Create();
auto async = AsyncFunction<void()>::Create(loop);
auto prepare = Prepare::Create(loop);
prepare->prepare.connect([&] {
if (prepare_cb_called++) {
return;
}
theThread = std::thread([&] {
ASSERT_FALSE(async->Call().wait_for(std::chrono::milliseconds(10)));
});
});
prepare->Start();
async->wakeup = [&](promise<void> out) {
async->Close();
prepare->Close();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
out.set_value();
};
loop->Run();
if (theThread.joinable()) {
theThread.join();
}
}
} // namespace wpi::uv

View File

@@ -1,185 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include "wpi/uv/Async.h" // NOLINT(build/include_order)
#include "gtest/gtest.h" // NOLINT(build/include_order)
#include <atomic>
#include <thread>
#include "wpi/mutex.h"
#include "wpi/uv/Loop.h"
#include "wpi/uv/Prepare.h"
namespace wpi::uv {
TEST(UvAsyncTest, CallbackOnly) {
std::atomic_int async_cb_called{0};
int prepare_cb_called = 0;
int close_cb_called = 0;
wpi::mutex mutex;
mutex.lock();
std::thread theThread;
auto loop = Loop::Create();
auto async = Async<>::Create(loop);
auto prepare = Prepare::Create(loop);
loop->error.connect([](Error) { FAIL(); });
prepare->error.connect([](Error) { FAIL(); });
prepare->closed.connect([&] { close_cb_called++; });
prepare->prepare.connect([&] {
if (prepare_cb_called++) {
return;
}
theThread = std::thread([&] {
for (;;) {
mutex.lock();
int n = async_cb_called;
mutex.unlock();
if (n == 3) {
break;
}
async->Send();
std::this_thread::yield();
}
});
mutex.unlock();
});
prepare->Start();
async->error.connect([](Error) { FAIL(); });
async->closed.connect([&] { close_cb_called++; });
async->wakeup.connect([&] {
mutex.lock();
int n = ++async_cb_called;
mutex.unlock();
if (n == 3) {
async->Close();
prepare->Close();
}
});
loop->Run();
ASSERT_GT(prepare_cb_called, 0);
ASSERT_EQ(async_cb_called, 3);
ASSERT_EQ(close_cb_called, 2);
if (theThread.joinable()) {
theThread.join();
}
}
TEST(UvAsyncTest, Data) {
int prepare_cb_called = 0;
int async_cb_called[2] = {0, 0};
int close_cb_called = 0;
std::thread theThread;
auto loop = Loop::Create();
auto async = Async<int, std::function<void(int)>>::Create(loop);
auto prepare = Prepare::Create(loop);
loop->error.connect([](Error) { FAIL(); });
prepare->error.connect([](Error) { FAIL(); });
prepare->prepare.connect([&] {
if (prepare_cb_called++) {
return;
}
theThread = std::thread([&] {
async->Send(0, [&](int v) {
ASSERT_EQ(v, 0);
++async_cb_called[0];
});
async->Send(1, [&](int v) {
ASSERT_EQ(v, 1);
++async_cb_called[1];
async->Close();
prepare->Close();
});
});
});
prepare->Start();
async->error.connect([](Error) { FAIL(); });
async->closed.connect([&] { close_cb_called++; });
async->wakeup.connect([&](int v, std::function<void(int)> f) { f(v); });
loop->Run();
ASSERT_EQ(async_cb_called[0], 1);
ASSERT_EQ(async_cb_called[1], 1);
ASSERT_EQ(close_cb_called, 1);
if (theThread.joinable()) {
theThread.join();
}
}
TEST(UvAsyncTest, DataRef) {
int prepare_cb_called = 0;
int val = 0;
std::thread theThread;
auto loop = Loop::Create();
auto async = Async<int, int&>::Create(loop);
auto prepare = Prepare::Create(loop);
prepare->prepare.connect([&] {
if (prepare_cb_called++) {
return;
}
theThread = std::thread([&] { async->Send(1, val); });
});
prepare->Start();
async->wakeup.connect([&](int v, int& r) {
r = v;
async->Close();
prepare->Close();
});
loop->Run();
ASSERT_EQ(val, 1);
if (theThread.joinable()) {
theThread.join();
}
}
} // namespace wpi::uv

View File

@@ -1,48 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/uv/Buffer.h" // NOLINT(build/include_order)
#include "gtest/gtest.h" // NOLINT(build/include_order)
namespace wpi::uv {
TEST(UvSimpleBufferPoolTest, ConstructDefault) {
SimpleBufferPool<> pool;
auto buf1 = pool.Allocate();
ASSERT_EQ(buf1.len, 4096u); // NOLINT
pool.Release({&buf1, 1});
}
TEST(UvSimpleBufferPoolTest, ConstructSize) {
SimpleBufferPool<4> pool{8192};
auto buf1 = pool.Allocate();
ASSERT_EQ(buf1.len, 8192u); // NOLINT
pool.Release({&buf1, 1});
}
TEST(UvSimpleBufferPoolTest, ReleaseReuse) {
SimpleBufferPool<4> pool;
auto buf1 = pool.Allocate();
auto buf1copy = buf1;
auto origSize = buf1.len;
buf1.len = 8;
pool.Release({&buf1, 1});
ASSERT_EQ(buf1.base, nullptr);
auto buf2 = pool.Allocate();
ASSERT_EQ(buf1copy.base, buf2.base);
ASSERT_EQ(buf2.len, origSize);
pool.Release({&buf2, 1});
}
TEST(UvSimpleBufferPoolTest, ClearRemaining) {
SimpleBufferPool<4> pool;
auto buf1 = pool.Allocate();
pool.Release({&buf1, 1});
ASSERT_EQ(pool.Remaining(), 1u);
pool.Clear();
ASSERT_EQ(pool.Remaining(), 0u);
}
} // namespace wpi::uv

View File

@@ -1,109 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include "wpi/uv/GetAddrInfo.h" // NOLINT(build/include_order)
#include "gtest/gtest.h" // NOLINT(build/include_order)
#include "wpi/uv/Loop.h"
#define CONCURRENT_COUNT 10
namespace wpi::uv {
TEST(UvGetAddrInfoTest, BothNull) {
int fail_cb_called = 0;
auto loop = Loop::Create();
loop->error.connect([&](Error err) {
ASSERT_EQ(err.code(), UV_EINVAL);
fail_cb_called++;
});
GetAddrInfo(
loop, [](const addrinfo&) { FAIL(); }, "");
loop->Run();
ASSERT_EQ(fail_cb_called, 1);
}
TEST(UvGetAddrInfoTest, FailedLookup) {
int fail_cb_called = 0;
auto loop = Loop::Create();
loop->error.connect([&](Error err) {
ASSERT_EQ(fail_cb_called, 0);
ASSERT_LT(err.code(), 0);
fail_cb_called++;
});
// Use a FQDN by ending in a period
GetAddrInfo(
loop, [](const addrinfo&) { FAIL(); }, "xyzzy.xyzzy.xyzzy.");
loop->Run();
ASSERT_EQ(fail_cb_called, 1);
}
TEST(UvGetAddrInfoTest, Basic) {
int getaddrinfo_cbs = 0;
auto loop = Loop::Create();
loop->error.connect([](Error) { FAIL(); });
GetAddrInfo(
loop, [&](const addrinfo&) { getaddrinfo_cbs++; }, "localhost");
loop->Run();
ASSERT_EQ(getaddrinfo_cbs, 1);
}
#ifndef _WIN32
TEST(UvGetAddrInfoTest, Concurrent) {
int getaddrinfo_cbs = 0;
int callback_counts[CONCURRENT_COUNT];
auto loop = Loop::Create();
loop->error.connect([](Error) { FAIL(); });
for (int i = 0; i < CONCURRENT_COUNT; i++) {
callback_counts[i] = 0;
GetAddrInfo(
loop,
[i, &callback_counts, &getaddrinfo_cbs](const addrinfo&) {
callback_counts[i]++;
getaddrinfo_cbs++;
},
"localhost");
}
loop->Run();
for (int i = 0; i < CONCURRENT_COUNT; i++) {
ASSERT_EQ(callback_counts[i], 1);
}
}
#endif
} // namespace wpi::uv

View File

@@ -1,74 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include "wpi/uv/GetNameInfo.h" // NOLINT(build/include_order)
#include "gtest/gtest.h" // NOLINT(build/include_order)
#include "wpi/uv/Loop.h"
namespace wpi::uv {
TEST(UvGetNameInfoTest, BasicIp4) {
int getnameinfo_cbs = 0;
auto loop = Loop::Create();
loop->error.connect([](Error) { FAIL(); });
GetNameInfo4(
loop,
[&](const char* hostname, const char* service) {
ASSERT_NE(hostname, nullptr);
ASSERT_NE(service, nullptr);
getnameinfo_cbs++;
},
"127.0.0.1", 80);
loop->Run();
ASSERT_EQ(getnameinfo_cbs, 1);
}
TEST(UvGetNameInfoTest, BasicIp6) {
int getnameinfo_cbs = 0;
auto loop = Loop::Create();
loop->error.connect([](Error) { FAIL(); });
GetNameInfo6(
loop,
[&](const char* hostname, const char* service) {
ASSERT_NE(hostname, nullptr);
ASSERT_NE(service, nullptr);
getnameinfo_cbs++;
},
"::1", 80);
loop->Run();
ASSERT_EQ(getnameinfo_cbs, 1);
}
} // namespace wpi::uv

View File

@@ -1,69 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
/* Copyright Joyent, Inc. and other Node contributors. All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include "wpi/uv/Loop.h" // NOLINT(build/include_order)
#include "gtest/gtest.h" // NOLINT(build/include_order)
#include "wpi/uv/Timer.h"
namespace wpi::uv {
TEST(UvLoopTest, Walk) {
int seen_timer_handle = 0;
auto loop = Loop::Create();
auto timer = Timer::Create(loop);
loop->error.connect([](Error) { FAIL(); });
timer->error.connect([](Error) { FAIL(); });
timer->timeout.connect([&, theTimer = timer.get()] {
theTimer->GetLoopRef().Walk([&](Handle& it) {
if (&it == timer.get()) {
seen_timer_handle++;
}
});
theTimer->Close();
});
timer->Start(Timer::Time{1});
// Start event loop, expect to see the timer handle
ASSERT_EQ(seen_timer_handle, 0);
loop->Run();
ASSERT_EQ(seen_timer_handle, 1);
// Loop is finished, should not see our timer handle
seen_timer_handle = 0;
loop->Walk([&](Handle& it) {
if (&it == timer.get()) {
seen_timer_handle++;
}
});
ASSERT_EQ(seen_timer_handle, 0);
}
} // namespace wpi::uv

View File

@@ -1,69 +0,0 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
#include "wpi/uv/Timer.h" // NOLINT(build/include_order)
#include "gtest/gtest.h"
namespace wpi::uv {
TEST(UvTimerTest, StartAndStop) {
auto loop = Loop::Create();
auto handleNoRepeat = Timer::Create(loop);
auto handleRepeat = Timer::Create(loop);
bool checkTimerNoRepeatEvent = false;
bool checkTimerRepeatEvent = false;
handleNoRepeat->error.connect([](Error) { FAIL(); });
handleRepeat->error.connect([](Error) { FAIL(); });
handleNoRepeat->timeout.connect(
[&checkTimerNoRepeatEvent, handle = handleNoRepeat.get()] {
ASSERT_FALSE(checkTimerNoRepeatEvent);
checkTimerNoRepeatEvent = true;
handle->Stop();
handle->Close();
ASSERT_TRUE(handle->IsClosing());
});
handleRepeat->timeout.connect(
[&checkTimerRepeatEvent, handle = handleRepeat.get()] {
if (checkTimerRepeatEvent) {
handle->Stop();
handle->Close();
ASSERT_TRUE(handle->IsClosing());
} else {
checkTimerRepeatEvent = true;
ASSERT_FALSE(handle->IsClosing());
}
});
handleNoRepeat->Start(Timer::Time{0}, Timer::Time{0});
handleRepeat->Start(Timer::Time{0}, Timer::Time{1});
ASSERT_TRUE(handleNoRepeat->IsActive());
ASSERT_FALSE(handleNoRepeat->IsClosing());
ASSERT_TRUE(handleRepeat->IsActive());
ASSERT_FALSE(handleRepeat->IsClosing());
loop->Run();
ASSERT_TRUE(checkTimerNoRepeatEvent);
ASSERT_TRUE(checkTimerRepeatEvent);
}
TEST(UvTimerTest, Repeat) {
auto loop = Loop::Create();
auto handle = Timer::Create(loop);
handle->SetRepeat(Timer::Time{42});
ASSERT_EQ(handle->GetRepeat(), Timer::Time{42});
handle->Close();
loop->Run(); // forces close callback to run
}
} // namespace wpi::uv