blob: c5d9548f39055867ba9072658c8846c761d86074 [file] [log] [blame]
James Kuszmaulb13e13f2023-11-22 20:44:04 -08001// 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.
4
5#include "WebSocketSerializer.h"
6
7#include <random>
8
9using namespace wpi::detail;
10
11static constexpr uint8_t kFlagMasking = 0x80;
12static constexpr size_t kWriteAllocSize = 4096;
13
14static std::span<uint8_t> BuildHeader(std::span<uint8_t, 10> header,
15 bool server,
16 const wpi::WebSocket::Frame& frame) {
17 uint8_t* pHeader = header.data();
18
19 // opcode (includes FIN bit)
20 *pHeader++ = frame.opcode;
21
22 // payload length
23 uint64_t size = 0;
24 for (auto&& buf : frame.data) {
25 size += buf.len;
26 }
27 if (size < 126) {
28 *pHeader++ = (server ? 0x00 : kFlagMasking) | size;
29 } else if (size <= 0xffff) {
30 *pHeader++ = (server ? 0x00 : kFlagMasking) | 126;
31 *pHeader++ = (size >> 8) & 0xff;
32 *pHeader++ = size & 0xff;
33 } else {
34 *pHeader++ = (server ? 0x00 : kFlagMasking) | 127;
35 *pHeader++ = (size >> 56) & 0xff;
36 *pHeader++ = (size >> 48) & 0xff;
37 *pHeader++ = (size >> 40) & 0xff;
38 *pHeader++ = (size >> 32) & 0xff;
39 *pHeader++ = (size >> 24) & 0xff;
40 *pHeader++ = (size >> 16) & 0xff;
41 *pHeader++ = (size >> 8) & 0xff;
42 *pHeader++ = size & 0xff;
43 }
44 return header.subspan(0, pHeader - header.data());
45}
46
47size_t SerializedFrames::AddClientFrame(const WebSocket::Frame& frame) {
48 uint8_t headerBuf[10];
49 auto header = BuildHeader(headerBuf, false, frame);
50
51 // allocate a buffer per frame
52 size_t size = header.size() + 4;
53 for (auto&& buf : frame.data) {
54 size += buf.len;
55 }
56 m_allocBufs.emplace_back(uv::Buffer::Allocate(size));
57 m_bufs.emplace_back(m_allocBufs.back());
58
59 char* internalBuf = m_allocBufs.back().data().data();
60 std::memcpy(internalBuf, header.data(), header.size());
61 internalBuf += header.size();
62
63 // generate masking key
64 static std::random_device rd;
65 static std::default_random_engine gen{rd()};
66 std::uniform_int_distribution<unsigned int> dist(0, 255);
67 uint8_t key[4];
68 for (uint8_t& v : key) {
69 v = dist(gen);
70 }
71 std::memcpy(internalBuf, key, 4);
72 internalBuf += 4;
73
74 // copy and mask data
75 int n = 0;
76 for (auto&& buf : frame.data) {
77 for (auto&& ch : buf.data()) {
78 *internalBuf++ = static_cast<uint8_t>(ch) ^ key[n++];
79 if (n >= 4) {
80 n = 0;
81 }
82 }
83 }
84 return size;
85}
86
87size_t SerializedFrames::AddServerFrame(const WebSocket::Frame& frame) {
88 uint8_t headerBuf[10];
89 auto header = BuildHeader(headerBuf, true, frame);
90
91 // manage allocBufs to efficiently store header
92 if (m_allocBufs.empty() ||
93 (m_allocBufPos + header.size()) > kWriteAllocSize) {
94 m_allocBufs.emplace_back(uv::Buffer::Allocate(kWriteAllocSize));
95 m_allocBufPos = 0;
96 }
97 char* internalBuf = m_allocBufs.back().data().data() + m_allocBufPos;
98 std::memcpy(internalBuf, header.data(), header.size());
99 m_bufs.emplace_back(internalBuf, header.size());
100 m_allocBufPos += header.size();
101 // servers can just send the buffers directly without masking
102 m_bufs.append(frame.data.begin(), frame.data.end());
103 size_t sent = header.size();
104 for (auto&& buf : frame.data) {
105 sent += buf.len;
106 }
107 return sent;
108}