1use kevy_resp::{Argv, ArgvView, ProtocolError, parse_command_into};
13
14pub use crate::wire_snapshot::{
18 SNAPSHOT_CHUNK_MAX, SNAPSHOT_LINE_MAX, SnapshotMarker, decode_replconf_ack,
19 decode_snapshot_chunk, decode_snapshot_marker, encode_ping, encode_replconf_ack,
20 encode_snapshot_begin, encode_snapshot_chunk, encode_snapshot_end,
21};
22
23#[derive(Debug)]
28pub enum WireError {
29 Truncated,
32 BadEnvelope,
35 BadOffset,
37 NegativeOffset(i64),
39 BadPayload(ProtocolError),
41}
42
43impl std::fmt::Display for WireError {
44 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45 match self {
46 Self::Truncated => write!(f, "wire frame truncated"),
47 Self::BadEnvelope => write!(f, "wire envelope not *2"),
48 Self::BadOffset => write!(f, "wire offset element not RESP integer"),
49 Self::NegativeOffset(n) => write!(f, "wire offset is negative: {n}"),
50 Self::BadPayload(e) => write!(f, "wire inner payload malformed: {e:?}"),
51 }
52 }
53}
54
55impl std::error::Error for WireError {}
56
57impl PartialEq for WireError {
58 fn eq(&self, other: &Self) -> bool {
59 core::mem::discriminant(self) == core::mem::discriminant(other)
64 }
65}
66
67pub fn encode_frame<A: ArgvView + ?Sized>(offset: u64, argv: &A) -> Vec<u8> {
80 debug_assert!(
81 i64::try_from(offset).is_ok(),
82 "replication offset {offset} exceeds i64::MAX — wire envelope cannot encode",
83 );
84 let est = 32 + argv_byte_estimate_view(argv);
88 let mut out = Vec::with_capacity(est);
89 out.extend_from_slice(b"*2\r\n");
91 out.push(b':');
93 push_u64(&mut out, offset);
94 out.extend_from_slice(b"\r\n");
95 let n = argv.len();
98 out.push(b'*');
99 push_u64(&mut out, n as u64);
100 out.extend_from_slice(b"\r\n");
101 for i in 0..n {
102 let arg = &argv[i];
103 out.push(b'$');
104 push_u64(&mut out, arg.len() as u64);
105 out.extend_from_slice(b"\r\n");
106 out.extend_from_slice(arg);
107 out.extend_from_slice(b"\r\n");
108 }
109 out
110}
111
112pub fn decode_frame(buf: &[u8]) -> Result<(u64, Argv, usize), WireError> {
120 let after_env = parse_envelope_header(buf)?;
122 let (offset, after_offset) = parse_offset_line(buf, after_env)?;
124 let inner = &buf[after_offset..];
126 let mut argv = Argv::default();
127 let consumed_inner = match parse_command_into(inner, &mut argv) {
128 Ok(Some(n)) => n,
129 Ok(None) => return Err(WireError::Truncated),
130 Err(e) => return Err(WireError::BadPayload(e)),
131 };
132 Ok((offset, argv, after_offset + consumed_inner))
133}
134
135fn parse_envelope_header(buf: &[u8]) -> Result<usize, WireError> {
138 if buf.len() < 4 {
140 return Err(WireError::Truncated);
141 }
142 if buf[0] != b'*' {
143 return Err(WireError::BadEnvelope);
144 }
145 let eol = find_crlf(buf, 1).ok_or(WireError::Truncated)?;
146 let count = parse_decimal(&buf[1..eol]).ok_or(WireError::BadEnvelope)?;
147 if count != 2 {
148 return Err(WireError::BadEnvelope);
149 }
150 Ok(eol + 2)
151}
152
153fn parse_offset_line(buf: &[u8], start: usize) -> Result<(u64, usize), WireError> {
155 if start >= buf.len() {
156 return Err(WireError::Truncated);
157 }
158 if buf[start] != b':' {
159 return Err(WireError::BadOffset);
160 }
161 let eol = find_crlf(buf, start + 1).ok_or(WireError::Truncated)?;
162 let raw = &buf[start + 1..eol];
163 let signed = parse_signed_decimal(raw).ok_or(WireError::BadOffset)?;
166 if signed < 0 {
167 return Err(WireError::NegativeOffset(signed));
168 }
169 Ok((signed as u64, eol + 2))
170}
171
172pub(crate) fn find_crlf(buf: &[u8], from: usize) -> Option<usize> {
175 let mut i = from;
176 while i + 1 < buf.len() {
177 if buf[i] == b'\r' && buf[i + 1] == b'\n' {
178 return Some(i);
179 }
180 i += 1;
181 }
182 None
183}
184
185pub(crate) fn parse_decimal(bytes: &[u8]) -> Option<u64> {
187 if bytes.is_empty() {
188 return None;
189 }
190 let mut n: u64 = 0;
191 for &b in bytes {
192 if !b.is_ascii_digit() {
193 return None;
194 }
195 n = n.checked_mul(10)?.checked_add(u64::from(b - b'0'))?;
196 }
197 Some(n)
198}
199
200fn parse_signed_decimal(bytes: &[u8]) -> Option<i64> {
203 if bytes.is_empty() {
204 return None;
205 }
206 let (neg, digits) = match bytes[0] {
207 b'-' => (true, &bytes[1..]),
208 b'+' => (false, &bytes[1..]),
209 _ => (false, bytes),
210 };
211 let n = parse_decimal(digits)?;
212 if neg {
213 if n > (i64::MAX as u64) + 1 {
216 return None;
217 }
218 if n == (i64::MAX as u64) + 1 {
219 return Some(i64::MIN);
220 }
221 Some(-(n as i64))
222 } else {
223 if n > i64::MAX as u64 {
224 return None;
225 }
226 Some(n as i64)
227 }
228}
229
230pub(crate) fn push_u64(out: &mut Vec<u8>, n: u64) {
233 if n == 0 {
234 out.push(b'0');
235 return;
236 }
237 let mut tmp = [0u8; 20]; let mut i = tmp.len();
239 let mut v = n;
240 while v != 0 {
241 i -= 1;
242 tmp[i] = b'0' + (v % 10) as u8;
243 v /= 10;
244 }
245 out.extend_from_slice(&tmp[i..]);
246}
247
248fn argv_byte_estimate_view<A: ArgvView + ?Sized>(argv: &A) -> usize {
252 let mut bytes = 0usize;
255 for i in 0..argv.len() {
256 bytes += argv[i].len();
257 }
258 bytes + argv.len() * 10
259}
260
261#[cfg(test)]
262mod tests {
263 use super::*;
264
265 fn argv_from(args: &[&[u8]]) -> Argv {
266 let mut a = Argv::default();
267 for arg in args {
268 a.push(arg);
269 }
270 a
271 }
272
273 #[test]
274 fn roundtrip_simple_set() {
275 let argv = argv_from(&[b"SET", b"foo", b"bar"]);
276 let bytes = encode_frame(42, &argv);
277 let (offset, decoded, used) = decode_frame(&bytes).expect("decode");
278 assert_eq!(offset, 42);
279 assert_eq!(decoded, argv);
280 assert_eq!(used, bytes.len());
281 }
282
283 #[test]
284 fn roundtrip_offset_zero_and_max() {
285 for offset in [0u64, 1, i64::MAX as u64] {
288 let argv = argv_from(&[b"PING"]);
289 let bytes = encode_frame(offset, &argv);
290 let (back, _, _) = decode_frame(&bytes).expect("decode");
291 assert_eq!(back, offset);
292 }
293 }
294
295 #[test]
296 #[cfg(debug_assertions)] #[should_panic(expected = "exceeds i64::MAX")]
298 fn encoding_offset_above_i64_max_panics_in_debug() {
299 let argv = argv_from(&[b"PING"]);
303 let _ = encode_frame(u64::MAX, &argv);
304 }
305
306 #[test]
307 fn roundtrip_argv_with_binary_and_empty_args() {
308 let bin: Vec<u8> = (0u8..=255).collect();
309 let argv = argv_from(&[b"HSET", b"key", b"field", &bin, b""]);
310 let bytes = encode_frame(7, &argv);
311 let (_, decoded, _) = decode_frame(&bytes).expect("decode");
312 assert_eq!(decoded.len(), 5);
313 assert_eq!(decoded.get(3), Some(bin.as_slice()));
314 assert_eq!(decoded.get(4), Some(&b""[..]));
315 }
316
317 #[test]
318 fn two_concatenated_frames_decode_in_order() {
319 let a = encode_frame(1, &argv_from(&[b"SET", b"k", b"a"]));
320 let b = encode_frame(2, &argv_from(&[b"DEL", b"k"]));
321 let mut buf = a.clone();
322 buf.extend_from_slice(&b);
323
324 let (off1, argv1, used1) = decode_frame(&buf).expect("frame 1");
325 assert_eq!(off1, 1);
326 assert_eq!(argv1, argv_from(&[b"SET", b"k", b"a"]));
327 assert_eq!(used1, a.len());
328
329 let (off2, argv2, used2) = decode_frame(&buf[used1..]).expect("frame 2");
330 assert_eq!(off2, 2);
331 assert_eq!(argv2, argv_from(&[b"DEL", b"k"]));
332 assert_eq!(used1 + used2, buf.len());
333 }
334
335 #[test]
336 fn offsets_are_strictly_increasing_when_emitted_in_order() {
337 let mut bytes = Vec::new();
338 for o in 0u64..16 {
339 bytes.extend(encode_frame(o, &argv_from(&[b"PING"])));
340 }
341 let mut pos = 0;
342 let mut last: Option<u64> = None;
343 while pos < bytes.len() {
344 let (offset, _, used) = decode_frame(&bytes[pos..]).expect("decode");
345 if let Some(prev) = last {
346 assert!(offset > prev, "offset {offset} not > prev {prev}");
347 }
348 last = Some(offset);
349 pos += used;
350 }
351 assert_eq!(last, Some(15));
352 assert_eq!(pos, bytes.len());
353 }
354
355 #[test]
356 fn truncated_envelope_is_truncated_not_bad() {
357 assert_eq!(decode_frame(&[]), Err(WireError::Truncated));
359 assert_eq!(decode_frame(b"*"), Err(WireError::Truncated));
361 assert_eq!(decode_frame(b"*2\r\n"), Err(WireError::Truncated));
363 assert_eq!(decode_frame(b"*2\r\n:42"), Err(WireError::Truncated));
365 assert_eq!(decode_frame(b"*2\r\n:42\r\n"), Err(WireError::Truncated));
367 assert_eq!(decode_frame(b"*2\r\n:42\r\n*1\r\n$3\r\nfo"), Err(WireError::Truncated));
369 }
370
371 #[test]
372 fn wrong_envelope_count_rejected() {
373 let bad = b"*1\r\n:42\r\n";
375 assert!(matches!(decode_frame(bad), Err(WireError::BadEnvelope)));
376 let bad3 = b"*3\r\n:42\r\n*0\r\n:0\r\n";
378 assert!(matches!(decode_frame(bad3), Err(WireError::BadEnvelope)));
379 }
380
381 #[test]
382 fn non_array_envelope_rejected() {
383 let bad = b":42\r\n*1\r\n$4\r\nPING\r\n";
385 assert!(matches!(decode_frame(bad), Err(WireError::BadEnvelope)));
386 }
387
388 #[test]
389 fn offset_not_integer_rejected() {
390 let bad = b"*2\r\n$2\r\n42\r\n*1\r\n$4\r\nPING\r\n";
392 assert!(matches!(decode_frame(bad), Err(WireError::BadOffset)));
393 }
394
395 #[test]
396 fn negative_offset_rejected_with_value() {
397 let bad = b"*2\r\n:-7\r\n*1\r\n$4\r\nPING\r\n";
398 match decode_frame(bad) {
399 Err(WireError::NegativeOffset(n)) => assert_eq!(n, -7),
400 other => panic!("expected NegativeOffset, got {other:?}"),
401 }
402 }
403
404 #[test]
405 fn malformed_inner_payload_surfaces_bad_payload() {
406 let bad = b"*2\r\n:1\r\n*1\r\n!nope\r\n";
409 assert!(matches!(decode_frame(bad), Err(WireError::BadPayload(_))));
410 }
411
412 #[test]
413 fn offset_with_extra_digits_overflow_rejected() {
414 let mut bad = b"*2\r\n:".to_vec();
418 bad.extend(std::iter::repeat_n(b'9', 21));
419 bad.extend_from_slice(b"\r\n*1\r\n$4\r\nPING\r\n");
420 assert!(matches!(decode_frame(&bad), Err(WireError::BadOffset)));
421 }
422
423 #[test]
428 fn encoded_bytes_are_exactly_what_spec_says() {
429 let argv = argv_from(&[b"SET", b"foo", b"bar"]);
432 let bytes = encode_frame(99, &argv);
433 let expected = b"*2\r\n:99\r\n*3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n";
434 assert_eq!(bytes, expected);
435 }
436}