"""v2 wire format: ``[method_id:u16][req_id:u32][len:u32][protobuf]``. Each RPC is sent over its own QUIC stream. The response uses the same framing on the same stream. """ from __future__ import annotations import struct # Header: method_id (u16) + req_id (u32) + length (u32) = 10 bytes. HEADER_FMT = "!HII" # network byte-order: u16 + u32 + u32 HEADER_SIZE = struct.calcsize(HEADER_FMT) # Method IDs (mirrors quicprochat-proto/src/lib.rs::method_ids). # Auth (100-103) OPAQUE_REGISTER_START = 100 OPAQUE_REGISTER_FINISH = 101 OPAQUE_LOGIN_START = 102 OPAQUE_LOGIN_FINISH = 103 # Delivery (200-205) ENQUEUE = 200 FETCH = 201 FETCH_WAIT = 202 PEEK = 203 ACK = 204 BATCH_ENQUEUE = 205 # Keys (300-304) UPLOAD_KEY_PACKAGE = 300 FETCH_KEY_PACKAGE = 301 UPLOAD_HYBRID_KEY = 302 FETCH_HYBRID_KEY = 303 FETCH_HYBRID_KEYS = 304 # Channel (400) CREATE_CHANNEL = 400 # User (500-501) RESOLVE_USER = 500 RESOLVE_IDENTITY = 501 # Blob (600-601) UPLOAD_BLOB = 600 DOWNLOAD_BLOB = 601 # Device (700-702) REGISTER_DEVICE = 700 LIST_DEVICES = 701 REVOKE_DEVICE = 702 # P2P (800-802) PUBLISH_ENDPOINT = 800 RESOLVE_ENDPOINT = 801 HEALTH = 802 # Delete account (950) DELETE_ACCOUNT = 950 def encode_frame(method_id: int, req_id: int, payload: bytes) -> bytes: """Encode a wire frame: header + protobuf payload.""" header = struct.pack(HEADER_FMT, method_id, req_id, len(payload)) return header + payload def decode_header(data: bytes) -> tuple[int, int, int]: """Decode a wire frame header, returning (method_id, req_id, payload_len).""" if len(data) < HEADER_SIZE: raise ValueError(f"header too short: {len(data)} < {HEADER_SIZE}") method_id, req_id, length = struct.unpack(HEADER_FMT, data[:HEADER_SIZE]) return method_id, req_id, length