Skip to main content

yantrikdb_protocol/
opcodes.rs

1/// Wire protocol opcodes for YantrikDB.
2///
3/// Each opcode is a single byte identifying the command type.
4/// Direction conventions:
5///   C→S = Client to Server
6///   S→C = Server to Client
7///   Both = Either direction
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
9#[repr(u8)]
10pub enum OpCode {
11    // --- Auth (0x01–0x03) ---
12    Auth = 0x01,
13    AuthOk = 0x02,
14    AuthFail = 0x03,
15
16    // --- Database (0x10–0x12) ---
17    SelectDb = 0x10,
18    CreateDb = 0x11,
19    DbOk = 0x12,
20    ListDb = 0x13,
21    ListDbResult = 0x14,
22
23    // --- Remember (0x20–0x22) ---
24    Remember = 0x20,
25    RememberOk = 0x21,
26    RememberBatch = 0x22,
27
28    // --- Recall (0x30–0x32) ---
29    Recall = 0x30,
30    RecallResult = 0x31,
31    RecallEnd = 0x32,
32
33    // --- Graph (0x40–0x43) ---
34    Relate = 0x40,
35    RelateOk = 0x41,
36    Edges = 0x42,
37    EdgesResult = 0x43,
38
39    // --- Forget (0x50–0x51) ---
40    Forget = 0x50,
41    ForgetOk = 0x51,
42
43    // --- Session (0x60–0x62) ---
44    SessionStart = 0x60,
45    SessionEnd = 0x61,
46    SessionOk = 0x62,
47
48    // --- Think (0x70–0x71) ---
49    Think = 0x70,
50    ThinkResult = 0x71,
51
52    // --- Events (0x80–0x82) ---
53    Subscribe = 0x80,
54    Event = 0x81,
55    Unsubscribe = 0x82,
56
57    // --- Conflicts (0x90–0x92) ---
58    Conflicts = 0x90,
59    Resolve = 0x91,
60    ConflictResult = 0x92,
61
62    // --- Info (0xA0–0xA2) ---
63    Personality = 0xA0,
64    Stats = 0xA1,
65    InfoResult = 0xA2,
66
67    // --- Cluster / Replication (0xC0–0xCF) ---
68    ClusterHello = 0xC0,    // Initial peer handshake
69    ClusterHelloOk = 0xC1,  // Handshake response
70    OplogPull = 0xC2,       // Request ops since (hlc, op_id)
71    OplogPullResult = 0xC3, // Batch of ops
72    OplogPush = 0xC4,       // Push ops to peer (primary → secondary)
73    OplogPushOk = 0xC5,     // Ack with last_applied (hlc, op_id)
74    Heartbeat = 0xC6,       // Leader → follower heartbeat
75    HeartbeatAck = 0xC7,    // Follower → leader ack with current state
76    RequestVote = 0xC8,     // Candidate requests vote
77    VoteGranted = 0xC9,     // Vote granted
78    VoteDenied = 0xCA,      // Vote denied
79    ClusterStatus = 0xCB,   // Get cluster overview
80    ClusterStatusResult = 0xCC,
81    ReadOnlyError = 0xCD, // Sent when client tries to write to read-only replica
82    ClusterDatabaseList = 0xCE, // Request list of databases on peer
83    ClusterDatabaseListResult = 0xCF, // Database list response
84
85    // --- Control (0xF0–0xF2) ---
86    Error = 0xF0,
87    Ping = 0xF1,
88    Pong = 0xF2,
89}
90
91impl OpCode {
92    pub fn from_u8(byte: u8) -> Option<Self> {
93        match byte {
94            0x01 => Some(Self::Auth),
95            0x02 => Some(Self::AuthOk),
96            0x03 => Some(Self::AuthFail),
97
98            0x10 => Some(Self::SelectDb),
99            0x11 => Some(Self::CreateDb),
100            0x12 => Some(Self::DbOk),
101            0x13 => Some(Self::ListDb),
102            0x14 => Some(Self::ListDbResult),
103
104            0x20 => Some(Self::Remember),
105            0x21 => Some(Self::RememberOk),
106            0x22 => Some(Self::RememberBatch),
107
108            0x30 => Some(Self::Recall),
109            0x31 => Some(Self::RecallResult),
110            0x32 => Some(Self::RecallEnd),
111
112            0x40 => Some(Self::Relate),
113            0x41 => Some(Self::RelateOk),
114            0x42 => Some(Self::Edges),
115            0x43 => Some(Self::EdgesResult),
116
117            0x50 => Some(Self::Forget),
118            0x51 => Some(Self::ForgetOk),
119
120            0x60 => Some(Self::SessionStart),
121            0x61 => Some(Self::SessionEnd),
122            0x62 => Some(Self::SessionOk),
123
124            0x70 => Some(Self::Think),
125            0x71 => Some(Self::ThinkResult),
126
127            0x80 => Some(Self::Subscribe),
128            0x81 => Some(Self::Event),
129            0x82 => Some(Self::Unsubscribe),
130
131            0x90 => Some(Self::Conflicts),
132            0x91 => Some(Self::Resolve),
133            0x92 => Some(Self::ConflictResult),
134
135            0xA0 => Some(Self::Personality),
136            0xA1 => Some(Self::Stats),
137            0xA2 => Some(Self::InfoResult),
138
139            0xC0 => Some(Self::ClusterHello),
140            0xC1 => Some(Self::ClusterHelloOk),
141            0xC2 => Some(Self::OplogPull),
142            0xC3 => Some(Self::OplogPullResult),
143            0xC4 => Some(Self::OplogPush),
144            0xC5 => Some(Self::OplogPushOk),
145            0xC6 => Some(Self::Heartbeat),
146            0xC7 => Some(Self::HeartbeatAck),
147            0xC8 => Some(Self::RequestVote),
148            0xC9 => Some(Self::VoteGranted),
149            0xCA => Some(Self::VoteDenied),
150            0xCB => Some(Self::ClusterStatus),
151            0xCC => Some(Self::ClusterStatusResult),
152            0xCD => Some(Self::ReadOnlyError),
153            0xCE => Some(Self::ClusterDatabaseList),
154            0xCF => Some(Self::ClusterDatabaseListResult),
155
156            0xF0 => Some(Self::Error),
157            0xF1 => Some(Self::Ping),
158            0xF2 => Some(Self::Pong),
159
160            _ => None,
161        }
162    }
163}
164
165#[cfg(test)]
166mod tests {
167    use super::*;
168
169    #[test]
170    fn roundtrip_all_opcodes() {
171        let all = [
172            OpCode::Auth,
173            OpCode::AuthOk,
174            OpCode::AuthFail,
175            OpCode::SelectDb,
176            OpCode::CreateDb,
177            OpCode::DbOk,
178            OpCode::ListDb,
179            OpCode::ListDbResult,
180            OpCode::Remember,
181            OpCode::RememberOk,
182            OpCode::RememberBatch,
183            OpCode::Recall,
184            OpCode::RecallResult,
185            OpCode::RecallEnd,
186            OpCode::Relate,
187            OpCode::RelateOk,
188            OpCode::Edges,
189            OpCode::EdgesResult,
190            OpCode::Forget,
191            OpCode::ForgetOk,
192            OpCode::SessionStart,
193            OpCode::SessionEnd,
194            OpCode::SessionOk,
195            OpCode::Think,
196            OpCode::ThinkResult,
197            OpCode::Subscribe,
198            OpCode::Event,
199            OpCode::Unsubscribe,
200            OpCode::Conflicts,
201            OpCode::Resolve,
202            OpCode::ConflictResult,
203            OpCode::ClusterHello,
204            OpCode::ClusterHelloOk,
205            OpCode::OplogPull,
206            OpCode::OplogPullResult,
207            OpCode::OplogPush,
208            OpCode::OplogPushOk,
209            OpCode::Heartbeat,
210            OpCode::HeartbeatAck,
211            OpCode::RequestVote,
212            OpCode::VoteGranted,
213            OpCode::VoteDenied,
214            OpCode::ClusterStatus,
215            OpCode::ClusterStatusResult,
216            OpCode::ReadOnlyError,
217            OpCode::ClusterDatabaseList,
218            OpCode::ClusterDatabaseListResult,
219            OpCode::Personality,
220            OpCode::Stats,
221            OpCode::InfoResult,
222            OpCode::Error,
223            OpCode::Ping,
224            OpCode::Pong,
225        ];
226        for op in all {
227            let byte = op as u8;
228            let decoded = OpCode::from_u8(byte)
229                .unwrap_or_else(|| panic!("failed to decode opcode 0x{byte:02X}"));
230            assert_eq!(decoded, op);
231        }
232    }
233}