Implements quicproquo-py with two transport backends: - Async QUIC transport via aioquic with v2 protobuf wire format - Synchronous Rust FFI transport via CFFI wrapping libquicproquo_ffi Includes manual protobuf encode/decode (no codegen), full RPC coverage (auth, delivery, channels, users, keys, health), PyPI-ready packaging, async echo bot and FFI demo examples, and 15 passing unit tests.
74 lines
1.7 KiB
Python
74 lines
1.7 KiB
Python
"""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 quicproquo-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
|