1use kevy_resp::Argv;
30
31#[derive(Debug, Clone, PartialEq, Eq)]
34pub struct HandshakeReq {
35 pub generation: u64,
38 pub from_offset: u64,
41 pub replica_id: String,
44}
45
46#[derive(Debug, PartialEq, Eq)]
48pub enum HandshakeError {
49 BadCommand,
51 WrongArity(usize),
53 BadFromKeyword,
55 BadGeneration,
57 BadOffset,
59 BadIdKeyword,
61 BadReplicaId,
63}
64
65impl std::fmt::Display for HandshakeError {
66 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67 match self {
68 Self::BadCommand => write!(f, "expected REPLICATE command"),
69 Self::WrongArity(n) => write!(f, "REPLICATE expects 6 args, got {n}"),
70 Self::BadFromKeyword => write!(f, "expected 'FROM' keyword"),
71 Self::BadGeneration => write!(f, "generation must be an unsigned decimal"),
72 Self::BadOffset => write!(f, "from-offset must be an unsigned decimal"),
73 Self::BadIdKeyword => write!(f, "expected 'ID' keyword"),
74 Self::BadReplicaId => write!(f, "replica id must be non-empty UTF-8"),
75 }
76 }
77}
78
79impl std::error::Error for HandshakeError {}
80
81#[allow(clippy::missing_panics_doc)]
87pub fn parse_replicate_from(argv: &Argv) -> Result<HandshakeReq, HandshakeError> {
88 if argv.len() != 6 {
89 return Err(HandshakeError::WrongArity(argv.len()));
90 }
91 if !eq_ascii_ci(argv.get(0).unwrap(), b"REPLICATE") {
92 return Err(HandshakeError::BadCommand);
93 }
94 if !eq_ascii_ci(argv.get(1).unwrap(), b"FROM") {
95 return Err(HandshakeError::BadFromKeyword);
96 }
97 let generation =
98 parse_decimal_u64(argv.get(2).unwrap()).ok_or(HandshakeError::BadGeneration)?;
99 let from_offset =
100 parse_decimal_u64(argv.get(3).unwrap()).ok_or(HandshakeError::BadOffset)?;
101 if !eq_ascii_ci(argv.get(4).unwrap(), b"ID") {
102 return Err(HandshakeError::BadIdKeyword);
103 }
104 let id_bytes = argv.get(5).unwrap();
105 if id_bytes.is_empty() {
106 return Err(HandshakeError::BadReplicaId);
107 }
108 let replica_id =
109 std::str::from_utf8(id_bytes).map_err(|_| HandshakeError::BadReplicaId)?.to_string();
110 Ok(HandshakeReq {
111 generation,
112 from_offset,
113 replica_id,
114 })
115}
116
117pub fn encode_ack(generation: u64, current_offset: u64) -> Vec<u8> {
122 let mut out = Vec::with_capacity(8 + 20 + 20 + 3);
123 out.extend_from_slice(b"+ACK ");
124 push_u64(&mut out, generation);
125 out.push(b' ');
126 push_u64(&mut out, current_offset);
127 out.extend_from_slice(b"\r\n");
128 out
129}
130
131fn eq_ascii_ci(a: &[u8], b: &[u8]) -> bool {
132 a.len() == b.len()
133 && a.iter()
134 .zip(b)
135 .all(|(x, y)| x.eq_ignore_ascii_case(y))
136}
137
138fn parse_decimal_u64(bytes: &[u8]) -> Option<u64> {
139 if bytes.is_empty() {
140 return None;
141 }
142 let mut n: u64 = 0;
143 for &b in bytes {
144 if !b.is_ascii_digit() {
145 return None;
146 }
147 n = n.checked_mul(10)?.checked_add(u64::from(b - b'0'))?;
148 }
149 Some(n)
150}
151
152fn push_u64(out: &mut Vec<u8>, n: u64) {
153 if n == 0 {
154 out.push(b'0');
155 return;
156 }
157 let mut tmp = [0u8; 20];
158 let mut i = tmp.len();
159 let mut v = n;
160 while v != 0 {
161 i -= 1;
162 tmp[i] = b'0' + (v % 10) as u8;
163 v /= 10;
164 }
165 out.extend_from_slice(&tmp[i..]);
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171
172 fn argv(args: &[&[u8]]) -> Argv {
173 let mut a = Argv::default();
174 for arg in args {
175 a.push(arg);
176 }
177 a
178 }
179
180 #[test]
181 fn parses_fresh_replica_from_zero() {
182 let req = parse_replicate_from(&argv(&[
183 b"REPLICATE",
184 b"FROM",
185 b"0",
186 b"0",
187 b"ID",
188 b"replica-a",
189 ]))
190 .unwrap();
191 assert_eq!(req.generation, 0);
192 assert_eq!(req.from_offset, 0);
193 assert_eq!(req.replica_id, "replica-a");
194 }
195
196 #[test]
197 fn parses_reconnect_with_generation_and_large_offset() {
198 let req = parse_replicate_from(&argv(&[
199 b"REPLICATE",
200 b"FROM",
201 b"7",
202 b"4294967296", b"ID",
204 b"node-7",
205 ]))
206 .unwrap();
207 assert_eq!(req.generation, 7);
208 assert_eq!(req.from_offset, 4_294_967_296);
209 assert_eq!(req.replica_id, "node-7");
210 }
211
212 #[test]
213 fn keywords_are_case_insensitive() {
214 let req = parse_replicate_from(&argv(&[
215 b"replicate", b"from", b"2", b"1", b"id", b"x",
216 ]))
217 .unwrap();
218 assert_eq!(req.generation, 2);
219 assert_eq!(req.from_offset, 1);
220 assert_eq!(req.replica_id, "x");
221 }
222
223 #[test]
224 fn wrong_arity_rejected_with_actual_count() {
225 let err = parse_replicate_from(&argv(&[b"REPLICATE", b"FROM", b"0", b"ID", b"a"]))
228 .unwrap_err();
229 assert_eq!(err, HandshakeError::WrongArity(5));
230 }
231
232 #[test]
233 fn wrong_command_rejected() {
234 let err =
235 parse_replicate_from(&argv(&[b"SUBSCRIBE", b"FROM", b"0", b"0", b"ID", b"a"]))
236 .unwrap_err();
237 assert_eq!(err, HandshakeError::BadCommand);
238 }
239
240 #[test]
241 fn wrong_from_keyword_rejected() {
242 let err =
243 parse_replicate_from(&argv(&[b"REPLICATE", b"AT", b"0", b"0", b"ID", b"a"]))
244 .unwrap_err();
245 assert_eq!(err, HandshakeError::BadFromKeyword);
246 }
247
248 #[test]
249 fn wrong_id_keyword_rejected() {
250 let err = parse_replicate_from(&argv(&[
251 b"REPLICATE",
252 b"FROM",
253 b"0",
254 b"0",
255 b"NAME",
256 b"a",
257 ]))
258 .unwrap_err();
259 assert_eq!(err, HandshakeError::BadIdKeyword);
260 }
261
262 #[test]
263 fn non_decimal_generation_rejected() {
264 let err =
265 parse_replicate_from(&argv(&[b"REPLICATE", b"FROM", b"NaN", b"0", b"ID", b"a"]))
266 .unwrap_err();
267 assert_eq!(err, HandshakeError::BadGeneration);
268 }
269
270 #[test]
271 fn non_decimal_offset_rejected() {
272 let err =
273 parse_replicate_from(&argv(&[b"REPLICATE", b"FROM", b"1", b"NaN", b"ID", b"a"]))
274 .unwrap_err();
275 assert_eq!(err, HandshakeError::BadOffset);
276 }
277
278 #[test]
279 fn negative_offset_rejected_as_bad_offset() {
280 let err =
281 parse_replicate_from(&argv(&[b"REPLICATE", b"FROM", b"1", b"-1", b"ID", b"a"]))
282 .unwrap_err();
283 assert_eq!(err, HandshakeError::BadOffset);
284 }
285
286 #[test]
287 fn empty_replica_id_rejected() {
288 let err =
289 parse_replicate_from(&argv(&[b"REPLICATE", b"FROM", b"0", b"0", b"ID", b""]))
290 .unwrap_err();
291 assert_eq!(err, HandshakeError::BadReplicaId);
292 }
293
294 #[test]
295 fn non_utf8_replica_id_rejected() {
296 let err = parse_replicate_from(&argv(&[
297 b"REPLICATE",
298 b"FROM",
299 b"0",
300 b"0",
301 b"ID",
302 &[0xFF, 0xFE, 0xFD], ]))
304 .unwrap_err();
305 assert_eq!(err, HandshakeError::BadReplicaId);
306 }
307
308 #[test]
309 fn ack_format_for_zero() {
310 assert_eq!(encode_ack(1, 0), b"+ACK 1 0\r\n");
311 }
312
313 #[test]
314 fn ack_format_for_large_values() {
315 assert_eq!(encode_ack(12, 987_654_321), b"+ACK 12 987654321\r\n");
316 }
317}