Rename all project references from quicproquo/qpq to quicprochat/qpc across documentation, Docker configuration, CI workflows, packaging scripts, operational configs, and build tooling. - Docker: crate paths, binary names, user/group, data dirs, env vars - CI: workflow crate references, binary names, artifact names - Docs: all markdown files under docs/, SDK READMEs, book.toml - Packaging: OpenWrt Makefile, init script, UCI config (file renames) - Scripts: justfile, dev-shell, screenshot, cross-compile, ai_team - Operations: Prometheus config, alert rules, Grafana dashboard - Config: .env.example (QPQ_* → QPC_*), CODEOWNERS paths - Top-level: README, CONTRIBUTING, ROADMAP, CLAUDE.md
14 KiB
Protobuf Framing
quicprochat v2 uses a custom binary framing protocol layered over QUIC bidirectional streams. Message payloads are serialised with Protocol Buffers (Protobuf) via the prost crate. The framing layer (implemented in quicprochat-rpc) adds a compact fixed-size header that carries the method ID, request correlation ID, and payload length -- enabling zero-copy dispatch without a separate length-delimited codec.
This page covers the three frame types, the method ID dispatch table, status codes, push event delivery, and the Protobuf schema organisation.
Frame Types
There are three frame types in the v2 protocol. All multi-byte integers are big-endian (network byte order).
Request Frame (client -> server)
Sent on a QUIC bidirectional stream (one stream per RPC call):
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| method_id (u16 BE) | request_id (u32 BE) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| request_id (cont.) | payload_len (u32 BE) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| payload_len (cont.) | protobuf payload ... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Field | Type | Bytes | Description |
|---|---|---|---|
method_id |
u16 |
0-1 | RPC method identifier (see method IDs table) |
request_id |
u32 |
2-5 | Client-generated correlation ID; echoed back in the response |
payload_len |
u32 |
6-9 | Length of the Protobuf payload in bytes |
| payload | bytes | 10+ | Protobuf-encoded request message |
Header size: 10 bytes. Maximum payload: 4 MiB.
Response Frame (server -> client)
Sent on the same QUIC bidirectional stream as the request:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| status (u8) | request_id (u32 BE) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| request_id (cont.) | payload_len (u32 BE) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| payload_len (cont.) | protobuf payload ... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Field | Type | Bytes | Description |
|---|---|---|---|
status |
u8 |
0 | Status code (see status codes table) |
request_id |
u32 |
1-4 | Echoes the request_id from the request frame |
payload_len |
u32 |
5-8 | Length of the Protobuf payload in bytes |
| payload | bytes | 9+ | Protobuf-encoded response message (may be empty on error) |
Header size: 9 bytes.
Push Frame (server -> client, uni-stream)
Sent by the server on QUIC uni-directional streams for real-time event delivery. No request ID -- push frames are not correlated to any client request.
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| event_type (u16 BE) | payload_len (u32 BE) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| payload_len (cont.) | protobuf payload ... |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Field | Type | Bytes | Description |
|---|---|---|---|
event_type |
u16 |
0-1 | Push event type (see push event types table) |
payload_len |
u32 |
2-5 | Length of the Protobuf payload in bytes |
| payload | bytes | 6+ | Protobuf-encoded push event message |
Header size: 6 bytes.
Status Codes
The status byte in a Response frame carries one of the following values:
| Value | RpcStatus variant |
Meaning |
|---|---|---|
| 0 | Ok |
Success. Response payload contains the result. |
| 1 | BadRequest |
Malformed request, missing required field, or failed validation. |
| 2 | Unauthorized |
Missing or invalid session token. |
| 3 | Forbidden |
Valid token but insufficient permissions for this operation. |
| 4 | NotFound |
Requested resource does not exist (e.g., KeyPackage not found). |
| 5 | RateLimited |
Request rate limit exceeded. Client should back off before retrying. |
| 8 | DeadlineExceeded |
Server could not complete the request within the configured deadline. |
| 9 | Unavailable |
Server temporarily unable to serve the request (e.g., storage unavailable). |
| 10 | Internal |
Unexpected server-side error. |
| 11 | UnknownMethod |
The method_id in the request is not registered. |
Method IDs
All 44 RPC method IDs are defined in crates/quicprochat-proto/src/lib.rs in the method_ids module. The numeric ranges group related methods by service category.
Auth (100-103)
| ID | Method | Request type | Response type |
|---|---|---|---|
| 100 | OpaqueRegisterStart |
OpaqueRegisterStartRequest |
OpaqueRegisterStartResponse |
| 101 | OpaqueRegisterFinish |
OpaqueRegisterFinishRequest |
OpaqueRegisterFinishResponse |
| 102 | OpaqueLoginStart |
OpaqueLoginStartRequest |
OpaqueLoginStartResponse |
| 103 | OpaqueLoginFinish |
OpaqueLoginFinishRequest |
OpaqueLoginFinishResponse |
Delivery (200-205)
| ID | Method | Request type | Response type |
|---|---|---|---|
| 200 | Enqueue |
EnqueueRequest |
EnqueueResponse |
| 201 | Fetch |
FetchRequest |
FetchResponse |
| 202 | FetchWait |
FetchWaitRequest |
FetchWaitResponse |
| 203 | Peek |
PeekRequest |
PeekResponse |
| 204 | Ack |
AckRequest |
AckResponse |
| 205 | BatchEnqueue |
BatchEnqueueRequest |
BatchEnqueueResponse |
Keys (300-304)
| ID | Method | Request type | Response type |
|---|---|---|---|
| 300 | UploadKeyPackage |
UploadKeyPackageRequest |
UploadKeyPackageResponse |
| 301 | FetchKeyPackage |
FetchKeyPackageRequest |
FetchKeyPackageResponse |
| 302 | UploadHybridKey |
UploadHybridKeyRequest |
UploadHybridKeyResponse |
| 303 | FetchHybridKey |
FetchHybridKeyRequest |
FetchHybridKeyResponse |
| 304 | FetchHybridKeys |
FetchHybridKeysRequest |
FetchHybridKeysResponse |
Channel (400)
| ID | Method | Request type | Response type |
|---|---|---|---|
| 400 | CreateChannel |
CreateChannelRequest |
CreateChannelResponse |
Group Management (410-413)
| ID | Method | Request type | Response type |
|---|---|---|---|
| 410 | RemoveMember |
RemoveMemberRequest |
RemoveMemberResponse |
| 411 | UpdateGroupMetadata |
UpdateGroupMetadataRequest |
UpdateGroupMetadataResponse |
| 412 | ListGroupMembers |
ListGroupMembersRequest |
ListGroupMembersResponse |
| 413 | RotateKeys |
RotateKeysRequest |
RotateKeysResponse |
Moderation (420-424)
| ID | Method | Request type | Response type |
|---|---|---|---|
| 420 | ReportMessage |
ReportMessageRequest |
ReportMessageResponse |
| 421 | BanUser |
BanUserRequest |
BanUserResponse |
| 422 | UnbanUser |
UnbanUserRequest |
UnbanUserResponse |
| 423 | ListReports |
ListReportsRequest |
ListReportsResponse |
| 424 | ListBanned |
ListBannedRequest |
ListBannedResponse |
User / Identity (500-501)
| ID | Method | Request type | Response type |
|---|---|---|---|
| 500 | ResolveUser |
ResolveUserRequest |
ResolveUserResponse |
| 501 | ResolveIdentity |
ResolveIdentityRequest |
ResolveIdentityResponse |
Key Transparency (510-520)
| ID | Method | Request type | Response type |
|---|---|---|---|
| 510 | RevokeKey |
RevokeKeyRequest |
RevokeKeyResponse |
| 511 | CheckRevocation |
CheckRevocationRequest |
CheckRevocationResponse |
| 520 | AuditKeyTransparency |
AuditKeyTransparencyRequest |
AuditKeyTransparencyResponse |
Blob Storage (600-601)
| ID | Method | Request type | Response type |
|---|---|---|---|
| 600 | UploadBlob |
UploadBlobRequest |
UploadBlobResponse |
| 601 | DownloadBlob |
DownloadBlobRequest |
DownloadBlobResponse |
Device Management (700-710)
| ID | Method | Request type | Response type |
|---|---|---|---|
| 700 | RegisterDevice |
RegisterDeviceRequest |
RegisterDeviceResponse |
| 701 | ListDevices |
ListDevicesRequest |
ListDevicesResponse |
| 702 | RevokeDevice |
RevokeDeviceRequest |
RevokeDeviceResponse |
| 710 | RegisterPushToken |
RegisterPushTokenRequest |
RegisterPushTokenResponse |
Recovery (750-752)
| ID | Method | Request type | Response type |
|---|---|---|---|
| 750 | StoreRecoveryBundle |
StoreRecoveryBundleRequest |
StoreRecoveryBundleResponse |
| 751 | FetchRecoveryBundle |
FetchRecoveryBundleRequest |
FetchRecoveryBundleResponse |
| 752 | DeleteRecoveryBundle |
DeleteRecoveryBundleRequest |
DeleteRecoveryBundleResponse |
P2P and Health (800-802)
| ID | Method | Request type | Response type |
|---|---|---|---|
| 800 | PublishEndpoint |
PublishEndpointRequest |
PublishEndpointResponse |
| 801 | ResolveEndpoint |
ResolveEndpointRequest |
ResolveEndpointResponse |
| 802 | Health |
HealthRequest |
HealthResponse |
Federation (900-905)
| ID | Method | Request type | Response type |
|---|---|---|---|
| 900 | RelayEnqueue |
RelayEnqueueRequest |
RelayEnqueueResponse |
| 901 | RelayBatchEnqueue |
RelayBatchEnqueueRequest |
RelayBatchEnqueueResponse |
| 902 | ProxyFetchKeyPackage |
ProxyFetchKeyPackageRequest |
ProxyFetchKeyPackageResponse |
| 903 | ProxyFetchHybridKey |
ProxyFetchHybridKeyRequest |
ProxyFetchHybridKeyResponse |
| 904 | ProxyResolveUser |
ProxyResolveUserRequest |
ProxyResolveUserResponse |
| 905 | FederationHealth |
FederationHealthRequest |
FederationHealthResponse |
Account (950)
| ID | Method | Request type | Response type |
|---|---|---|---|
| 950 | DeleteAccount |
DeleteAccountRequest |
DeleteAccountResponse |
Push Event Types
Server-to-client push events are delivered on QUIC uni-streams using the Push frame format. Event types are defined alongside method IDs in quicprochat-proto/src/lib.rs:
| Value | Event | Description |
|---|---|---|
| 1000 | PushNewMessage |
A new message has been enqueued for the client. |
| 1001 | PushTyping |
A group member has started or stopped typing. |
| 1002 | PushPresence |
A contact's presence status has changed (online/offline). |
| 1003 | PushMembership |
Group membership changed (member added or removed). |
Push events avoid the need for the client to long-poll FetchWait (202) for real-time delivery. The client can listen on a background task for incoming uni-streams and process push events independently of pending RPC calls.
Stream Model
Each RPC call uses a dedicated QUIC bidirectional stream:
- Client opens a new bidirectional stream (
connection.open_bi()). - Client encodes the request into a
RequestFrameand writes it to the send half. - Client closes the send half (marks end-of-write).
- Server reads the complete
RequestFramefrom the receive half. - Server processes the request and writes a
ResponseFrameto its send half. - Server closes the send half.
- Client reads the complete
ResponseFrame.
This allows many concurrent RPCs on a single QUIC connection without head-of-line blocking.
Protobuf Schema Organisation
All message types are defined in proto/qpc/v1/:
| File | Contents |
|---|---|
auth.proto |
OPAQUE registration and login message types |
common.proto |
Auth context, account deletion, shared types |
delivery.proto |
Enqueue, Fetch, Peek, Ack, BatchEnqueue |
keys.proto |
MLS key packages, hybrid keys |
channel.proto |
Channel creation |
group.proto |
Group management (remove member, metadata, rotate keys) |
moderation.proto |
Report, ban, unban, list |
user.proto |
User and identity resolution |
kt.proto |
Key transparency (revoke, check, audit) |
blob.proto |
Binary object storage |
device.proto |
Multi-device management, push tokens |
recovery.proto |
Account recovery bundles |
p2p.proto |
P2P endpoints, health |
federation.proto |
Cross-server relay |
All .proto files use package qpc.v1; and are compiled to Rust at build time using prost-build via the quicprochat-proto crate's build.rs. The protobuf-src crate vendors protoc, so no system-wide protoc installation is required.
Generated Rust types are accessed via:
use quicprochat_proto::qpc::v1::{EnqueueRequest, FetchResponse, /* ... */};
use quicprochat_proto::method_ids::{ENQUEUE, FETCH, /* ... */};
Design Constraints of quicprochat-proto
The quicprochat-proto crate enforces three constraints:
- No crypto: Key material never enters this crate. All encryption and signing happens in
quicprochat-core. - No I/O: Callers own the transport. This crate only converts between bytes and types.
- No async: Pure synchronous data-layer code. Async is the caller's responsibility.
These constraints keep the serialisation layer thin and auditable.
Further reading
- QUIC + TLS 1.3 -- The transport layer that carries these frames.
- Service Architecture -- How the server dispatches method IDs to handlers.
- Wire Format Reference -- Full Protobuf schema documentation.
- MLS (RFC 9420) -- How MLS messages are carried as opaque payloads inside Protobuf delivery messages.
- ADR-007 -- Design rationale for the v1 Cap'n Proto to v2 Protobuf migration.