eidetica/sync/protocol.rs
1//! Protocol definitions for sync communication.
2//!
3//! This module defines transport-agnostic message types that can be
4//! used across different network transports (HTTP, Iroh, Bluetooth, etc.).
5
6use serde::{Deserialize, Serialize};
7
8use super::peer_types::Address;
9use crate::{
10 auth::{Permission, crypto::PublicKey},
11 entry::{Entry, ID},
12};
13
14/// Handshake request sent when establishing a peer connection.
15#[allow(clippy::large_enum_variant)]
16#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
17pub struct HandshakeRequest {
18 // FIXME: device_id and public_key are functionally identical
19 /// Unique device identifier
20 pub device_id: PublicKey,
21 /// Ed25519 public key of the sender
22 pub public_key: PublicKey,
23 /// Optional human-readable display name
24 pub display_name: Option<String>,
25 /// Protocol version number
26 pub protocol_version: u32,
27 /// Random challenge bytes for signature verification
28 pub challenge: Vec<u8>,
29 /// Addresses where this peer can be reached for sync
30 pub listen_addresses: Vec<Address>,
31}
32
33/// Information about a tree available for sync
34#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
35pub struct TreeInfo {
36 /// The root ID of the tree
37 pub tree_id: ID,
38 /// Optional human-readable name for the tree
39 pub name: Option<String>,
40 /// Number of entries in the tree
41 pub entry_count: usize,
42 /// Unix timestamp of last modification
43 pub last_modified: u64,
44}
45
46/// Handshake response sent in reply to a handshake request.
47#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
48pub struct HandshakeResponse {
49 // FIXME: device_id and public_key are functionally identical
50 /// Unique device identifier
51 pub device_id: PublicKey,
52 /// Ed25519 public key of the responder
53 pub public_key: PublicKey,
54 /// Optional human-readable display name
55 pub display_name: Option<String>,
56 /// Protocol version number
57 pub protocol_version: u32,
58 /// Signed challenge from the request
59 pub challenge_response: Vec<u8>,
60 /// New challenge for mutual authentication
61 pub new_challenge: Vec<u8>,
62 /// Trees available for synchronization
63 pub available_trees: Vec<TreeInfo>,
64}
65
66/// Unified sync request for both bootstrap and incremental sync
67#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
68pub struct SyncTreeRequest {
69 /// Database ID to sync
70 pub tree_id: ID,
71 /// Our current tips (empty vec signals bootstrap needed)
72 pub our_tips: Vec<ID>,
73 /// Device public key of the requesting peer (used for automatic tree/peer relationship tracking)
74 pub peer_pubkey: Option<PublicKey>,
75 // Note: requesting_key is unverified but this is safe - see handler.rs
76 // handle_bootstrap_request() for detailed explanation.
77 /// Authentication key requesting access (for bootstrap)
78 pub requesting_key: Option<PublicKey>,
79 /// Key name/identifier for the requesting key
80 pub requesting_key_name: Option<String>,
81 /// Desired permission level for bootstrap
82 pub requested_permission: Option<Permission>,
83}
84
85/// Bootstrap response containing complete tree state
86#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
87pub struct BootstrapResponse {
88 /// Database ID being bootstrapped
89 pub tree_id: ID,
90 /// The root entry of the tree
91 pub root_entry: Entry,
92 /// All entries in the tree (excluding root)
93 pub all_entries: Vec<Entry>,
94 /// Whether the requesting key was approved and added
95 pub key_approved: bool,
96 /// The permission level granted (if approved)
97 pub granted_permission: Option<Permission>,
98}
99
100/// Incremental sync response for existing trees
101#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
102pub struct IncrementalResponse {
103 /// Database ID being synced
104 pub tree_id: ID,
105 /// Peer's current tips
106 pub their_tips: Vec<ID>,
107 /// Entries missing from our tree
108 pub missing_entries: Vec<Entry>,
109}
110
111/// Request messages that can be sent to a sync peer.
112#[allow(clippy::large_enum_variant)]
113#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
114pub enum SyncRequest {
115 /// Initial handshake request
116 Handshake(HandshakeRequest),
117 /// Unified tree sync request (handles both bootstrap and incremental)
118 SyncTree(SyncTreeRequest),
119 /// Send entries for synchronization (backward compatibility)
120 SendEntries(Vec<Entry>),
121}
122
123/// Response messages returned from a sync peer.
124#[allow(clippy::large_enum_variant)]
125#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
126pub enum SyncResponse {
127 /// Handshake response
128 Handshake(HandshakeResponse),
129 /// Full database bootstrap for new peers
130 Bootstrap(BootstrapResponse),
131 /// Incremental sync for existing peers
132 Incremental(IncrementalResponse),
133 /// Bootstrap request pending manual approval
134 BootstrapPending {
135 /// Unique identifier for the pending request
136 request_id: String,
137 /// Human-readable message about the pending status
138 message: String,
139 },
140 /// Acknowledgment that entries were received successfully
141 Ack,
142 /// Number of entries received (for multiple entries)
143 Count(usize),
144 /// Error response
145 Error(String),
146}
147
148/// Current protocol version - 0 indicates unstable
149pub const PROTOCOL_VERSION: u32 = 0;
150
151/// Context information about the incoming request.
152///
153/// This struct captures metadata about the connection that initiated
154/// the request, allowing the handler to know where the request came from.
155#[derive(Debug, Clone, Default)]
156pub struct RequestContext {
157 /// The remote address from which this request originated.
158 /// Extracted from the transport layer's connection metadata.
159 pub remote_address: Option<Address>,
160 /// The public key of the peer making this request.
161 /// Set after successful handshake to identify the authenticated peer.
162 pub peer_pubkey: Option<PublicKey>,
163}