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).expect("the argv.len() != 6 return above"), b"REPLICATE") {
92 return Err(HandshakeError::BadCommand);
93 }
94 if !eq_ascii_ci(argv.get(1).expect("the argv.len() != 6 return above"), b"FROM") {
95 return Err(HandshakeError::BadFromKeyword);
96 }
97 let generation = parse_decimal_u64(argv.get(2).expect("the argv.len() != 6 return above"))
98 .ok_or(HandshakeError::BadGeneration)?;
99 let from_offset = parse_decimal_u64(argv.get(3).expect("the argv.len() != 6 return above"))
100 .ok_or(HandshakeError::BadOffset)?;
101 if !eq_ascii_ci(argv.get(4).expect("the argv.len() != 6 return above"), b"ID") {
102 return Err(HandshakeError::BadIdKeyword);
103 }
104 let id_bytes = argv.get(5).expect("the argv.len() != 6 return above");
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 { generation, from_offset, replica_id })
111}
112
113pub fn encode_ack(generation: u64, current_offset: u64) -> Vec<u8> {
118 let mut out = Vec::with_capacity(8 + 20 + 20 + 3);
119 out.extend_from_slice(b"+ACK ");
120 push_u64(&mut out, generation);
121 out.push(b' ');
122 push_u64(&mut out, current_offset);
123 out.extend_from_slice(b"\r\n");
124 out
125}
126
127fn eq_ascii_ci(a: &[u8], b: &[u8]) -> bool {
128 a.len() == b.len() && a.iter().zip(b).all(|(x, y)| x.eq_ignore_ascii_case(y))
129}
130
131fn parse_decimal_u64(bytes: &[u8]) -> Option<u64> {
132 if bytes.is_empty() {
133 return None;
134 }
135 let mut n: u64 = 0;
136 for &b in bytes {
137 if !b.is_ascii_digit() {
138 return None;
139 }
140 n = n.checked_mul(10)?.checked_add(u64::from(b - b'0'))?;
141 }
142 Some(n)
143}
144
145fn push_u64(out: &mut Vec<u8>, n: u64) {
146 if n == 0 {
147 out.push(b'0');
148 return;
149 }
150 let mut tmp = [0u8; 20];
151 let mut i = tmp.len();
152 let mut v = n;
153 while v != 0 {
154 i -= 1;
155 tmp[i] = b'0' + (v % 10) as u8;
156 v /= 10;
157 }
158 out.extend_from_slice(&tmp[i..]);
159}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164
165 fn argv(args: &[&[u8]]) -> Argv {
166 let mut a = Argv::default();
167 for arg in args {
168 a.push(arg);
169 }
170 a
171 }
172
173 #[test]
174 fn parses_fresh_replica_from_zero() {
175 let req =
176 parse_replicate_from(&argv(&[b"REPLICATE", b"FROM", b"0", b"0", b"ID", b"replica-a"]))
177 .unwrap();
178 assert_eq!(req.generation, 0);
179 assert_eq!(req.from_offset, 0);
180 assert_eq!(req.replica_id, "replica-a");
181 }
182
183 #[test]
184 fn parses_reconnect_with_generation_and_large_offset() {
185 let req = parse_replicate_from(&argv(&[
186 b"REPLICATE",
187 b"FROM",
188 b"7",
189 b"4294967296", b"ID",
191 b"node-7",
192 ]))
193 .unwrap();
194 assert_eq!(req.generation, 7);
195 assert_eq!(req.from_offset, 4_294_967_296);
196 assert_eq!(req.replica_id, "node-7");
197 }
198
199 #[test]
200 fn keywords_are_case_insensitive() {
201 let req =
202 parse_replicate_from(&argv(&[b"replicate", b"from", b"2", b"1", b"id", b"x"])).unwrap();
203 assert_eq!(req.generation, 2);
204 assert_eq!(req.from_offset, 1);
205 assert_eq!(req.replica_id, "x");
206 }
207
208 #[test]
209 fn wrong_arity_rejected_with_actual_count() {
210 let err =
213 parse_replicate_from(&argv(&[b"REPLICATE", b"FROM", b"0", b"ID", b"a"])).unwrap_err();
214 assert_eq!(err, HandshakeError::WrongArity(5));
215 }
216
217 #[test]
218 fn wrong_command_rejected() {
219 let err = parse_replicate_from(&argv(&[b"SUBSCRIBE", b"FROM", b"0", b"0", b"ID", b"a"]))
220 .unwrap_err();
221 assert_eq!(err, HandshakeError::BadCommand);
222 }
223
224 #[test]
225 fn wrong_from_keyword_rejected() {
226 let err = parse_replicate_from(&argv(&[b"REPLICATE", b"AT", b"0", b"0", b"ID", b"a"]))
227 .unwrap_err();
228 assert_eq!(err, HandshakeError::BadFromKeyword);
229 }
230
231 #[test]
232 fn wrong_id_keyword_rejected() {
233 let err = parse_replicate_from(&argv(&[b"REPLICATE", b"FROM", b"0", b"0", b"NAME", b"a"]))
234 .unwrap_err();
235 assert_eq!(err, HandshakeError::BadIdKeyword);
236 }
237
238 #[test]
239 fn non_decimal_generation_rejected() {
240 let err = parse_replicate_from(&argv(&[b"REPLICATE", b"FROM", b"NaN", b"0", b"ID", b"a"]))
241 .unwrap_err();
242 assert_eq!(err, HandshakeError::BadGeneration);
243 }
244
245 #[test]
246 fn non_decimal_offset_rejected() {
247 let err = parse_replicate_from(&argv(&[b"REPLICATE", b"FROM", b"1", b"NaN", b"ID", b"a"]))
248 .unwrap_err();
249 assert_eq!(err, HandshakeError::BadOffset);
250 }
251
252 #[test]
253 fn negative_offset_rejected_as_bad_offset() {
254 let err = parse_replicate_from(&argv(&[b"REPLICATE", b"FROM", b"1", b"-1", b"ID", b"a"]))
255 .unwrap_err();
256 assert_eq!(err, HandshakeError::BadOffset);
257 }
258
259 #[test]
260 fn empty_replica_id_rejected() {
261 let err = parse_replicate_from(&argv(&[b"REPLICATE", b"FROM", b"0", b"0", b"ID", b""]))
262 .unwrap_err();
263 assert_eq!(err, HandshakeError::BadReplicaId);
264 }
265
266 #[test]
267 fn non_utf8_replica_id_rejected() {
268 let err = parse_replicate_from(&argv(&[
269 b"REPLICATE",
270 b"FROM",
271 b"0",
272 b"0",
273 b"ID",
274 &[0xFF, 0xFE, 0xFD], ]))
276 .unwrap_err();
277 assert_eq!(err, HandshakeError::BadReplicaId);
278 }
279
280 #[test]
281 fn ack_format_for_zero() {
282 assert_eq!(encode_ack(1, 0), b"+ACK 1 0\r\n");
283 }
284
285 #[test]
286 fn ack_format_for_large_values() {
287 assert_eq!(encode_ack(12, 987_654_321), b"+ACK 12 987654321\r\n");
288 }
289}