James Kuszmaul | b13e13f | 2023-11-22 20:44:04 -0800 | [diff] [blame^] | 1 | // 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 | |
| 9 | using namespace wpi::detail; |
| 10 | |
| 11 | static constexpr uint8_t kFlagMasking = 0x80; |
| 12 | static constexpr size_t kWriteAllocSize = 4096; |
| 13 | |
| 14 | static 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 | |
| 47 | size_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 | |
| 87 | size_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 | } |