Files
quicproquo/docs/sdk/build-your-own.md
Christian Nennemann 2e081ead8e chore: rename quicproquo → quicprochat in docs, Docker, CI, and packaging
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
2026-03-21 19:14:06 +01:00

5.4 KiB

Build Your Own SDK

This guide explains how to implement a quicprochat client SDK in any language.

Two Approaches

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:

  1. Generate RegistrationRequest locally
  2. Send via OpaqueRegisterStart (method 100)
  3. Process server's RegistrationResponse locally to produce Upload
  4. Send via OpaqueRegisterFinish (method 101)
  5. Generate LoginRequest locally
  6. Send via OpaqueLoginStart (method 102)
  7. Process server's LoginResponse locally to produce Finalization
  8. Send via OpaqueLoginFinish (method 103)
  9. 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:

  1. Health check returns status "ok"
  2. OPAQUE register + login succeeds
  3. Send + receive roundtrip works
  4. Error handling for invalid credentials