or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

connection-management.mdevent-system.mdextensions.mdindex.mdlow-level-protocol.md

low-level-protocol.mddocs/

0

# Low-Level Protocol

1

2

Direct frame protocol access for advanced use cases requiring fine-grained control over WebSocket frame generation and parsing. This module provides the underlying protocol implementation that powers the high-level WSConnection interface.

3

4

## Capabilities

5

6

### Frame Protocol

7

8

Core WebSocket frame protocol implementation handling frame parsing, generation, and message assembly.

9

10

```python { .api }

11

class FrameProtocol:

12

"""

13

Complete frame protocol implementation for WebSocket connections.

14

"""

15

16

def __init__(self, client: bool, extensions: List[Extension]) -> None:

17

"""

18

Initialize frame protocol.

19

20

Args:

21

client: True if this is a client connection, False for server

22

extensions: List of enabled extensions

23

"""

24

25

def receive_bytes(self, data: bytes) -> None:

26

"""

27

Feed received bytes into the protocol for parsing.

28

29

Args:

30

data: Raw bytes received from network

31

"""

32

33

def received_frames(self) -> Generator[Frame, None, None]:

34

"""

35

Generator yielding parsed frames from received data.

36

37

Yields:

38

Frame objects representing complete WebSocket frames

39

"""

40

41

def send_data(

42

self, payload: Union[bytes, bytearray, str] = b"", fin: bool = True

43

) -> bytes:

44

"""

45

Generate data frame bytes for transmission.

46

47

Args:

48

payload: Data to send (bytes for binary, str for text)

49

fin: Whether this completes the message

50

51

Returns:

52

Raw frame bytes to send over network

53

54

Raises:

55

ValueError: If payload type is invalid

56

TypeError: If data type changes within a fragmented message

57

"""

58

59

def ping(self, payload: bytes = b"") -> bytes:

60

"""

61

Generate ping frame bytes.

62

63

Args:

64

payload: Optional ping payload (max 125 bytes)

65

66

Returns:

67

Raw ping frame bytes to send

68

"""

69

70

def pong(self, payload: bytes = b"") -> bytes:

71

"""

72

Generate pong frame bytes.

73

74

Args:

75

payload: Optional pong payload (max 125 bytes)

76

77

Returns:

78

Raw pong frame bytes to send

79

"""

80

81

def close(self, code: Optional[int] = None, reason: Optional[str] = None) -> bytes:

82

"""

83

Generate close frame bytes.

84

85

Args:

86

code: Close code (see CloseReason enum)

87

reason: Optional close reason string

88

89

Returns:

90

Raw close frame bytes to send

91

92

Raises:

93

TypeError: If reason provided without code

94

"""

95

```

96

97

### Frame Data Structures

98

99

Core data structures representing WebSocket frames and their components.

100

101

```python { .api }

102

class Frame(NamedTuple):

103

"""

104

Complete frame information.

105

"""

106

opcode: Opcode # Frame opcode (TEXT, BINARY, CLOSE, etc.)

107

payload: Union[bytes, str, Tuple[int, str]] # Frame payload data

108

frame_finished: bool # Whether this frame is complete

109

message_finished: bool # Whether this completes the message

110

111

class Header(NamedTuple):

112

"""

113

Frame header information.

114

"""

115

fin: bool # FIN bit - whether frame completes message

116

rsv: RsvBits # Reserved bits (used by extensions)

117

opcode: Opcode # Frame opcode

118

payload_len: int # Payload length

119

masking_key: Optional[bytes] # Masking key (client frames only)

120

121

class RsvBits(NamedTuple):

122

"""

123

Reserved bits in frame header.

124

"""

125

rsv1: bool # RSV1 bit (used by extensions like permessage-deflate)

126

rsv2: bool # RSV2 bit (reserved for future use)

127

rsv3: bool # RSV3 bit (reserved for future use)

128

```

129

130

### Frame Opcodes

131

132

Enumeration of WebSocket frame opcodes as defined in RFC 6455.

133

134

```python { .api }

135

class Opcode(IntEnum):

136

"""

137

RFC 6455, Section 5.2 - Base Framing Protocol opcodes.

138

"""

139

140

CONTINUATION = 0x0 # Continuation frame for fragmented messages

141

TEXT = 0x1 # Text message frame

142

BINARY = 0x2 # Binary message frame

143

CLOSE = 0x8 # Connection close frame

144

PING = 0x9 # Ping control frame

145

PONG = 0xA # Pong control frame

146

147

def iscontrol(self) -> bool:

148

"""

149

Check if this opcode represents a control frame.

150

151

Returns:

152

True if this is a control frame (CLOSE, PING, PONG)

153

"""

154

```

155

156

### Close Reason Codes

157

158

Standard WebSocket close codes as defined in RFC 6455 Section 7.4.1.

