feat: v2 Phase 1 — foundation, proto schemas, RPC framework, SDK skeleton
New workspace structure with 9 crates. Adds: - proto/qpq/v1/*.proto: 11 protobuf schemas covering all 33 RPC methods - quicproquo-proto: dual codegen (capnp legacy + prost v2) - quicproquo-rpc: QUIC RPC framework (framing, server, client, middleware) - quicproquo-sdk: client SDK (QpqClient, events, conversation store) - quicproquo-server/domain/: protocol-agnostic domain types and services - justfile: build commands Wire format: [method_id:u16][req_id:u32][len:u32][protobuf] per QUIC stream. All 151 existing tests pass. Backward compatible with v1 capnp code.
This commit is contained in:
@@ -1,56 +1,38 @@
|
||||
//! Cap'n Proto schemas, generated types, and serialisation helpers for quicproquo.
|
||||
//! Protocol types for quicproquo.
|
||||
//!
|
||||
//! This crate contains both:
|
||||
//! - **v1 (legacy)**: Cap'n Proto generated types from `schemas/*.capnp`
|
||||
//! - **v2**: Protobuf generated types from `proto/qpq/v1/*.proto`
|
||||
//!
|
||||
//! Generated Cap'n Proto code emits unnecessary parentheses; allow per coding standards.
|
||||
#![allow(unused_parens)]
|
||||
|
||||
//! # Design constraints
|
||||
//!
|
||||
//! This crate is intentionally restricted:
|
||||
//! - **No crypto** — key material never enters this crate.
|
||||
//! - **No I/O** — callers own transport; this crate only converts bytes ↔ types.
|
||||
//! - **No I/O** — callers own transport; this crate only converts bytes <-> types.
|
||||
//! - **No async** — pure synchronous data-layer code.
|
||||
//!
|
||||
//! # Generated code
|
||||
//!
|
||||
//! `build.rs` invokes `capnpc` at compile time and writes generated Rust source
|
||||
//! into `$OUT_DIR`. The `include!` macros below splice that code in as a module.
|
||||
|
||||
// ── Generated types ───────────────────────────────────────────────────────────
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
// v1 (legacy): Cap'n Proto generated types
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
#![allow(unused_parens)]
|
||||
|
||||
/// Cap'n Proto generated types for `schemas/auth.capnp`.
|
||||
///
|
||||
/// Do not edit this module by hand — it is entirely machine-generated.
|
||||
pub mod auth_capnp {
|
||||
include!(concat!(env!("OUT_DIR"), "/auth_capnp.rs"));
|
||||
}
|
||||
|
||||
/// Cap'n Proto generated types for `schemas/delivery.capnp`.
|
||||
///
|
||||
/// Do not edit this module by hand — it is entirely machine-generated.
|
||||
pub mod delivery_capnp {
|
||||
include!(concat!(env!("OUT_DIR"), "/delivery_capnp.rs"));
|
||||
}
|
||||
|
||||
/// Cap'n Proto generated types for `schemas/node.capnp`.
|
||||
///
|
||||
/// Do not edit this module by hand — it is entirely machine-generated.
|
||||
pub mod node_capnp {
|
||||
include!(concat!(env!("OUT_DIR"), "/node_capnp.rs"));
|
||||
}
|
||||
|
||||
/// Cap'n Proto generated types for `schemas/federation.capnp`.
|
||||
///
|
||||
/// Do not edit this module by hand — it is entirely machine-generated.
|
||||
pub mod federation_capnp {
|
||||
include!(concat!(env!("OUT_DIR"), "/federation_capnp.rs"));
|
||||
}
|
||||
|
||||
// ── Low-level byte ↔ message conversions ──────────────────────────────────────
|
||||
|
||||
/// Serialise a Cap'n Proto message builder to unpacked wire bytes.
|
||||
///
|
||||
/// The output includes the segment table header. For transport, the
|
||||
/// `quicproquo-core` frame codec prepends a 4-byte little-endian length field.
|
||||
pub fn to_bytes<A: capnp::message::Allocator>(
|
||||
msg: &capnp::message::Builder<A>,
|
||||
) -> Result<Vec<u8>, capnp::Error> {
|
||||
@@ -59,25 +41,17 @@ pub fn to_bytes<A: capnp::message::Allocator>(
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
/// Deserialise unpacked wire bytes into a message with owned segments.
|
||||
///
|
||||
/// Uses a stricter default traversal limit of 1 Mi words (~8 MiB) instead
|
||||
/// of the Cap'n Proto default of 64 MiB, reducing DoS amplification from
|
||||
/// untrusted input. Use [`from_bytes_with_options`] if you need a custom limit.
|
||||
/// Deserialise unpacked wire bytes into a Cap'n Proto message.
|
||||
pub fn from_bytes(
|
||||
bytes: &[u8],
|
||||
) -> Result<capnp::message::Reader<capnp::serialize::OwnedSegments>, capnp::Error> {
|
||||
let mut options = capnp::message::ReaderOptions::new();
|
||||
options.traversal_limit_in_words(Some(1_048_576)); // 1 Mi words = ~8 MiB
|
||||
options.traversal_limit_in_words(Some(1_048_576));
|
||||
let mut cursor = std::io::Cursor::new(bytes);
|
||||
capnp::serialize::read_message(&mut cursor, options)
|
||||
}
|
||||
|
||||
/// Deserialise unpacked wire bytes with caller-specified [`ReaderOptions`].
|
||||
///
|
||||
/// Prefer [`from_bytes`] for typical use. Use this variant when you need to
|
||||
/// raise the traversal limit for large messages (e.g. blob transfers) or
|
||||
/// lower it further for tighter validation.
|
||||
/// Deserialise with custom [`ReaderOptions`].
|
||||
pub fn from_bytes_with_options(
|
||||
bytes: &[u8],
|
||||
options: capnp::message::ReaderOptions,
|
||||
@@ -85,3 +59,79 @@ pub fn from_bytes_with_options(
|
||||
let mut cursor = std::io::Cursor::new(bytes);
|
||||
capnp::serialize::read_message(&mut cursor, options)
|
||||
}
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
// v2: Protobuf (prost) generated types
|
||||
// ════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
/// Protobuf types for the v2 RPC protocol.
|
||||
pub mod qpq {
|
||||
pub mod v1 {
|
||||
include!(concat!(env!("OUT_DIR"), "/qpq.v1.rs"));
|
||||
}
|
||||
}
|
||||
|
||||
/// Method ID constants for the v2 RPC dispatch table.
|
||||
pub mod method_ids {
|
||||
// Auth (100-103)
|
||||
pub const OPAQUE_REGISTER_START: u16 = 100;
|
||||
pub const OPAQUE_REGISTER_FINISH: u16 = 101;
|
||||
pub const OPAQUE_LOGIN_START: u16 = 102;
|
||||
pub const OPAQUE_LOGIN_FINISH: u16 = 103;
|
||||
|
||||
// Delivery (200-205)
|
||||
pub const ENQUEUE: u16 = 200;
|
||||
pub const FETCH: u16 = 201;
|
||||
pub const FETCH_WAIT: u16 = 202;
|
||||
pub const PEEK: u16 = 203;
|
||||
pub const ACK: u16 = 204;
|
||||
pub const BATCH_ENQUEUE: u16 = 205;
|
||||
|
||||
// Keys (300-304)
|
||||
pub const UPLOAD_KEY_PACKAGE: u16 = 300;
|
||||
pub const FETCH_KEY_PACKAGE: u16 = 301;
|
||||
pub const UPLOAD_HYBRID_KEY: u16 = 302;
|
||||
pub const FETCH_HYBRID_KEY: u16 = 303;
|
||||
pub const FETCH_HYBRID_KEYS: u16 = 304;
|
||||
|
||||
// Channel (400)
|
||||
pub const CREATE_CHANNEL: u16 = 400;
|
||||
|
||||
// User (500-501)
|
||||
pub const RESOLVE_USER: u16 = 500;
|
||||
pub const RESOLVE_IDENTITY: u16 = 501;
|
||||
|
||||
// Blob (600-601)
|
||||
pub const UPLOAD_BLOB: u16 = 600;
|
||||
pub const DOWNLOAD_BLOB: u16 = 601;
|
||||
|
||||
// Device (700-702)
|
||||
pub const REGISTER_DEVICE: u16 = 700;
|
||||
pub const LIST_DEVICES: u16 = 701;
|
||||
pub const REVOKE_DEVICE: u16 = 702;
|
||||
|
||||
// P2P (800-802)
|
||||
pub const PUBLISH_ENDPOINT: u16 = 800;
|
||||
pub const RESOLVE_ENDPOINT: u16 = 801;
|
||||
pub const HEALTH: u16 = 802;
|
||||
|
||||
// Federation (900-905)
|
||||
pub const RELAY_ENQUEUE: u16 = 900;
|
||||
pub const RELAY_BATCH_ENQUEUE: u16 = 901;
|
||||
pub const PROXY_FETCH_KEY_PACKAGE: u16 = 902;
|
||||
pub const PROXY_FETCH_HYBRID_KEY: u16 = 903;
|
||||
pub const PROXY_RESOLVE_USER: u16 = 904;
|
||||
pub const FEDERATION_HEALTH: u16 = 905;
|
||||
|
||||
// Account (950)
|
||||
pub const DELETE_ACCOUNT: u16 = 950;
|
||||
|
||||
// Push event types (1000+)
|
||||
pub const PUSH_NEW_MESSAGE: u16 = 1000;
|
||||
pub const PUSH_TYPING: u16 = 1001;
|
||||
pub const PUSH_PRESENCE: u16 = 1002;
|
||||
pub const PUSH_MEMBERSHIP: u16 = 1003;
|
||||
}
|
||||
|
||||
pub use prost;
|
||||
pub use bytes;
|
||||
|
||||
Reference in New Issue
Block a user