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
5.4 KiB
Build Your Own SDK
This guide explains how to implement a quicprochat client SDK in any language.
Two Approaches
Approach 1: C FFI Wrapper (recommended)
The simplest path. Wrap libquicprochat_ffi using your language's FFI mechanism (e.g., Python CFFI, Ruby FFI, JNI, Swift C interop).
Pros: Full Rust crypto stack (MLS, OPAQUE, hybrid KEM) with zero reimplementation. Cons: Requires shipping a native library, synchronous API only.
See C FFI docs for the function signatures.
Approach 2: Native QUIC + Protobuf
Implement the QUIC transport and protobuf serialization natively in your language.
Pros: No Rust dependency, full async support, smaller binary. Cons: Must handle crypto externally (OPAQUE, MLS key packages).
Native Implementation Checklist
1. QUIC Transport
Open a QUIC connection to the server:
- ALPN:
qpc - TLS: 1.3 with server certificate verification
- Port: 5001 (default)
Libraries by language:
| Language | Library |
|---|---|
| Go | github.com/quic-go/quic-go |
| Python | aioquic |
| Java/Kotlin | netty-quic or Kwik |
| Swift | Network.framework (built-in) |
| C/C++ | quiche, msquic, or ngtcp2 |
| Rust | quinn |
2. Wire Framing
Each RPC uses a dedicated QUIC bidirectional stream with a 10-byte header:
[method_id:u16][req_id:u32][payload_len:u32][protobuf bytes]
All integers are big-endian (network byte order).
Implementation:
# Encode
header = struct.pack("!HII", method_id, req_id, len(payload))
frame = header + payload
# Decode
method_id, req_id, length = struct.unpack("!HII", data[:10])
payload = data[10 : 10 + length]
3. Protobuf Messages
Generate or hand-write protobuf encode/decode for the message types in proto/qpc/v1/*.proto.
Minimum required messages for a basic client:
| Proto file | Messages |
|---|---|
auth.proto |
OpaqueLoginStart{Request,Response}, OpaqueLoginFinish{Request,Response} |
delivery.proto |
EnqueueRequest, EnqueueResponse, FetchRequest, FetchResponse |
user.proto |
ResolveUserRequest, ResolveUserResponse |
channel.proto |
CreateChannelRequest, CreateChannelResponse |
p2p.proto |
HealthRequest, HealthResponse |
common.proto |
DeleteAccountRequest, DeleteAccountResponse |
4. RPC Flow
Client Server
│ │
│ 1. Open QUIC bidirectional stream │
│ 2. Send framed request │
│ 3. Close send side (end-stream) │
│ ──────────────────────────────────►│
│ │
│ 4. Read framed response │
│ 5. Close receive side │
│ ◄──────────────────────────────────│
5. Implementation Checklist
Core operations (implement in order):
- Connect: Open QUIC connection with TLS 1.3 + ALPN
qpc - Health: Send
HealthRequest(method 802), verify server is running - Register: OPAQUE registration (methods 100-101)
- Login: OPAQUE login (methods 102-103), store session token
- Upload Key Package: Upload MLS key package (method 300)
- Resolve User: Look up peer's identity key (method 500)
- Create Channel: Create DM channel with peer (method 400)
- Send: Enqueue encrypted message (method 200)
- Receive: Fetch pending messages (method 201 or 202 for long-poll)
- Ack: Acknowledge received messages (method 204)
- Disconnect: Close QUIC connection gracefully
6. OPAQUE Authentication
OPAQUE is a 4-message protocol. You need an OPAQUE library for your language:
| Language | Library |
|---|---|
| Rust | opaque-ke |
| Go | github.com/cloudflare/circl/opaque |
| Python | opaque (pure Python) or bind to Rust via FFI |
| JavaScript | @nicholasgasior/opaque-wasm or FFI |
The SDK only needs to transport the OPAQUE bytes -- the crypto is handled by the OPAQUE library. The flow is:
- Generate
RegistrationRequestlocally - Send via
OpaqueRegisterStart(method 100) - Process server's
RegistrationResponselocally to produceUpload - Send via
OpaqueRegisterFinish(method 101) - Generate
LoginRequestlocally - Send via
OpaqueLoginStart(method 102) - Process server's
LoginResponselocally to produceFinalization - Send via
OpaqueLoginFinish(method 103) - Store the returned
session_token
7. Error Handling
The server returns protobuf-encoded error responses. Your SDK should:
- Distinguish authentication errors from transport errors
- Implement timeouts on all RPC calls
- Handle QUIC connection loss and reconnection
Reference Implementations
Study these SDKs for patterns:
- Go (
sdks/go/): Native QUIC + Cap'n Proto, full OPAQUE flow - Python (
sdks/python/): Native QUIC + Protobuf v2 wire format - TypeScript (
sdks/typescript/): WebSocket bridge, WASM crypto - C FFI (
crates/quicprochat-ffi/): Synchronous wrapper
Testing
Test your SDK against a local server:
# Start the server
cargo run -p quicprochat-server
# Run your SDK's tests
Verify at minimum:
- Health check returns status "ok"
- OPAQUE register + login succeeds
- Send + receive roundtrip works
- Error handling for invalid credentials