1use super::ascii::validate_key;
8
9#[derive(Debug, PartialEq, Clone)]
13pub(crate) struct MetaFlag {
14 pub ch: u8,
15 pub token: Option<String>,
16}
17
18#[derive(Debug, PartialEq)]
20pub(crate) enum MetaCmd<'a> {
21 Get { key: &'a [u8], flags: Vec<MetaFlag> },
23 Set {
25 key: &'a [u8],
26 datalen: u32,
27 flags: Vec<MetaFlag>,
28 },
29 Delete { key: &'a [u8], flags: Vec<MetaFlag> },
31 Arithmetic { key: &'a [u8], flags: Vec<MetaFlag> },
33 Noop { flags: Vec<MetaFlag> },
35 Debug { key: &'a [u8], flags: Vec<MetaFlag> },
37}
38
39#[derive(Debug)]
40pub(crate) enum MetaParseResult<'a> {
41 Ok(MetaCmd<'a>),
42 ClientError(String),
43 NeedData(MetaCmd<'a>, u32),
45}
46
47pub(crate) fn parse_meta_flags(tokens: &[&str]) -> Result<Vec<MetaFlag>, String> {
53 let mut flags = Vec::new();
54 for &tok in tokens {
55 if tok.is_empty() {
56 continue;
57 }
58 let bytes = tok.as_bytes();
59 let ch = bytes[0];
60 let token = if bytes.len() > 1 {
61 Some(
62 std::str::from_utf8(&bytes[1..])
63 .map_err(|_| "bad flag token encoding".to_string())?
64 .to_string(),
65 )
66 } else {
67 None
68 };
69 flags.push(MetaFlag { ch, token });
70 }
71 Ok(flags)
72}
73
74pub(crate) fn has_flag(flags: &[MetaFlag], ch: u8) -> bool {
76 flags.iter().any(|f| f.ch == ch)
77}
78
79pub(crate) fn get_flag_token(flags: &[MetaFlag], ch: u8) -> Option<&str> {
81 flags
82 .iter()
83 .find(|f| f.ch == ch)
84 .and_then(|f| f.token.as_deref())
85}
86
87const IGNORED_FLAGS: &[u8] = b"PL";
91
92pub(crate) fn validate_mg_flags(flags: &[MetaFlag]) -> Result<(), String> {
94 const SUPPORTED: &[u8] = b"vcfksOqtT";
95 for f in flags {
96 if SUPPORTED.contains(&f.ch) || IGNORED_FLAGS.contains(&f.ch) {
97 continue;
98 }
99 return Err(format!("unsupported meta flag '{}'", f.ch as char));
100 }
101 validate_numeric_tokens(flags, b"T")?;
102 Ok(())
103}
104
105pub(crate) fn validate_ms_flags(flags: &[MetaFlag]) -> Result<(), String> {
107 const SUPPORTED: &[u8] = b"FTCqOkM";
108 for f in flags {
109 if SUPPORTED.contains(&f.ch) || IGNORED_FLAGS.contains(&f.ch) {
110 continue;
111 }
112 return Err(format!("unsupported meta flag '{}'", f.ch as char));
113 }
114 validate_mode_token_present(flags)?;
116 if let Some(mode) = get_flag_token(flags, b'M') {
118 match mode {
119 "S" | "E" | "A" | "P" | "R" => {}
120 _ => return Err(format!("unsupported ms mode '{mode}'")),
121 }
122 if (mode == "A" || mode == "P") && (has_flag(flags, b'F') || has_flag(flags, b'T')) {
124 return Err(format!("flags F/T not supported with ms mode '{mode}'"));
125 }
126 }
127 validate_numeric_tokens(flags, b"FTC")?;
128 Ok(())
129}
130
131pub(crate) fn validate_md_flags(flags: &[MetaFlag]) -> Result<(), String> {
133 const SUPPORTED: &[u8] = b"CqOk";
134 for f in flags {
135 if SUPPORTED.contains(&f.ch) || IGNORED_FLAGS.contains(&f.ch) {
136 continue;
137 }
138 return Err(format!("unsupported meta flag '{}'", f.ch as char));
139 }
140 validate_numeric_tokens(flags, b"C")?;
141 Ok(())
142}
143
144pub(crate) fn validate_ma_flags(flags: &[MetaFlag]) -> Result<(), String> {
146 const SUPPORTED: &[u8] = b"DJNqOkvcM";
147 for f in flags {
148 if SUPPORTED.contains(&f.ch) || IGNORED_FLAGS.contains(&f.ch) {
149 continue;
150 }
151 return Err(format!("unsupported meta flag '{}'", f.ch as char));
152 }
153 validate_mode_token_present(flags)?;
155 if let Some(mode) = get_flag_token(flags, b'M') {
157 match mode {
158 "I" | "D" => {}
159 _ => return Err(format!("unsupported ma mode '{mode}'")),
160 }
161 }
162 validate_numeric_tokens(flags, b"DJN")?;
163 Ok(())
164}
165
166pub(crate) fn validate_mn_flags(flags: &[MetaFlag]) -> Result<(), String> {
168 const SUPPORTED: &[u8] = b"O";
169 for f in flags {
170 if SUPPORTED.contains(&f.ch) || IGNORED_FLAGS.contains(&f.ch) {
171 continue;
172 }
173 return Err(format!("unsupported meta flag '{}'", f.ch as char));
174 }
175 Ok(())
176}
177
178pub(crate) fn validate_me_flags(flags: &[MetaFlag]) -> Result<(), String> {
180 const SUPPORTED: &[u8] = b"Okq";
181 for f in flags {
182 if SUPPORTED.contains(&f.ch) || IGNORED_FLAGS.contains(&f.ch) {
183 continue;
184 }
185 return Err(format!("unsupported meta flag '{}'", f.ch as char));
186 }
187 Ok(())
188}
189
190fn validate_numeric_tokens(flags: &[MetaFlag], numeric_flags: &[u8]) -> Result<(), String> {
193 for f in flags {
194 if numeric_flags.contains(&f.ch) {
195 match &f.token {
196 Some(tok) => {
197 if tok.parse::<u64>().is_err() {
198 return Err(format!(
199 "bad numeric value '{}' for flag '{}'",
200 tok, f.ch as char
201 ));
202 }
203 }
204 None => {
205 return Err(format!("flag '{}' requires a numeric token", f.ch as char));
206 }
207 }
208 }
209 }
210 Ok(())
211}
212
213fn validate_mode_token_present(flags: &[MetaFlag]) -> Result<(), String> {
215 for f in flags {
216 if f.ch == b'M' && f.token.is_none() {
217 return Err("flag 'M' requires a mode token".to_string());
218 }
219 }
220 Ok(())
221}
222
223pub(crate) fn parse_meta_command(line: &[u8]) -> MetaParseResult<'_> {
227 let line_str = match std::str::from_utf8(line) {
228 Ok(s) => s,
229 Err(_) => return MetaParseResult::ClientError("bad command line format".into()),
230 };
231 let tokens: Vec<&str> = line_str.split_whitespace().collect();
232 if tokens.is_empty() {
233 return MetaParseResult::ClientError("bad command line format".into());
234 }
235
236 match tokens[0] {
237 "mn" => {
238 let flags = match parse_meta_flags(&tokens[1..]) {
239 Ok(f) => f,
240 Err(e) => return MetaParseResult::ClientError(e),
241 };
242 MetaParseResult::Ok(MetaCmd::Noop { flags })
243 }
244 "mg" | "me" | "md" | "ma" => {
245 if tokens.len() < 2 {
246 return MetaParseResult::ClientError("bad command line format".into());
247 }
248 let key = tokens[1].as_bytes();
249 if let Err(e) = validate_key(key) {
250 return MetaParseResult::ClientError(e);
251 }
252 let flags = match parse_meta_flags(&tokens[2..]) {
253 Ok(f) => f,
254 Err(e) => return MetaParseResult::ClientError(e),
255 };
256 match tokens[0] {
257 "mg" => MetaParseResult::Ok(MetaCmd::Get { key, flags }),
258 "me" => MetaParseResult::Ok(MetaCmd::Debug { key, flags }),
259 "md" => MetaParseResult::Ok(MetaCmd::Delete { key, flags }),
260 "ma" => MetaParseResult::Ok(MetaCmd::Arithmetic { key, flags }),
261 _ => unreachable!(),
262 }
263 }
264 "ms" => parse_meta_set(&tokens),
265 _ => MetaParseResult::ClientError("bad command line format".into()),
266 }
267}
268
269fn parse_meta_set<'a>(tokens: &[&'a str]) -> MetaParseResult<'a> {
271 if tokens.len() < 3 {
272 return MetaParseResult::ClientError("bad command line format".into());
273 }
274 let key = tokens[1].as_bytes();
275 if let Err(e) = validate_key(key) {
276 return MetaParseResult::ClientError(e);
277 }
278 let datalen = match tokens[2].parse::<u32>() {
279 Ok(n) => n,
280 Err(_) => return MetaParseResult::ClientError("bad command line format".into()),
281 };
282 let flags = match parse_meta_flags(&tokens[3..]) {
283 Ok(f) => f,
284 Err(e) => return MetaParseResult::ClientError(e),
285 };
286 MetaParseResult::NeedData(
287 MetaCmd::Set {
288 key,
289 datalen,
290 flags,
291 },
292 datalen,
293 )
294}
295
296pub(crate) fn write_meta_flag_echo(out: &mut Vec<u8>, flags: &[MetaFlag], key: &[u8]) {
301 if let Some(opaque) = get_flag_token(flags, b'O') {
302 out.push(b' ');
303 out.push(b'O');
304 out.extend_from_slice(opaque.as_bytes());
305 }
306 if has_flag(flags, b'k') {
307 out.push(b' ');
308 out.push(b'k');
309 out.extend_from_slice(key);
310 }
311}
312
313#[cfg(test)]
316mod tests {
317 use super::*;
318
319 #[test]
320 fn parse_mn() {
321 match parse_meta_command(b"mn") {
322 MetaParseResult::Ok(MetaCmd::Noop { flags }) => assert!(flags.is_empty()),
323 other => panic!("expected Noop, got {other:?}"),
324 }
325 }
326
327 #[test]
328 fn parse_mn_with_opaque() {
329 match parse_meta_command(b"mn Otoken123") {
330 MetaParseResult::Ok(MetaCmd::Noop { flags }) => {
331 assert_eq!(flags.len(), 1);
332 assert_eq!(flags[0].ch, b'O');
333 assert_eq!(flags[0].token.as_deref(), Some("token123"));
334 }
335 other => panic!("expected Noop with O flag, got {other:?}"),
336 }
337 }
338
339 #[test]
340 fn parse_mg_basic() {
341 match parse_meta_command(b"mg mykey v f c") {
342 MetaParseResult::Ok(MetaCmd::Get { key, flags }) => {
343 assert_eq!(key, b"mykey");
344 assert_eq!(flags.len(), 3);
345 assert!(has_flag(&flags, b'v'));
346 assert!(has_flag(&flags, b'f'));
347 assert!(has_flag(&flags, b'c'));
348 }
349 other => panic!("expected Get, got {other:?}"),
350 }
351 }
352
353 #[test]
354 fn parse_mg_with_ttl_update() {
355 match parse_meta_command(b"mg mykey v T300") {
356 MetaParseResult::Ok(MetaCmd::Get { key, flags }) => {
357 assert_eq!(key, b"mykey");
358 assert!(has_flag(&flags, b'v'));
359 assert_eq!(get_flag_token(&flags, b'T'), Some("300"));
360 }
361 other => panic!("expected Get, got {other:?}"),
362 }
363 }
364
365 #[test]
366 fn parse_ms_basic() {
367 match parse_meta_command(b"ms mykey 5 F123 T300") {
368 MetaParseResult::NeedData(
369 MetaCmd::Set {
370 key,
371 datalen,
372 flags,
373 },
374 5,
375 ) => {
376 assert_eq!(key, b"mykey");
377 assert_eq!(datalen, 5);
378 assert_eq!(get_flag_token(&flags, b'F'), Some("123"));
379 assert_eq!(get_flag_token(&flags, b'T'), Some("300"));
380 }
381 other => panic!("expected Set NeedData, got {other:?}"),
382 }
383 }
384
385 #[test]
386 fn parse_md_basic() {
387 match parse_meta_command(b"md mykey") {
388 MetaParseResult::Ok(MetaCmd::Delete { key, flags }) => {
389 assert_eq!(key, b"mykey");
390 assert!(flags.is_empty());
391 }
392 other => panic!("expected Delete, got {other:?}"),
393 }
394 }
395
396 #[test]
397 fn parse_ma_basic() {
398 match parse_meta_command(b"ma counter D10 MI") {
399 MetaParseResult::Ok(MetaCmd::Arithmetic { key, flags }) => {
400 assert_eq!(key, b"counter");
401 assert_eq!(get_flag_token(&flags, b'D'), Some("10"));
402 assert_eq!(get_flag_token(&flags, b'M'), Some("I"));
403 }
404 other => panic!("expected Arithmetic, got {other:?}"),
405 }
406 }
407
408 #[test]
409 fn parse_me_basic() {
410 match parse_meta_command(b"me mykey") {
411 MetaParseResult::Ok(MetaCmd::Debug { key, .. }) => {
412 assert_eq!(key, b"mykey");
413 }
414 other => panic!("expected Debug, got {other:?}"),
415 }
416 }
417
418 #[test]
419 fn flag_validation_mg() {
420 let flags = parse_meta_flags(&["v", "f", "c", "k", "s", "T300", "t", "q", "Oabc"]).unwrap();
421 assert!(validate_mg_flags(&flags).is_ok());
422
423 let bad = parse_meta_flags(&["v", "N30"]).unwrap();
424 assert!(validate_mg_flags(&bad).is_err());
425 }
426
427 #[test]
428 fn flag_validation_ms() {
429 let flags = parse_meta_flags(&["F123", "T300", "C5", "q", "k", "MS"]).unwrap();
430 assert!(validate_ms_flags(&flags).is_ok());
431
432 let bad = parse_meta_flags(&["v"]).unwrap();
433 assert!(validate_ms_flags(&bad).is_err());
434 }
435
436 #[test]
437 fn flag_validation_ms_rejects_unknown_mode() {
438 let bad = parse_meta_flags(&["MX"]).unwrap();
439 assert!(validate_ms_flags(&bad).is_err());
440 }
441
442 #[test]
443 fn flag_validation_ms_rejects_bad_numeric() {
444 let bad = parse_meta_flags(&["Tabc"]).unwrap();
445 assert!(validate_ms_flags(&bad).is_err());
446 }
447
448 #[test]
449 fn flag_validation_md() {
450 let flags = parse_meta_flags(&["C5", "q", "k"]).unwrap();
451 assert!(validate_md_flags(&flags).is_ok());
452
453 let bad = parse_meta_flags(&["I"]).unwrap();
454 assert!(validate_md_flags(&bad).is_err());
455 }
456
457 #[test]
458 fn flag_validation_md_rejects_bad_cas() {
459 let bad = parse_meta_flags(&["Cabc"]).unwrap();
460 assert!(validate_md_flags(&bad).is_err());
461 }
462
463 #[test]
464 fn flag_validation_ma() {
465 let flags = parse_meta_flags(&["D10", "J0", "N300", "q", "v", "c", "MI"]).unwrap();
466 assert!(validate_ma_flags(&flags).is_ok());
467 }
468
469 #[test]
470 fn flag_validation_ma_rejects_unknown_mode() {
471 let bad = parse_meta_flags(&["MX"]).unwrap();
472 assert!(validate_ma_flags(&bad).is_err());
473 }
474
475 #[test]
476 fn flag_validation_ma_rejects_t_flag() {
477 let bad = parse_meta_flags(&["t"]).unwrap();
479 assert!(validate_ma_flags(&bad).is_err());
480 }
481
482 #[test]
483 fn flag_validation_ma_rejects_bad_delta() {
484 let bad = parse_meta_flags(&["Dabc"]).unwrap();
485 assert!(validate_ma_flags(&bad).is_err());
486 }
487
488 #[test]
489 fn flag_validation_mn() {
490 let flags = parse_meta_flags(&["Oabc"]).unwrap();
491 assert!(validate_mn_flags(&flags).is_ok());
492
493 let bad = parse_meta_flags(&["v"]).unwrap();
494 assert!(validate_mn_flags(&bad).is_err());
495 }
496
497 #[test]
498 fn flag_validation_me() {
499 let flags = parse_meta_flags(&["Oabc", "k", "q"]).unwrap();
500 assert!(validate_me_flags(&flags).is_ok());
501
502 let bad = parse_meta_flags(&["v"]).unwrap();
503 assert!(validate_me_flags(&bad).is_err());
504 }
505
506 #[test]
507 fn flag_validation_mg_rejects_bad_ttl() {
508 let bad = parse_meta_flags(&["Tabc"]).unwrap();
509 assert!(validate_mg_flags(&bad).is_err());
510 }
511
512 #[test]
513 fn reject_bare_numeric_flags() {
514 let bad = parse_meta_flags(&["T"]).unwrap();
516 assert!(validate_mg_flags(&bad).is_err());
517
518 let bad = parse_meta_flags(&["F", "MS"]).unwrap();
520 assert!(validate_ms_flags(&bad).is_err());
521
522 let bad = parse_meta_flags(&["C"]).unwrap();
524 assert!(validate_md_flags(&bad).is_err());
525
526 let bad = parse_meta_flags(&["D", "MI"]).unwrap();
528 assert!(validate_ma_flags(&bad).is_err());
529 }
530
531 #[test]
532 fn reject_bare_m_flag() {
533 let bad = parse_meta_flags(&["M"]).unwrap();
535 assert!(validate_ms_flags(&bad).is_err());
536
537 let bad = parse_meta_flags(&["M"]).unwrap();
539 assert!(validate_ma_flags(&bad).is_err());
540 }
541
542 #[test]
543 fn reject_ft_on_ms_append_prepend() {
544 let bad = parse_meta_flags(&["MA", "F123"]).unwrap();
546 assert!(validate_ms_flags(&bad).is_err());
547
548 let bad = parse_meta_flags(&["MP", "T300"]).unwrap();
550 assert!(validate_ms_flags(&bad).is_err());
551
552 let bad = parse_meta_flags(&["MA", "F0", "T0"]).unwrap();
554 assert!(validate_ms_flags(&bad).is_err());
555
556 let ok = parse_meta_flags(&["MS", "F0", "T0"]).unwrap();
558 assert!(validate_ms_flags(&ok).is_ok());
559 }
560
561 #[test]
562 fn ignored_proxy_flags() {
563 let flags = parse_meta_flags(&["v", "P", "L"]).unwrap();
564 assert!(validate_mg_flags(&flags).is_ok());
565 }
566
567 #[test]
568 fn missing_key_error() {
569 match parse_meta_command(b"mg") {
570 MetaParseResult::ClientError(msg) => assert!(!msg.is_empty()),
571 other => panic!("expected error, got {other:?}"),
572 }
573 }
574
575 #[test]
576 fn write_flag_echo_opaque_and_key() {
577 let flags = parse_meta_flags(&["v", "Otoken42", "k"]).unwrap();
578 let mut out = Vec::new();
579 write_meta_flag_echo(&mut out, &flags, b"mykey");
580 assert_eq!(&out, b" Otoken42 kmykey");
581 }
582
583 #[test]
584 fn write_flag_echo_empty() {
585 let flags = parse_meta_flags(&["v"]).unwrap();
586 let mut out = Vec::new();
587 write_meta_flag_echo(&mut out, &flags, b"mykey");
588 assert!(out.is_empty());
589 }
590
591 #[test]
594 fn parse_meta_empty_line() {
595 assert!(matches!(
596 parse_meta_command(b""),
597 MetaParseResult::ClientError(_)
598 ));
599 }
600
601 #[test]
602 fn parse_meta_unknown_prefix() {
603 assert!(matches!(
604 parse_meta_command(b"mx mykey"),
605 MetaParseResult::ClientError(_)
606 ));
607 }
608
609 #[test]
610 fn parse_md_missing_key() {
611 assert!(matches!(
612 parse_meta_command(b"md"),
613 MetaParseResult::ClientError(_)
614 ));
615 }
616
617 #[test]
618 fn parse_ma_missing_key() {
619 assert!(matches!(
620 parse_meta_command(b"ma"),
621 MetaParseResult::ClientError(_)
622 ));
623 }
624
625 #[test]
626 fn parse_me_missing_key() {
627 assert!(matches!(
628 parse_meta_command(b"me"),
629 MetaParseResult::ClientError(_)
630 ));
631 }
632
633 #[test]
634 fn parse_ms_missing_datalen() {
635 assert!(matches!(
636 parse_meta_command(b"ms mykey"),
637 MetaParseResult::ClientError(_)
638 ));
639 }
640
641 #[test]
642 fn parse_ms_bad_datalen() {
643 assert!(matches!(
644 parse_meta_command(b"ms mykey notanum"),
645 MetaParseResult::ClientError(_)
646 ));
647 }
648
649 #[test]
650 fn parse_ms_missing_key_and_datalen() {
651 assert!(matches!(
652 parse_meta_command(b"ms"),
653 MetaParseResult::ClientError(_)
654 ));
655 }
656
657 #[test]
658 fn parse_meta_non_utf8() {
659 assert!(matches!(
660 parse_meta_command(b"mg \xff\xfe"),
661 MetaParseResult::ClientError(_)
662 ));
663 }
664
665 #[test]
668 fn parse_meta_flags_empty_tokens() {
669 let flags = parse_meta_flags(&[]).unwrap();
670 assert!(flags.is_empty());
671 }
672
673 #[test]
674 fn parse_meta_flags_empty_string_tokens() {
675 let flags = parse_meta_flags(&["", ""]).unwrap();
676 assert!(flags.is_empty());
677 }
678
679 #[test]
680 fn parse_meta_flags_multiple() {
681 let flags = parse_meta_flags(&["v", "T300", "q", "Oopaque"]).unwrap();
682 assert_eq!(flags.len(), 4);
683 assert_eq!(flags[0].ch, b'v');
684 assert!(flags[0].token.is_none());
685 assert_eq!(flags[1].ch, b'T');
686 assert_eq!(flags[1].token.as_deref(), Some("300"));
687 }
688
689 #[test]
692 fn has_flag_returns_false_for_absent() {
693 let flags = parse_meta_flags(&["v", "f"]).unwrap();
694 assert!(!has_flag(&flags, b'q'));
695 }
696
697 #[test]
698 fn get_flag_token_returns_none_for_absent() {
699 let flags = parse_meta_flags(&["v"]).unwrap();
700 assert!(get_flag_token(&flags, b'T').is_none());
701 }
702
703 #[test]
704 fn get_flag_token_returns_none_for_bare_flag() {
705 let flags = parse_meta_flags(&["v"]).unwrap();
706 assert!(get_flag_token(&flags, b'v').is_none());
707 }
708
709 #[test]
712 fn write_flag_echo_opaque_only() {
713 let flags = parse_meta_flags(&["Otest"]).unwrap();
714 let mut out = Vec::new();
715 write_meta_flag_echo(&mut out, &flags, b"k");
716 assert_eq!(&out, b" Otest");
717 }
718
719 #[test]
720 fn write_flag_echo_key_only() {
721 let flags = parse_meta_flags(&["k"]).unwrap();
722 let mut out = Vec::new();
723 write_meta_flag_echo(&mut out, &flags, b"mykey");
724 assert_eq!(&out, b" kmykey");
725 }
726
727 #[test]
730 fn ms_mode_e_accepted() {
731 let flags = parse_meta_flags(&["ME"]).unwrap();
732 assert!(validate_ms_flags(&flags).is_ok());
733 }
734
735 #[test]
736 fn ms_mode_r_accepted() {
737 let flags = parse_meta_flags(&["MR"]).unwrap();
738 assert!(validate_ms_flags(&flags).is_ok());
739 }
740
741 #[test]
742 fn ma_mode_d_accepted() {
743 let flags = parse_meta_flags(&["MD"]).unwrap();
744 assert!(validate_ma_flags(&flags).is_ok());
745 }
746}