blob: 9d85e6676a1265401f34c873694857688456969d [file] [log] [blame]
Austin Schuh812d0d12021-11-04 20:16:48 -07001// Copyright (c) FIRST and other WPILib contributors.
2// Open Source Software; you can modify and/or share it under the terms of
3// the WPILib BSD license file in the root directory of this project.
Brian Silverman8fce7482020-01-05 13:18:21 -08004
5#include "wpi/WebSocket.h" // NOLINT(build/include_order)
6
7#include "WebSocketTest.h"
8#include "wpi/Base64.h"
9#include "wpi/HttpParser.h"
10#include "wpi/SmallString.h"
Austin Schuh812d0d12021-11-04 20:16:48 -070011#include "wpi/StringExtras.h"
Brian Silverman8fce7482020-01-05 13:18:21 -080012#include "wpi/raw_uv_ostream.h"
13#include "wpi/sha1.h"
14
15namespace wpi {
16
17class WebSocketClientTest : public WebSocketTest {
18 public:
19 WebSocketClientTest() {
20 // Bare bones server
Austin Schuh812d0d12021-11-04 20:16:48 -070021 req.header.connect([this](std::string_view name, std::string_view value) {
Brian Silverman8fce7482020-01-05 13:18:21 -080022 // save key (required for valid response)
Austin Schuh812d0d12021-11-04 20:16:48 -070023 if (equals_lower(name, "sec-websocket-key")) {
24 clientKey = value;
25 }
Brian Silverman8fce7482020-01-05 13:18:21 -080026 });
27 req.headersComplete.connect([this](bool) {
28 // send response
29 SmallVector<uv::Buffer, 4> bufs;
30 raw_uv_ostream os{bufs, 4096};
31 os << "HTTP/1.1 101 Switching Protocols\r\n";
32 os << "Upgrade: websocket\r\n";
33 os << "Connection: Upgrade\r\n";
34
35 // accept hash
36 SHA1 hash;
37 hash.Update(clientKey);
38 hash.Update("258EAFA5-E914-47DA-95CA-C5AB0DC85B11");
Austin Schuh812d0d12021-11-04 20:16:48 -070039 if (mockBadAccept) {
40 hash.Update("1");
41 }
Brian Silverman8fce7482020-01-05 13:18:21 -080042 SmallString<64> hashBuf;
43 SmallString<64> acceptBuf;
44 os << "Sec-WebSocket-Accept: "
45 << Base64Encode(hash.RawFinal(hashBuf), acceptBuf) << "\r\n";
46
Austin Schuh812d0d12021-11-04 20:16:48 -070047 if (!mockProtocol.empty()) {
Brian Silverman8fce7482020-01-05 13:18:21 -080048 os << "Sec-WebSocket-Protocol: " << mockProtocol << "\r\n";
Austin Schuh812d0d12021-11-04 20:16:48 -070049 }
Brian Silverman8fce7482020-01-05 13:18:21 -080050
51 os << "\r\n";
52
53 conn->Write(bufs, [](auto bufs, uv::Error) {
Austin Schuh812d0d12021-11-04 20:16:48 -070054 for (auto& buf : bufs) {
55 buf.Deallocate();
56 }
Brian Silverman8fce7482020-01-05 13:18:21 -080057 });
58
59 serverHeadersDone = true;
Austin Schuh812d0d12021-11-04 20:16:48 -070060 if (connected) {
61 connected();
62 }
Brian Silverman8fce7482020-01-05 13:18:21 -080063 });
64
65 serverPipe->Listen([this] {
66 conn = serverPipe->Accept();
67 conn->StartRead();
68 conn->data.connect([this](uv::Buffer& buf, size_t size) {
Austin Schuh812d0d12021-11-04 20:16:48 -070069 std::string_view data{buf.base, size};
Brian Silverman8fce7482020-01-05 13:18:21 -080070 if (!serverHeadersDone) {
71 data = req.Execute(data);
Austin Schuh812d0d12021-11-04 20:16:48 -070072 if (req.HasError()) {
73 Finish();
74 }
Brian Silverman8fce7482020-01-05 13:18:21 -080075 ASSERT_EQ(req.GetError(), HPE_OK) << http_errno_name(req.GetError());
Austin Schuh812d0d12021-11-04 20:16:48 -070076 if (data.empty()) {
77 return;
78 }
Brian Silverman8fce7482020-01-05 13:18:21 -080079 }
Austin Schuh812d0d12021-11-04 20:16:48 -070080 wireData.insert(wireData.end(), data.begin(), data.end());
Brian Silverman8fce7482020-01-05 13:18:21 -080081 });
82 conn->end.connect([this] { Finish(); });
83 });
84 }
85
86 bool mockBadAccept = false;
87 std::vector<uint8_t> wireData;
88 std::shared_ptr<uv::Pipe> conn;
89 HttpParser req{HttpParser::kRequest};
90 SmallString<64> clientKey;
91 std::string mockProtocol;
92 bool serverHeadersDone = false;
93 std::function<void()> connected;
94};
95
96TEST_F(WebSocketClientTest, Open) {
97 int gotOpen = 0;
98
99 clientPipe->Connect(pipeName, [&] {
100 auto ws = WebSocket::CreateClient(*clientPipe, "/test", pipeName);
Austin Schuh812d0d12021-11-04 20:16:48 -0700101 ws->closed.connect([&](uint16_t code, std::string_view reason) {
Brian Silverman8fce7482020-01-05 13:18:21 -0800102 Finish();
Austin Schuh812d0d12021-11-04 20:16:48 -0700103 if (code != 1005 && code != 1006) {
Brian Silverman8fce7482020-01-05 13:18:21 -0800104 FAIL() << "Code: " << code << " Reason: " << reason;
Austin Schuh812d0d12021-11-04 20:16:48 -0700105 }
Brian Silverman8fce7482020-01-05 13:18:21 -0800106 });
Austin Schuh812d0d12021-11-04 20:16:48 -0700107 ws->open.connect([&](std::string_view protocol) {
Brian Silverman8fce7482020-01-05 13:18:21 -0800108 ++gotOpen;
109 Finish();
110 ASSERT_TRUE(protocol.empty());
111 });
112 });
113
114 loop->Run();
115
Austin Schuh812d0d12021-11-04 20:16:48 -0700116 if (HasFatalFailure()) {
117 return;
118 }
Brian Silverman8fce7482020-01-05 13:18:21 -0800119 ASSERT_EQ(gotOpen, 1);
120}
121
122TEST_F(WebSocketClientTest, BadAccept) {
123 int gotClosed = 0;
124
125 mockBadAccept = true;
126
127 clientPipe->Connect(pipeName, [&] {
128 auto ws = WebSocket::CreateClient(*clientPipe, "/test", pipeName);
Austin Schuh812d0d12021-11-04 20:16:48 -0700129 ws->closed.connect([&](uint16_t code, std::string_view msg) {
Brian Silverman8fce7482020-01-05 13:18:21 -0800130 Finish();
131 ++gotClosed;
132 ASSERT_EQ(code, 1002) << "Message: " << msg;
133 });
Austin Schuh812d0d12021-11-04 20:16:48 -0700134 ws->open.connect([&](std::string_view protocol) {
Brian Silverman8fce7482020-01-05 13:18:21 -0800135 Finish();
136 FAIL() << "Got open";
137 });
138 });
139
140 loop->Run();
141
Austin Schuh812d0d12021-11-04 20:16:48 -0700142 if (HasFatalFailure()) {
143 return;
144 }
Brian Silverman8fce7482020-01-05 13:18:21 -0800145 ASSERT_EQ(gotClosed, 1);
146}
147
148TEST_F(WebSocketClientTest, ProtocolGood) {
149 int gotOpen = 0;
150
151 mockProtocol = "myProtocol";
152
153 clientPipe->Connect(pipeName, [&] {
154 auto ws = WebSocket::CreateClient(*clientPipe, "/test", pipeName,
155 {"myProtocol", "myProtocol2"});
Austin Schuh812d0d12021-11-04 20:16:48 -0700156 ws->closed.connect([&](uint16_t code, std::string_view msg) {
Brian Silverman8fce7482020-01-05 13:18:21 -0800157 Finish();
Austin Schuh812d0d12021-11-04 20:16:48 -0700158 if (code != 1005 && code != 1006) {
Brian Silverman8fce7482020-01-05 13:18:21 -0800159 FAIL() << "Code: " << code << "Message: " << msg;
Austin Schuh812d0d12021-11-04 20:16:48 -0700160 }
Brian Silverman8fce7482020-01-05 13:18:21 -0800161 });
Austin Schuh812d0d12021-11-04 20:16:48 -0700162 ws->open.connect([&](std::string_view protocol) {
Brian Silverman8fce7482020-01-05 13:18:21 -0800163 ++gotOpen;
164 Finish();
165 ASSERT_EQ(protocol, "myProtocol");
166 });
167 });
168
169 loop->Run();
170
Austin Schuh812d0d12021-11-04 20:16:48 -0700171 if (HasFatalFailure()) {
172 return;
173 }
Brian Silverman8fce7482020-01-05 13:18:21 -0800174 ASSERT_EQ(gotOpen, 1);
175}
176
177TEST_F(WebSocketClientTest, ProtocolRespNotReq) {
178 int gotClosed = 0;
179
180 mockProtocol = "myProtocol";
181
182 clientPipe->Connect(pipeName, [&] {
183 auto ws = WebSocket::CreateClient(*clientPipe, "/test", pipeName);
Austin Schuh812d0d12021-11-04 20:16:48 -0700184 ws->closed.connect([&](uint16_t code, std::string_view msg) {
Brian Silverman8fce7482020-01-05 13:18:21 -0800185 Finish();
186 ++gotClosed;
187 ASSERT_EQ(code, 1003) << "Message: " << msg;
188 });
Austin Schuh812d0d12021-11-04 20:16:48 -0700189 ws->open.connect([&](std::string_view protocol) {
Brian Silverman8fce7482020-01-05 13:18:21 -0800190 Finish();
191 FAIL() << "Got open";
192 });
193 });
194
195 loop->Run();
196
Austin Schuh812d0d12021-11-04 20:16:48 -0700197 if (HasFatalFailure()) {
198 return;
199 }
Brian Silverman8fce7482020-01-05 13:18:21 -0800200 ASSERT_EQ(gotClosed, 1);
201}
202
203TEST_F(WebSocketClientTest, ProtocolReqNotResp) {
204 int gotClosed = 0;
205
206 clientPipe->Connect(pipeName, [&] {
207 auto ws = WebSocket::CreateClient(*clientPipe, "/test", pipeName,
Austin Schuh812d0d12021-11-04 20:16:48 -0700208 {{"myProtocol"}});
209 ws->closed.connect([&](uint16_t code, std::string_view msg) {
Brian Silverman8fce7482020-01-05 13:18:21 -0800210 Finish();
211 ++gotClosed;
212 ASSERT_EQ(code, 1002) << "Message: " << msg;
213 });
Austin Schuh812d0d12021-11-04 20:16:48 -0700214 ws->open.connect([&](std::string_view protocol) {
Brian Silverman8fce7482020-01-05 13:18:21 -0800215 Finish();
216 FAIL() << "Got open";
217 });
218 });
219
220 loop->Run();
221
Austin Schuh812d0d12021-11-04 20:16:48 -0700222 if (HasFatalFailure()) {
223 return;
224 }
Brian Silverman8fce7482020-01-05 13:18:21 -0800225 ASSERT_EQ(gotClosed, 1);
226}
227
228//
229// Send and receive data. Most of these cases are tested in
230// WebSocketServerTest, so only spot check differences like masking.
231//
232
233class WebSocketClientDataTest : public WebSocketClientTest,
234 public ::testing::WithParamInterface<size_t> {
235 public:
236 WebSocketClientDataTest() {
237 clientPipe->Connect(pipeName, [&] {
238 ws = WebSocket::CreateClient(*clientPipe, "/test", pipeName);
Austin Schuh812d0d12021-11-04 20:16:48 -0700239 if (setupWebSocket) {
240 setupWebSocket();
241 }
Brian Silverman8fce7482020-01-05 13:18:21 -0800242 });
243 }
244
245 std::function<void()> setupWebSocket;
246 std::shared_ptr<WebSocket> ws;
247};
248
249INSTANTIATE_TEST_SUITE_P(WebSocketClientDataTests, WebSocketClientDataTest,
250 ::testing::Values(0, 1, 125, 126, 65535, 65536));
251
252TEST_P(WebSocketClientDataTest, SendBinary) {
253 int gotCallback = 0;
254 std::vector<uint8_t> data(GetParam(), 0x03u);
255 setupWebSocket = [&] {
Austin Schuh812d0d12021-11-04 20:16:48 -0700256 ws->open.connect([&](std::string_view) {
257 ws->SendBinary({{data}}, [&](auto bufs, uv::Error) {
Brian Silverman8fce7482020-01-05 13:18:21 -0800258 ++gotCallback;
259 ws->Terminate();
260 ASSERT_FALSE(bufs.empty());
261 ASSERT_EQ(bufs[0].base, reinterpret_cast<const char*>(data.data()));
262 });
263 });
264 };
265
266 loop->Run();
267
268 auto expectData = BuildMessage(0x02, true, true, data);
269 AdjustMasking(wireData);
270 ASSERT_EQ(wireData, expectData);
271 ASSERT_EQ(gotCallback, 1);
272}
273
274TEST_P(WebSocketClientDataTest, ReceiveBinary) {
275 int gotCallback = 0;
276 std::vector<uint8_t> data(GetParam(), 0x03u);
277 setupWebSocket = [&] {
Austin Schuh812d0d12021-11-04 20:16:48 -0700278 ws->binary.connect([&](auto inData, bool fin) {
Brian Silverman8fce7482020-01-05 13:18:21 -0800279 ++gotCallback;
280 ws->Terminate();
281 ASSERT_TRUE(fin);
282 std::vector<uint8_t> recvData{inData.begin(), inData.end()};
283 ASSERT_EQ(data, recvData);
284 });
285 };
286 auto message = BuildMessage(0x02, true, false, data);
Austin Schuh812d0d12021-11-04 20:16:48 -0700287 connected = [&] { conn->Write({{message}}, [&](auto bufs, uv::Error) {}); };
Brian Silverman8fce7482020-01-05 13:18:21 -0800288
289 loop->Run();
290
291 ASSERT_EQ(gotCallback, 1);
292}
293
294//
295// The client must close the connection if a masked frame is received.
296//
297
298TEST_P(WebSocketClientDataTest, ReceiveMasked) {
299 int gotCallback = 0;
300 std::vector<uint8_t> data(GetParam(), ' ');
301 setupWebSocket = [&] {
Austin Schuh812d0d12021-11-04 20:16:48 -0700302 ws->text.connect([&](std::string_view, bool) {
Brian Silverman8fce7482020-01-05 13:18:21 -0800303 ws->Terminate();
304 FAIL() << "Should not have gotten masked message";
305 });
Austin Schuh812d0d12021-11-04 20:16:48 -0700306 ws->closed.connect([&](uint16_t code, std::string_view reason) {
Brian Silverman8fce7482020-01-05 13:18:21 -0800307 ++gotCallback;
308 ASSERT_EQ(code, 1002) << "reason: " << reason;
309 });
310 };
311 auto message = BuildMessage(0x01, true, true, data);
Austin Schuh812d0d12021-11-04 20:16:48 -0700312 connected = [&] { conn->Write({{message}}, [&](auto bufs, uv::Error) {}); };
Brian Silverman8fce7482020-01-05 13:18:21 -0800313
314 loop->Run();
315
316 ASSERT_EQ(gotCallback, 1);
317}
318
319} // namespace wpi