Read a single WebSocket frame from the socket. If deadline_ns is non-zero, abort when the monotonic clock exceeds it. `max_payload_size` caps the advertised payload length and is set tighter for pre-auth frames to prevent a single unauthenticated client from forcing the server to allocate up to 16 MiB before any credentials have been checked.
| 168 | /// forcing the server to allocate up to 16 MiB before any credentials have |
| 169 | /// been checked. |
| 170 | WebSocketFrame readWebSocketFrame(Poco::Net::StreamSocket & socket, UInt64 deadline_ns = 0, uint64_t max_payload_size = 16 * 1024 * 1024) |
| 171 | { |
| 172 | WebSocketFrame frame; |
| 173 | uint8_t header[2]; |
| 174 | |
| 175 | if (!readExact(socket, reinterpret_cast<char *>(header), 2, deadline_ns)) |
| 176 | return frame; |
| 177 | |
| 178 | frame.fin = (header[0] & 0x80) != 0; |
| 179 | frame.opcode = header[0] & 0x0F; |
| 180 | |
| 181 | /// RSV1/RSV2/RSV3 must be zero (no extensions negotiated) |
| 182 | if (header[0] & 0x70) |
| 183 | { |
| 184 | frame.protocol_error = true; |
| 185 | return frame; |
| 186 | } |
| 187 | |
| 188 | /// Reject reserved opcodes per RFC 6455 Section 5.2 |
| 189 | if ((frame.opcode >= 0x03 && frame.opcode <= 0x07) || frame.opcode >= 0x0B) |
| 190 | { |
| 191 | frame.protocol_error = true; |
| 192 | return frame; |
| 193 | } |
| 194 | |
| 195 | bool masked = (header[1] & 0x80) != 0; |
| 196 | uint64_t payload_len = header[1] & 0x7F; |
| 197 | |
| 198 | /// RFC 6455: client-to-server frames MUST be masked |
| 199 | if (!masked) |
| 200 | { |
| 201 | frame.protocol_error = true; |
| 202 | return frame; |
| 203 | } |
| 204 | |
| 205 | /// Control frames (opcode >= 0x08) must have FIN set and payload <= 125 |
| 206 | if (frame.opcode >= 0x08) |
| 207 | { |
| 208 | if (!frame.fin || payload_len > 125) |
| 209 | { |
| 210 | frame.protocol_error = true; |
| 211 | return frame; |
| 212 | } |
| 213 | } |
| 214 | |
| 215 | if (payload_len == 126) |
| 216 | { |
| 217 | uint8_t ext[2]; |
| 218 | if (!readExact(socket, reinterpret_cast<char *>(ext), 2, deadline_ns)) |
| 219 | return frame; |
| 220 | payload_len = (static_cast<uint64_t>(ext[0]) << 8) | ext[1]; |
| 221 | } |
| 222 | else if (payload_len == 127) |
| 223 | { |
| 224 | uint8_t ext[8]; |
| 225 | if (!readExact(socket, reinterpret_cast<char *>(ext), 8, deadline_ns)) |
| 226 | return frame; |
| 227 | payload_len = 0; |
no test coverage detected