Types Reference
crates/types -- Shared Domain Primitives
ID Types
#![allow(unused)] fn main() { const CHAT_ID_LEN: usize = 32; const USER_ID_LEN: usize = 20; const SENDER_ID_LEN: usize = 20; const MSG_ID_LEN: usize = 32; type ChatId = [u8; 32]; type UserId = [u8; 20]; type SenderId = [u8; 20]; type MsgId = [u8; 32]; }
ChatKind
#![allow(unused)] fn main() { #[serde(tag = "t", content = "d")] enum ChatKind { #[serde(rename = "0")] Dm { peer: UserId }, // Direct message, peer is the other participant #[serde(rename = "1")] Group { title: Option<String> }, #[serde(rename = "2")] Channel { title: Option<String> }, } }
Methods: type_id() -> u8, is_dm() -> bool
Default: Dm { peer: [0u8; 20] } (backward compatibility)
CBOR representation: {"t": "0", "d": {"peer": [...]}} -- compact tagged format.
Note: User identity blobs (e.g. public keys) are stored in the dedicated
identity CF (see STORAGE.md), not inside ChatKind.
Message Type Constants
#![allow(unused)] fn main() { mod msg_types { const REGULAR: u8 = 0; // Regular text message const HANDSHAKE: u8 = 1; // E2EE key exchange const KEY_ROTATION: u8 = 2; // E2EE key rotation } }
Recommended ranges: 1-9 E2EE protocol, 10-99 chat management, 100+ application-specific.
HlcTimestamp
#![allow(unused)] fn main() { #[serde(transparent)] struct HlcTimestamp(u64); // packed: 48 bits physical_ms + 16 bits logical }
Hybrid Logical Clock timestamp used by CRDT-like domains (members,
identity) and as a network-wide ordering key for messages. Packed into a
single u64:
- Upper 48 bits:
physical_ms-- milliseconds since UNIX epoch - Lower 16 bits:
logical-- per-node counter for events within one ms
Methods: from_parts(phys, log), from_packed(u64), to_packed() -> u64, physical_ms() -> u64, logical() -> u16, next_logical() -> HlcTimestamp. Ord is derived from the packed u64, which gives
lexicographic (physical, logical) ordering -- the property CRDTs rely
on for last-writer-wins decisions.
Constants: MAX_PHYSICAL_MS (48 bits set, year ~10889),
MAX_LOGICAL = u16::MAX, HlcTimestamp::ZERO (sentinel for absent
optional HLC fields in record-id derivation).
Serde: #[serde(transparent)] -- wire encoding is identical to a raw
u64. The 8-byte slot for the legacy ts: u64 in seen_msg value
layout, messages CF key, and identity CF value is reused without size
change. Big-endian encoding of the packed value preserves HLC ordering
under RocksDB's lexicographic key comparison.
Clock
#![allow(unused)] fn main() { trait Clock: Send + Sync + 'static { fn now_ms(&self) -> u64; } struct SystemClock; // wraps SystemTime::now() struct MockClock { ... } // AtomicU64-backed, test-controllable }
Time-source abstraction. Production uses SystemClock; tests use
MockClock with set(ms) and advance(delta_ms). The abstraction
exists so HLC's clock-skew behaviour is reproducible in the test suite.
Shared via Arc<dyn Clock> so multiple owners (HLC state, retention
loop) can read the same source without further synchronisation.
crates/db -- Storage Types
MsgV1 (stored in CF messages)
#![allow(unused)] fn main() { struct MsgV1 { schema: u8, // Always 1 msg_id: MsgId, chat_id: ChatId, sender: SenderId, hlc: HlcTimestamp, // Server-stamped HLC (storage/CRDT/sync) origin_wall_ts: u64, // Frozen originator wall-clock (UI display) seq: u32, // Monotonic sequence within chat text: String, msg_type: u8, // 0 = regular, 1+ = control control: Option<Vec<u8>>, // CBOR payload for control messages kind: ChatKind, } }
ChatMeta (stored in CF chats_meta)
#![allow(unused)] fn main() { struct ChatMeta { last_ts: u64, last_seq: u32, last_msg_id: MsgId, members: Vec<UserId>, last_msg_ts: u64, last_announced_ms: u64, } }
InboxEntry (stored in CF user_inbox)
#![allow(unused)] fn main() { struct InboxEntry { chat_id: ChatId, kind: ChatKind, last_ts: u64, last_msg_id: MsgId, last_sender: SenderId, last_text_preview: String, // Max 80 chars last_seq: u32, } }
MemberInfo (stored in CF members)
#![allow(unused)] fn main() { struct MemberInfo { role: u8, // 0 = participant, 1 = admin added_at: HlcTimestamp, // HLC of the latest Add removed_at: Option<HlcTimestamp>, // HLC of the latest Remove } impl MemberInfo { fn is_active(&self) -> bool { match self.removed_at { None => true, Some(r) => self.added_at > r, } } } }
Public reads (get_member_info, list_members, is_member) filter to
is_active(). Sync helpers iterate raw seen_member and include
tombstones so Merkle anti-entropy can carry removed_at between
peers. See STORAGE.md for the full CRDT model.
Query/Result Types
#![allow(unused)] fn main() { struct PutMsg<'a> { chat: &'a ChatId, sender: &'a [u8; 20], hlc: HlcTimestamp, // Stored in CF `messages` key + msg_id hash origin_wall_ts: u64, // Frozen sender wall-clock for UI display text: &'a str, kind: ChatKind, msg_type: u8, control: Option<&'a [u8]>, } struct RangeQuery<'a> { chat: &'a ChatId, from_ts: u64, to_ts: Option<u64>, after_key_b64: Option<String>, limit: usize, } struct RangePage { items: Vec<(String, Vec<u8>)>, // (key_b64, msg_cbor) next_after_key_b64: Option<String>, } struct ListChatsQuery<'a> { user: &'a [u8; 20], limit: Option<usize>, after_cursor_b64: Option<String>, } struct ListChatsPage { items: Vec<InboxEntryWithCursor>, next_after_cursor_b64: Option<String>, } }
crates/api -- HTTP Types
Command Enum
#![allow(unused)] fn main() { enum Command { PutMessage { chat_id: [u8; 32], kind: ChatKind, sender: [u8; 20], members: Option<Vec<[u8; 20]>>, text: String, // HLC + origin_wall_ts are stamped server-side in the MPSC // handler; clients never specify time. msg_type: u8, control: Option<Vec<u8>>, resp: oneshot::Sender<Result<PutMessageResponseRaw, String>>, }, ListUserChats { user: [u8; 20], limit: usize, after_key: Option<Vec<u8>>, resp: oneshot::Sender<Result<ListUserChatsResponseRaw, String>>, }, GetChatRange { user: [u8; 20], chat_id: [u8; 32], from_ts: u64, to_ts: Option<u64>, after_key: Option<Vec<u8>>, limit: usize, skip_membership_check: bool, resp: oneshot::Sender<Result<GetChatRangeResponseRaw, String>>, }, ReadChatMessage { user: [u8; 20], chat_id: [u8; 32], seq: u32, resp: oneshot::Sender<Result<(), String>>, }, } }
Raw Response Types
#![allow(unused)] fn main() { struct PutMessageResponseRaw { chat_id: [u8; 32], msg_id: [u8; 32], origin_wall_ts: u64, // HTTP DTO surfaces this as `ts` for backward compat } struct InboxChatRaw { chat_id: [u8; 32], kind: ChatKind, last_ts: u64, last_sender: [u8; 20], last_text_preview: String, unread: u32, cursor_key: Vec<u8>, source: String, } struct ListUserChatsResponseRaw { items: Vec<InboxChatRaw>, next_after_key: Option<Vec<u8>>, } struct ChatMessageRaw { key_raw: Vec<u8>, msg_cbor: Vec<u8>, } struct GetChatRangeResponseRaw { items: Vec<ChatMessageRaw>, next_after_key: Option<Vec<u8>>, } }
DTO Types (JSON serialization)
#![allow(unused)] fn main() { #[serde(tag = "type")] enum ChatKindDto { #[serde(rename = "dm")] Dm { peer: String }, #[serde(rename = "group")] Group { title: Option<String> }, #[serde(rename = "channel")] Channel { title: Option<String> }, } struct PostDirectMessageBody { text: String } // validate: 1-1000 chars struct PostDirectMessageResponse { chat_id, msg_id, ts } struct PostControlDirectMessageBody { msg_type: u8, control: String } // validate: msg_type 1-255, control 2-512 hex chars struct PostControlMessageResponse { chat_id, msg_id, ts } struct ListUserChatsQueryParams { limit: Option<usize>, after: Option<String> } // validate: limit 1-1000 struct InboxChat { chat_id, kind, last_ts, last_sender, last_text_preview, unread, cursor } struct ListUserChatsResponse { items: Vec<InboxChat>, next_after: Option<String> } struct GetChatRangeQueryParams { from, to, after, limit } // validate: limit 1-1000 struct ChatMessage { key: String, msg_cbor: String } struct GetChatRangeResponse { items: Vec<ChatMessage>, next_after } struct ReadChatMessageBody { seq: u32 } // validate: >= 1 }
crates/node -- Node Types
GossipMessage
See PROTOCOL.md for full specification.
Key structs (abbreviated; full spec in PROTOCOL.md):
#![allow(unused)] fn main() { struct PutIdentity { user: [u8; 20], // User address blob: Vec<u8>, // Opaque identity blob (max 1024 bytes) hlc: HlcTimestamp, // Server-stamped HLC, used for LWW origin: String, // PeerId of the originating node } }
DbOp
#![allow(unused)] fn main() { enum DbOp { // HLC drives storage/CRDT/sync; origin_wall_ts is frozen sender wall-clock for UI. PutMessage { chat_id, sender, text, hlc: HlcTimestamp, origin_wall_ts: u64, kind, msg_type, control }, UpsertInbox { chat_id, kind, users, all_members, ts, seq, msg_id, sender, text_preview }, SetReadProgress { user, chat_id, seq }, // Discrete Add/Remove/Create from API or gossip. `hlc` is server-stamped. MembershipOp { chat_id, target, role, op_type, hlc: HlcTimestamp, recovered_signer }, // Full peer state from anti-entropy sync. CRDT-merged with local record. ApplyMemberRecord { chat_id, target, info: MemberInfo }, // HLC-LWW on identity blob (max 1024 bytes). PutIdentity { user, hlc: HlcTimestamp, blob }, DeleteInboxEntry { user, chat_id }, } /// Wrapper around UnboundedSender<DbOp> that increments /// db_writer_queue_depth gauge on every successful send. struct DbOpSender { inner: mpsc::UnboundedSender<DbOp> } enum MerkleUpdate { Insert([u8; 32]), Replace { old, new }, Remove([u8; 32]) } struct MerkleSenders { msg_tx: UnboundedSender<[u8; 32]>, // messages (append-only) member_tx: UnboundedSender<MerkleUpdate>, // members (mutable) identity_tx: UnboundedSender<MerkleUpdate>, // identity (mutable) } }
Sync Types
See SYNC.md for SyncDomain, SyncRequest, SyncResponse, SyncState, SyncSession.
Retention Types
See RETENTION.md for the full GC design.
#![allow(unused)] fn main() { struct CycleStats { removed_count: usize, // msg_ids removed from seen_msg and the Merkle tree chats_processed: usize, // chats range-deleted in `messages` hit_limit: bool, // true if GC_BATCH_LIMIT was reached } }
Constants in crates/node/src/retention.rs: RETENTION_WINDOW,
GC_INTERVAL, GC_SHORT_INTERVAL, GC_BATCH_LIMIT, GC_CHUNK_SIZE.
Functions: cutoff_ts_at(now) -> u64, cutoff_ts_now() -> u64,
gc_cycle(db, merkle_msgs, now) -> CycleStats,
run_gc_loop(db, merkle_msgs, cancel).
HlcState (per-node HLC)
#![allow(unused)] fn main() { struct HlcState { state: AtomicU64, // packed HlcTimestamp clock: Arc<dyn Clock>, max_drift_ms: u64, } enum HlcError { DriftTooLarge { incoming_ms: u64, local_ms: u64, max_drift_ms: u64 }, } const DEFAULT_MAX_DRIFT_MS: u64 = 5 * 60 * 1000; // 5 minutes }
Per-node Hybrid Logical Clock state. Generates outgoing HLC stamps
(stamp()) and merges incoming HLC values from gossip (receive()).
Lives in-memory only -- on restart, the state resets to
(clock.now_ms(), 0) and self-corrects via the first incoming gossip.
API:
HlcState::new(clock)-- default 5-minute drift boundHlcState::with_max_drift(clock, max_drift_ms)-- custom bound (tests)stamp() -> HlcTimestamp-- always strictly greater than every prior stamp from this state; wait-free in the uncontended casereceive(remote) -> Result<HlcTimestamp, HlcError>-- advances local state to dominate both. ReturnsDriftTooLargeand leaves state unchanged ifremote.physical_ms > now + max_drift_ms
Shared via Arc<HlcState> across handlers without further
synchronisation -- the single AtomicU64 serialises all updates.
NodeHandle
#![allow(unused)] fn main() { struct NodeHandle { cmd_tx: mpsc::Sender<Command>, local_peer_id: PeerId, db: Arc<ChatDb>, listen_addrs: Arc<RwLock<Vec<Multiaddr>>>, } }
Methods: shutdown(self) -- cancel token + await task.
Returned by run_node(AppConfig).
HandlerContext
#![allow(unused)] fn main() { struct HandlerContext<'a> { db: Arc<ChatDb>, swarm: &'a mut Swarm<MyBehaviour>, pending_queries: &'a mut HashMap<[u8; 16], PendingQuery>, peer_cache: &'a PeerCache, local_peer_id: PeerId, local_peer_id_str: Arc<str>, // cached PeerId string (avoids Base58 per request) db_write_tx: DbOpSender, inbox_batch_tx: Option<InboxBatchSender>, } }
Configuration
#![allow(unused)] fn main() { struct AppConfig { keypair: Keypair, // secp256k1 listen: Multiaddr, // e.g. /ip4/0.0.0.0/tcp/4001 bootnodes: Option<Vec<Multiaddr>>, listen_api: Option<SocketAddr>, // e.g. 0.0.0.0:3000 db_path: Option<String>, expose_metrics: bool, metrics_listen: Option<SocketAddr>, } }
TOML config file format (loaded via config::load_config):
private_key = "0x..."
listen = "/ip4/0.0.0.0/tcp/4001"
bootnodes = ["/ip4/.../tcp/4001/p2p/..."]
listen_api = "0.0.0.0:3000"
db_path = "./chatdb-data"
expose_metrics = true
metrics_listen = "0.0.0.0:9090"