Files
quicproquo/docs/sdk/build-your-own.md
Christian Nennemann cbb76af6b1 docs(sdk): add comprehensive SDK documentation and wire format reference
Covers all official SDKs (Rust, Go, Python, TypeScript, C FFI),
the v2 wire format with method ID tables, authentication flow,
and a build-your-own-SDK guide with implementation checklist.
2026-03-04 20:55:24 +01:00

167 lines
5.4 KiB
Markdown

# Build Your Own SDK
This guide explains how to implement a quicproquo client SDK in any language.
## Two Approaches
### Approach 1: C FFI Wrapper (recommended)
The simplest path. Wrap `libquicproquo_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](c-ffi.md) 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**: `qpq`
- **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:
```python
# 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/qpq/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 `qpq`
- [ ] **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/quicproquo-ffi/`): Synchronous wrapper
## Testing
Test your SDK against a local server:
```sh
# Start the server
cargo run -p quicproquo-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