159

160

```python { .api }

161

class CloseReason(IntEnum):

162

"""

163

RFC 6455, Section 7.4.1 - Defined Status Codes.

164

"""

165

166

# Standard close codes

167

NORMAL_CLOSURE = 1000 # Normal closure

168

GOING_AWAY = 1001 # Endpoint going away

169

PROTOCOL_ERROR = 1002 # Protocol error

170

UNSUPPORTED_DATA = 1003 # Unsupported data type

171

NO_STATUS_RCVD = 1005 # No status received (reserved)

172

ABNORMAL_CLOSURE = 1006 # Abnormal closure (reserved)

173

INVALID_FRAME_PAYLOAD_DATA = 1007 # Invalid frame payload

174

POLICY_VIOLATION = 1008 # Policy violation

175

MESSAGE_TOO_BIG = 1009 # Message too big

176

MANDATORY_EXT = 1010 # Mandatory extension missing

177

INTERNAL_ERROR = 1011 # Internal server error

178

SERVICE_RESTART = 1012 # Service restart (non-RFC)

179

TRY_AGAIN_LATER = 1013 # Try again later (non-RFC)

180

TLS_HANDSHAKE_FAILED = 1015 # TLS handshake failed (reserved)

181

```

182

183

### Frame Decoder

184

185

Low-level frame decoder for parsing raw WebSocket frame data.

186

187

```python { .api }

188

class FrameDecoder:

189

"""

190

Low-level WebSocket frame decoder.

191

"""

192

193

def __init__(

194

self, client: bool, extensions: Optional[List[Extension]] = None

195

) -> None:

196

"""

197

Initialize frame decoder.

198

199

Args:

200

client: True if decoding client frames, False for server frames

201

extensions: List of extensions for frame processing

202

"""

203

204

def receive_bytes(self, data: bytes) -> None:

205

"""

206

Feed bytes to the decoder.

207

208

Args:

209

data: Raw bytes from network

210

"""

211

212

def process_buffer(self) -> Optional[Frame]:

213

"""

214

Process buffered data and return a frame if complete.

215

216

Returns:

217

Complete Frame object or None if more data needed

218

219

Raises:

220

ParseFailed: If frame parsing fails

221

"""

222

```

223

224

### Protocol Exceptions

225

226

Exceptions raised during frame protocol operations.

227

228

```python { .api }

229

class ParseFailed(Exception):

230

"""

231

Exception raised when frame parsing fails.

232

"""

233

234

def __init__(

235

self, msg: str, code: CloseReason = CloseReason.PROTOCOL_ERROR

236

) -> None:

237

"""

238

Initialize parse failure exception.

239

240

Args:

241

msg: Error message

242

code: Associated close code for the error

243

"""

244

self.code: CloseReason # Close code associated with the error

245

```

246

247

### Message Decoder

248

249

Helper class for assembling fragmented WebSocket messages from individual frames.

250

251

```python { .api }

252

class MessageDecoder:

253

"""

254

Decoder for assembling WebSocket messages from frames.

255

256

Handles fragmented messages by buffering frame data and reconstructing

257

complete messages, including UTF-8 decoding for text messages.

258

"""

259

260

def __init__(self) -> None:

261

"""Initialize message decoder."""

262

263

def process_frame(self, frame: Frame) -> Frame:

264

"""

265

Process a frame and return assembled message data.

266

267

Handles continuation frames and message assembly, including

268

UTF-8 decoding for text messages.

269

270

Args:

271

frame: Frame to process

272

273

Returns:

274

Frame with assembled message data

275

276

Raises:

277

ParseFailed: If frame sequence is invalid or UTF-8 decoding fails

278

"""

279

```

280

281

### Constants

282

283

