# 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