Read a single WebSocket frame. Returns: tuple: (opcode, payload) or None if connection closed
(self)
| 260 | }) |
| 261 | |
| 262 | async def _read_frame(self): # pylint: disable=too-many-return-statements |
| 263 | """Read a single WebSocket frame. |
| 264 | |
| 265 | Returns: |
| 266 | tuple: (opcode, payload) or None if connection closed |
| 267 | """ |
| 268 | # Read frame header (2 bytes minimum) |
| 269 | header = await self._read_exact(2) |
| 270 | if not header: |
| 271 | return None |
| 272 | |
| 273 | first_byte, second_byte = header[0], header[1] |
| 274 | |
| 275 | fin = (first_byte >> 7) & 1 |
| 276 | rsv1 = (first_byte >> 6) & 1 |
| 277 | rsv2 = (first_byte >> 5) & 1 |
| 278 | rsv3 = (first_byte >> 4) & 1 |
| 279 | opcode = first_byte & 0x0F |
| 280 | |
| 281 | # RSV bits must be 0 (no extensions) |
| 282 | if rsv1 or rsv2 or rsv3: |
| 283 | await self._send_close(CLOSE_PROTOCOL_ERROR, "RSV bits set") |
| 284 | return None |
| 285 | |
| 286 | masked = (second_byte >> 7) & 1 |
| 287 | payload_len = second_byte & 0x7F |
| 288 | |
| 289 | # Client frames must be masked (RFC 6455) |
| 290 | if not masked: |
| 291 | await self._send_close(CLOSE_PROTOCOL_ERROR, "Frame not masked") |
| 292 | return None |
| 293 | |
| 294 | # Extended payload length |
| 295 | if payload_len == 126: |
| 296 | ext_len = await self._read_exact(2) |
| 297 | if not ext_len: |
| 298 | return None |
| 299 | payload_len = struct.unpack("!H", ext_len)[0] |
| 300 | elif payload_len == 127: |
| 301 | ext_len = await self._read_exact(8) |
| 302 | if not ext_len: |
| 303 | return None |
| 304 | payload_len = struct.unpack("!Q", ext_len)[0] |
| 305 | |
| 306 | # Read masking key |
| 307 | masking_key = await self._read_exact(4) |
| 308 | if not masking_key: |
| 309 | return None |
| 310 | |
| 311 | # Read payload |
| 312 | payload = await self._read_exact(payload_len) |
| 313 | if payload is None: |
| 314 | return None |
| 315 | |
| 316 | # Unmask payload |
| 317 | payload = self._unmask(payload, masking_key) |
| 318 | |
| 319 | # Handle fragmented messages |