```python { .api }

284

import struct

285

# Payload length constants

286

PAYLOAD_LENGTH_TWO_BYTE = 126 # Indicator for 2-byte length

287

PAYLOAD_LENGTH_EIGHT_BYTE = 127 # Indicator for 8-byte length

288

MAX_PAYLOAD_NORMAL = 125 # Maximum single-byte payload length

289

MAX_PAYLOAD_TWO_BYTE = 65535 # Maximum 2-byte payload length (2^16 - 1)

290

MAX_PAYLOAD_EIGHT_BYTE = 2**64 - 1 # Maximum 8-byte payload length

291

MAX_FRAME_PAYLOAD = MAX_PAYLOAD_EIGHT_BYTE # Alias for maximum frame payload

292

293

# Frame header bit masks

294

FIN_MASK = 0x80 # FIN bit mask

295

RSV1_MASK = 0x40 # RSV1 bit mask

296

RSV2_MASK = 0x20 # RSV2 bit mask

297

RSV3_MASK = 0x10 # RSV3 bit mask

298

OPCODE_MASK = 0x0F # Opcode mask

299

MASK_MASK = 0x80 # Mask bit mask

300

PAYLOAD_LEN_MASK = 0x7F # Payload length mask

301

302

# WebSocket protocol version

303

WEBSOCKET_VERSION = b"13" # RFC 6455 WebSocket version

304

305

# Close code ranges

306

MIN_CLOSE_REASON = 1000 # Minimum valid close code

307

MIN_PROTOCOL_CLOSE_REASON = 1000 # Minimum protocol-defined close code

308

MAX_PROTOCOL_CLOSE_REASON = 2999 # Maximum protocol-defined close code

309

MIN_LIBRARY_CLOSE_REASON = 3000 # Minimum library-defined close code

310

MAX_LIBRARY_CLOSE_REASON = 3999 # Maximum library-defined close code

311

MIN_PRIVATE_CLOSE_REASON = 4000 # Minimum private close code

312

MAX_PRIVATE_CLOSE_REASON = 4999 # Maximum private close code

313

MAX_CLOSE_REASON = 4999 # Maximum valid close code

314

315

LOCAL_ONLY_CLOSE_REASONS = ( # Codes that must not appear on wire

316

CloseReason.NO_STATUS_RCVD,

317

CloseReason.ABNORMAL_CLOSURE,

318

CloseReason.TLS_HANDSHAKE_FAILED,

319

)

320

321

# WebSocket accept GUID

322

ACCEPT_GUID = b"258EAFA5-E914-47DA-95CA-C5AB0DC85B11" # RFC 6455 magic string

323

324

# Null mask for server frames

325

NULL_MASK = struct.pack("!I", 0)

326

```

327

328

## Usage Examples

329

330

### Direct Frame Protocol Usage

331

332

```python

333

from wsproto.frame_protocol import FrameProtocol, Opcode, CloseReason

334

from wsproto.extensions import PerMessageDeflate

335

336

# Create frame protocol instance

337

extensions = [PerMessageDeflate()]

338

protocol = FrameProtocol(client=True, extensions=extensions)

339

340

# Send text data

341

text_frame_bytes = protocol.send_data("Hello, WebSocket!", fin=True)

342

print(f"Text frame: {len(text_frame_bytes)} bytes")

343

344

# Send binary data

345

binary_frame_bytes = protocol.send_data(b"Binary data", fin=True)

346

print(f"Binary frame: {len(binary_frame_bytes)} bytes")

347

348

# Send fragmented message

349

fragment1_bytes = protocol.send_data("Start of ", fin=False)

350

fragment2_bytes = protocol.send_data("fragmented message", fin=True)

351

352

# Send control frames

353

ping_bytes = protocol.ping(b"ping-payload")

354

pong_bytes = protocol.pong(b"pong-payload")

355

close_bytes = protocol.close(CloseReason.NORMAL_CLOSURE, "Goodbye")

356

```

357

358

### Frame Parsing

359

360

```python

361

from wsproto.frame_protocol import FrameProtocol, FrameDecoder, Opcode

362

363

# Create frame decoder

364

decoder = FrameDecoder(client=False, extensions=[])

365

366

# Process incoming frame data

367

decoder.receive_bytes(frame_data_chunk1)

368

decoder.receive_bytes(frame_data_chunk2)

369

370

# Try to parse complete frames

371

frame = decoder.process_buffer()

372

if frame:

373

print(f"Received frame: opcode={frame.opcode}")

374

print(f"Payload: {frame.payload}")

375

print(f"Frame finished: {frame.frame_finished}")

376

print(f"Message finished: {frame.message_finished}")

377

378

# Handle different frame types

379

if frame.opcode == Opcode.TEXT:

380

print(f"Text message: {frame.payload}")

381

elif frame.opcode == Opcode.BINARY:

382

print(f"Binary message: {len(frame.payload)} bytes")

383

elif frame.opcode == Opcode.CLOSE:

384

code, reason = frame.payload

385

print(f"Close frame: code={code}, reason='{reason}'")

386

elif frame.opcode == Opcode.PING:

387

print(f"Ping frame: payload={frame.payload}")

388

elif frame.opcode == Opcode.PONG:

389

print(f"Pong frame: payload={frame.payload}")

390

```

391

392

### Message Assembly

393

394

```python

395

from wsproto.frame_protocol import FrameProtocol, MessageDecoder, Opcode

396

397

# Assemble fragmented messages

398

message_decoder = MessageDecoder()

399

text_buffer = ""

400

binary_buffer = b""

401

402

protocol = FrameProtocol(client=False, extensions=[])

403

protocol.receive_bytes(fragmented_frame_data)

404

405

for frame in protocol.received_frames():

406

if frame.opcode in (Opcode.TEXT, Opcode.BINARY, Opcode.CONTINUATION):

407

# Process data frames through message decoder

408

message_frame = message_decoder.process_frame(frame)

409

410

if message_frame.opcode == Opcode.TEXT:

411

text_buffer += message_frame.payload

412

if message_frame.message_finished:

413

print(f"Complete text message: {text_buffer}")

414

text_buffer = ""

415

416

elif message_frame.opcode == Opcode.BINARY:

417

binary_buffer += message_frame.payload

418

if message_frame.message_finished:

419

print(f"Complete binary message: {len(binary_buffer)} bytes")

420

binary_buffer = b""

421

```

422

423

### Custom Frame Generation

424

425

```python

426

import struct

427

from wsproto.frame_protocol import Opcode, RsvBits

428

429

def create_custom_frame(opcode: Opcode, payload: bytes, fin: bool = True, masked: bool = True):

430

"""Create a custom WebSocket frame."""

431

# Build first byte (FIN + RSV + OPCODE)

432

first_byte = (int(fin) << 7) | int(opcode)

433

434

# Build second byte (MASK + payload length)

435

payload_len = len(payload)

436

if payload_len <= 125:

437

second_byte = (int(masked) << 7) | payload_len

438

extended_length = b""

439

elif payload_len <= 65535:

440

second_byte = (int(masked) << 7) | 126

441

extended_length = struct.pack("!H", payload_len)

442

else:

443

second_byte = (int(masked) << 7) | 127

444

extended_length = struct.pack("!Q", payload_len)

445

446

# Build frame

447

frame = bytes([first_byte, second_byte]) + extended_length

448

449

if masked:

450

import os

451

mask = os.urandom(4)

452

frame += mask

453

# Apply mask to payload

454

masked_payload = bytes(b ^ mask[i % 4] for i, b in enumerate(payload))

455

frame += masked_payload

456

else:

457

frame += payload

458

459

return frame

460

461

# Create custom frames

462

text_frame = create_custom_frame(Opcode.TEXT, b"Hello", fin=True, masked=True)

463

binary_frame = create_custom_frame(Opcode.BINARY, b"\x01\x02\x03", fin=True, masked=True)

464

ping_frame = create_custom_frame(Opcode.PING, b"ping", fin=True, masked=True)

465

```

466

467

### Error Handling

468

469

```python

470

from wsproto.frame_protocol import FrameProtocol, ParseFailed, CloseReason

471

472

protocol = FrameProtocol(client=False, extensions=[])

473

474

try:

475

protocol.receive_bytes(malformed_frame_data)

476

for frame in protocol.received_frames():

477

print(f"Received frame: {frame}")

478

479

except ParseFailed as e:

480

print(f"Frame parsing failed: {e}")

481

print(f"Suggested close code: {e.code}")

482

483

# Generate appropriate close frame

484

close_frame = protocol.close(e.code, str(e))

485

486

# Handle specific error types

487

if e.code == CloseReason.PROTOCOL_ERROR:

488

print("Protocol violation detected")

489

elif e.code == CloseReason.INVALID_FRAME_PAYLOAD_DATA:

490

print("Invalid payload data")

491

elif e.code == CloseReason.MESSAGE_TOO_BIG:

492

print("Message exceeds size limit")

493

```

494

495

### Performance Monitoring

496

497

```python

498

from wsproto.frame_protocol import FrameProtocol

499

import time

500

501

class MonitoredFrameProtocol(FrameProtocol):

502

"""Frame protocol with performance monitoring."""

503

504

def __init__(self, *args, **kwargs):

505

super().__init__(*args, **kwargs)

506

self.frame_count = 0

507

self.bytes_sent = 0

508

self.bytes_received = 0

509

self.start_time = time.time()

510

511

def send_data(self, payload=b"", fin=True):

512

frame_bytes = super().send_data(payload, fin)

513

self.frame_count += 1

514

self.bytes_sent += len(frame_bytes)

515

return frame_bytes

516

517

def receive_bytes(self, data):

518

self.bytes_received += len(data)

519

super().receive_bytes(data)

520

521

def get_stats(self):

522

elapsed = time.time() - self.start_time

523

return {

524

'frames_sent': self.frame_count,

525

'bytes_sent': self.bytes_sent,

526

'bytes_received': self.bytes_received,

527

'elapsed_time': elapsed,

528

'send_rate': self.bytes_sent / elapsed if elapsed > 0 else 0,

529

'receive_rate': self.bytes_received / elapsed if elapsed > 0 else 0,

530

}

531

532

# Use monitored protocol

533

protocol = MonitoredFrameProtocol(client=True, extensions=[])

534

535

# Send some data

536

protocol.send_data("Hello")

537

protocol.send_data(b"Binary data")

538

protocol.ping(b"ping")

539

540

# Check performance

541

stats = protocol.get_stats()

542

print(f"Performance stats: {stats}")

543

```