1#[cfg(not(test))]
10const MAX_LINE_LEN: usize = 2048;
11
12const MAX_KEY_LEN: usize = 250;
14
15#[cfg(not(test))]
17const VERSION_STRING: &str = "VERSION RedCouch 0.1.0";
18
19#[derive(Debug, PartialEq)]
22enum AsciiCmd<'a> {
23 Store {
24 cmd: StoreOp,
25 key: &'a [u8],
26 flags: u32,
27 exptime: u32,
28 bytes: u32,
29 noreply: bool,
30 },
31 Cas {
32 key: &'a [u8],
33 flags: u32,
34 exptime: u32,
35 bytes: u32,
36 cas_unique: u64,
37 noreply: bool,
38 },
39 AppendPrepend {
40 is_prepend: bool,
41 key: &'a [u8],
42 bytes: u32,
43 noreply: bool,
44 },
45 Retrieval {
46 cmd: RetrievalOp,
47 exptime: Option<u32>,
48 keys: Vec<&'a [u8]>,
49 },
50 Delete {
51 key: &'a [u8],
52 noreply: bool,
53 },
54 Counter {
55 is_decr: bool,
56 key: &'a [u8],
57 value: u64,
58 noreply: bool,
59 },
60 Touch {
61 key: &'a [u8],
62 exptime: u32,
63 noreply: bool,
64 },
65 FlushAll {
66 _delay: u32,
67 noreply: bool,
68 },
69 Version,
70 Stats {
71 args: Option<&'a str>,
72 },
73 Verbosity {
74 noreply: bool,
75 },
76 Quit,
77}
78
79#[derive(Debug, PartialEq, Clone, Copy)]
80enum StoreOp {
81 Set,
82 Add,
83 Replace,
84}
85
86#[cfg(not(test))]
89struct StoreParams {
90 op: StoreOp,
91 flags: u32,
92 exptime: u32,
93 cas: u64,
94 noreply: bool,
95}
96
97#[derive(Debug, PartialEq, Clone, Copy)]
98enum RetrievalOp {
99 Get,
100 Gets,
101 Gat,
102 Gats,
103}
104
105#[derive(Debug)]
106enum CmdParseResult<'a> {
107 Ok(AsciiCmd<'a>),
108 UnknownCommand,
109 ClientError(String),
110}
111
112fn find_line_end(buf: &[u8]) -> Option<(usize, usize)> {
117 for i in 0..buf.len() {
118 if buf[i] == b'\n' {
119 if i > 0 && buf[i - 1] == b'\r' {
120 return Some((i - 1, i + 1));
121 }
122 return Some((i, i + 1));
123 }
124 }
125 None
126}
127
128pub(crate) fn validate_key(key: &[u8]) -> Result<(), String> {
131 if key.is_empty() || key.len() > MAX_KEY_LEN {
132 return Err("bad command line format".into());
133 }
134 for &b in key {
135 if b <= 0x20 || b == 0x7F {
136 return Err("bad command line format".into());
137 }
138 }
139 Ok(())
140}
141
142fn parse_u32(s: &str) -> Result<u32, String> {
143 s.parse::<u32>()
144 .map_err(|_| "bad command line format".to_string())
145}
146
147fn parse_u64(s: &str) -> Result<u64, String> {
148 s.parse::<u64>()
149 .map_err(|_| "bad command line format".to_string())
150}
151
152fn parse_store_cmd<'a>(cmd_name: &str, args: &[&'a str], _line: &'a [u8]) -> CmdParseResult<'a> {
156 if args.len() < 4 || args.len() > 5 {
158 return CmdParseResult::ClientError("bad command line format".into());
159 }
160 let key = args[0].as_bytes();
161 if let Err(e) = validate_key(key) {
162 return CmdParseResult::ClientError(e);
163 }
164 let flags = match parse_u32(args[1]) {
165 Ok(v) => v,
166 Err(e) => return CmdParseResult::ClientError(e),
167 };
168 let exptime = match parse_u32(args[2]) {
169 Ok(v) => v,
170 Err(e) => return CmdParseResult::ClientError(e),
171 };
172 let bytes = match parse_u32(args[3]) {
173 Ok(v) => v,
174 Err(e) => return CmdParseResult::ClientError(e),
175 };
176 let noreply = args.len() == 5 && args[4] == "noreply";
177 if args.len() == 5 && args[4] != "noreply" {
178 return CmdParseResult::ClientError("bad command line format".into());
179 }
180 let cmd = match cmd_name {
181 "set" => StoreOp::Set,
182 "add" => StoreOp::Add,
183 "replace" => StoreOp::Replace,
184 _ => unreachable!(),
185 };
186 CmdParseResult::Ok(AsciiCmd::Store {
187 cmd,
188 key,
189 flags,
190 exptime,
191 bytes,
192 noreply,
193 })
194}
195
196fn parse_cas_cmd<'a>(args: &[&'a str], _line: &'a [u8]) -> CmdParseResult<'a> {
198 if args.len() < 5 || args.len() > 6 {
199 return CmdParseResult::ClientError("bad command line format".into());
200 }
201 let key = args[0].as_bytes();
202 if let Err(e) = validate_key(key) {
203 return CmdParseResult::ClientError(e);
204 }
205 let flags = match parse_u32(args[1]) {
206 Ok(v) => v,
207 Err(e) => return CmdParseResult::ClientError(e),
208 };
209 let exptime = match parse_u32(args[2]) {
210 Ok(v) => v,
211 Err(e) => return CmdParseResult::ClientError(e),
212 };
213 let bytes = match parse_u32(args[3]) {
214 Ok(v) => v,
215 Err(e) => return CmdParseResult::ClientError(e),
216 };
217 let cas_unique = match parse_u64(args[4]) {
218 Ok(v) => v,
219 Err(e) => return CmdParseResult::ClientError(e),
220 };
221 let noreply = args.len() == 6 && args[5] == "noreply";
222 if args.len() == 6 && args[5] != "noreply" {
223 return CmdParseResult::ClientError("bad command line format".into());
224 }
225 CmdParseResult::Ok(AsciiCmd::Cas {
226 key,
227 flags,
228 exptime,
229 bytes,
230 cas_unique,
231 noreply,
232 })
233}
234
235fn parse_append_prepend_cmd<'a>(
237 cmd_name: &str,
238 args: &[&'a str],
239 _line: &'a [u8],
240) -> CmdParseResult<'a> {
241 if args.len() < 2 || args.len() > 3 {
242 return CmdParseResult::ClientError("bad command line format".into());
243 }
244 let key = args[0].as_bytes();
245 if let Err(e) = validate_key(key) {
246 return CmdParseResult::ClientError(e);
247 }
248 let bytes = match parse_u32(args[1]) {
249 Ok(v) => v,
250 Err(e) => return CmdParseResult::ClientError(e),
251 };
252 let noreply = args.len() == 3 && args[2] == "noreply";
253 if args.len() == 3 && args[2] != "noreply" {
254 return CmdParseResult::ClientError("bad command line format".into());
255 }
256 let is_prepend = cmd_name == "prepend";
257 CmdParseResult::Ok(AsciiCmd::AppendPrepend {
258 is_prepend,
259 key,
260 bytes,
261 noreply,
262 })
263}
264
265fn parse_retrieval_cmd<'a>(
267 cmd_name: &str,
268 args: &[&'a str],
269 _line: &'a [u8],
270) -> CmdParseResult<'a> {
271 if args.is_empty() {
272 return CmdParseResult::ClientError("bad command line format".into());
273 }
274 let mut keys = Vec::with_capacity(args.len());
275 for &k in args {
276 let kb = k.as_bytes();
277 if let Err(e) = validate_key(kb) {
278 return CmdParseResult::ClientError(e);
279 }
280 keys.push(kb);
281 }
282 let cmd = if cmd_name == "gets" {
283 RetrievalOp::Gets
284 } else {
285 RetrievalOp::Get
286 };
287 CmdParseResult::Ok(AsciiCmd::Retrieval {
288 cmd,
289 exptime: None,
290 keys,
291 })
292}
293
294fn parse_gat_cmd<'a>(cmd_name: &str, args: &[&'a str], _line: &'a [u8]) -> CmdParseResult<'a> {
296 if args.len() < 2 {
297 return CmdParseResult::ClientError("bad command line format".into());
298 }
299 let exptime = match parse_u32(args[0]) {
300 Ok(v) => v,
301 Err(e) => return CmdParseResult::ClientError(e),
302 };
303 let mut keys = Vec::with_capacity(args.len() - 1);
304 for &k in &args[1..] {
305 let kb = k.as_bytes();
306 if let Err(e) = validate_key(kb) {
307 return CmdParseResult::ClientError(e);
308 }
309 keys.push(kb);
310 }
311 let cmd = if cmd_name == "gats" {
312 RetrievalOp::Gats
313 } else {
314 RetrievalOp::Gat
315 };
316 CmdParseResult::Ok(AsciiCmd::Retrieval {
317 cmd,
318 exptime: Some(exptime),
319 keys,
320 })
321}
322
323fn parse_delete_cmd<'a>(args: &[&'a str], _line: &'a [u8]) -> CmdParseResult<'a> {
325 if args.is_empty() || args.len() > 2 {
326 return CmdParseResult::ClientError("bad command line format".into());
327 }
328 let key = args[0].as_bytes();
329 if let Err(e) = validate_key(key) {
330 return CmdParseResult::ClientError(e);
331 }
332 let noreply = args.len() == 2 && args[1] == "noreply";
333 if args.len() == 2 && args[1] != "noreply" {
334 return CmdParseResult::ClientError("bad command line format".into());
335 }
336 CmdParseResult::Ok(AsciiCmd::Delete { key, noreply })
337}
338
339fn parse_counter_cmd<'a>(cmd_name: &str, args: &[&'a str], _line: &'a [u8]) -> CmdParseResult<'a> {
341 if args.len() < 2 || args.len() > 3 {
342 return CmdParseResult::ClientError("bad command line format".into());
343 }
344 let key = args[0].as_bytes();
345 if let Err(e) = validate_key(key) {
346 return CmdParseResult::ClientError(e);
347 }
348 let value = match parse_u64(args[1]) {
349 Ok(v) => v,
350 Err(e) => return CmdParseResult::ClientError(e),
351 };
352 let noreply = args.len() == 3 && args[2] == "noreply";
353 if args.len() == 3 && args[2] != "noreply" {
354 return CmdParseResult::ClientError("bad command line format".into());
355 }
356 let is_decr = cmd_name == "decr";
357 CmdParseResult::Ok(AsciiCmd::Counter {
358 is_decr,
359 key,
360 value,
361 noreply,
362 })
363}
364
365fn parse_touch_cmd<'a>(args: &[&'a str], _line: &'a [u8]) -> CmdParseResult<'a> {
367 if args.len() < 2 || args.len() > 3 {
368 return CmdParseResult::ClientError("bad command line format".into());
369 }
370 let key = args[0].as_bytes();
371 if let Err(e) = validate_key(key) {
372 return CmdParseResult::ClientError(e);
373 }
374 let exptime = match parse_u32(args[1]) {
375 Ok(v) => v,
376 Err(e) => return CmdParseResult::ClientError(e),
377 };
378 let noreply = args.len() == 3 && args[2] == "noreply";
379 if args.len() == 3 && args[2] != "noreply" {
380 return CmdParseResult::ClientError("bad command line format".into());
381 }
382 CmdParseResult::Ok(AsciiCmd::Touch {
383 key,
384 exptime,
385 noreply,
386 })
387}
388
389fn parse_flush_cmd<'a>(args: &[&'a str]) -> CmdParseResult<'a> {
391 let mut delay = 0u32;
392 let mut noreply = false;
393 for &a in args {
394 if a == "noreply" {
395 noreply = true;
396 } else if let Ok(d) = a.parse::<u32>() {
397 delay = d;
398 } else {
399 return CmdParseResult::ClientError("bad command line format".into());
400 }
401 }
402 CmdParseResult::Ok(AsciiCmd::FlushAll {
403 _delay: delay,
404 noreply,
405 })
406}
407
408fn parse_command_line(line: &[u8]) -> CmdParseResult<'_> {
410 let line_str = match std::str::from_utf8(line) {
411 Ok(s) => s,
412 Err(_) => return CmdParseResult::ClientError("bad command line format".into()),
413 };
414 let tokens: Vec<&str> = line_str.split_whitespace().collect();
415 if tokens.is_empty() {
416 return CmdParseResult::UnknownCommand;
417 }
418 match tokens[0] {
419 "set" | "add" | "replace" => parse_store_cmd(tokens[0], &tokens[1..], line),
420 "cas" => parse_cas_cmd(&tokens[1..], line),
421 "append" | "prepend" => parse_append_prepend_cmd(tokens[0], &tokens[1..], line),
422 "get" | "gets" => parse_retrieval_cmd(tokens[0], &tokens[1..], line),
423 "gat" | "gats" => parse_gat_cmd(tokens[0], &tokens[1..], line),
424 "delete" => parse_delete_cmd(&tokens[1..], line),
425 "incr" | "decr" => parse_counter_cmd(tokens[0], &tokens[1..], line),
426 "touch" => parse_touch_cmd(&tokens[1..], line),
427 "flush_all" => parse_flush_cmd(&tokens[1..]),
428 "version" => CmdParseResult::Ok(AsciiCmd::Version),
429 "stats" => {
430 let args_str = if tokens.len() > 1 {
431 let cmd_end = line_str.find(char::is_whitespace).unwrap_or(line_str.len());
433 let rest = line_str[cmd_end..].trim();
434 if rest.is_empty() { None } else { Some(rest) }
435 } else {
436 None
437 };
438 CmdParseResult::Ok(AsciiCmd::Stats { args: args_str })
439 }
440 "verbosity" => {
441 let noreply = tokens.last().map(|t| *t == "noreply").unwrap_or(false);
442 CmdParseResult::Ok(AsciiCmd::Verbosity { noreply })
443 }
444 "quit" => CmdParseResult::Ok(AsciiCmd::Quit),
445 _ => CmdParseResult::UnknownCommand,
446 }
447}
448
449#[cfg(not(test))]
454use crate::meta::{
455 MetaCmd, MetaFlag, MetaParseResult, get_flag_token, has_flag, parse_meta_command,
456 validate_ma_flags, validate_md_flags, validate_me_flags, validate_mg_flags, validate_mn_flags,
457 validate_ms_flags, write_meta_flag_echo,
458};
459#[cfg(not(test))]
460use crate::protocol::MAX_BODY_LEN;
461#[cfg(not(test))]
462use crate::{
463 Br, BridgeErr, CAS_COUNTER_KEY, MAX_CONNECTIONS, SCRIPT_APPEND, SCRIPT_COUNT_ITEMS,
464 SCRIPT_COUNTER, SCRIPT_DELETE, SCRIPT_FLUSH, SCRIPT_GAT, SCRIPT_GET, SCRIPT_META_GET,
465 SCRIPT_PREPEND, SCRIPT_STORE, SCRIPT_TOUCH, STARTUP_INSTANT, STAT_AUTH_CMDS, STAT_AUTH_ERRORS,
466 STAT_CAS_BADVAL, STAT_CAS_HITS, STAT_CAS_MISSES, STAT_CMD_FLUSH, STAT_CMD_GET, STAT_CMD_SET,
467 STAT_CMD_TOUCH, STAT_CURR_CONNECTIONS, STAT_DECR_HITS, STAT_DECR_MISSES, STAT_DELETE_HITS,
468 STAT_DELETE_MISSES, STAT_GET_HITS, STAT_GET_MISSES, STAT_INCR_HITS, STAT_INCR_MISSES,
469 STAT_REJECTED_CONNECTIONS, STAT_TOTAL_CONNECTIONS, eval_int, eval_lua, eval_str, hex_decode,
470 is_redis_error, make_redis_key, with_ctx,
471};
472#[cfg(not(test))]
473use bytes::{Buf, BytesMut};
474#[cfg(not(test))]
475use redis_module::RedisValue;
476#[cfg(not(test))]
477use std::io::Read;
478#[cfg(not(test))]
479use std::net::TcpStream;
480#[cfg(not(test))]
481use std::sync::atomic::Ordering;
482
483#[cfg(not(test))]
486pub(crate) fn handle_ascii_conn(sock: &mut TcpStream, buf: &mut BytesMut) -> Br<()> {
487 let mut out = Vec::with_capacity(4096);
488
489 loop {
490 if let Some((line_end, skip)) = find_line_end(buf) {
492 if line_end > MAX_LINE_LEN {
493 out.extend_from_slice(b"CLIENT_ERROR line too long\r\n");
494 buf.advance(skip);
495 flush_out(sock, &mut out)?;
496 continue;
497 }
498 let line_bytes = buf[..line_end].to_vec();
501 buf.advance(skip);
502 if line_bytes.is_empty() || line_bytes.iter().all(|b| b.is_ascii_whitespace()) {
504 continue;
505 }
506
507 if is_meta_command(&line_bytes) {
512 match parse_meta_command(&line_bytes) {
513 MetaParseResult::Ok(cmd) => {
514 dispatch_meta_cmd(cmd, None, &mut out)?;
515 }
516 MetaParseResult::NeedData(cmd, datalen) => {
517 if datalen > MAX_BODY_LEN {
518 out.extend_from_slice(b"CLIENT_ERROR object too large for cache\r\n");
519 drain_data_block(sock, buf, datalen)?;
520 } else {
521 match read_data_block(sock, buf, datalen) {
522 Ok(data) => {
523 dispatch_meta_cmd(cmd, Some(&data), &mut out)?;
524 }
525 Err(_) => {
526 out.extend_from_slice(b"CLIENT_ERROR bad data chunk\r\n");
527 }
528 }
529 }
530 }
531 MetaParseResult::ClientError(msg) => {
532 out.extend_from_slice(b"CLIENT_ERROR ");
533 out.extend_from_slice(msg.as_bytes());
534 out.extend_from_slice(b"\r\n");
535 }
536 }
537 flush_out(sock, &mut out)?;
538 continue;
539 }
540
541 let cmd = parse_command_line(&line_bytes);
542
543 match cmd {
544 CmdParseResult::Ok(ascii_cmd) => {
545 if needs_data_block(&ascii_cmd) {
546 let byte_count = data_block_len(&ascii_cmd);
547 if byte_count > MAX_BODY_LEN {
548 let nr = is_noreply(&ascii_cmd);
549 if !nr {
550 out.extend_from_slice(
551 b"CLIENT_ERROR object too large for cache\r\n",
552 );
553 }
554 drain_data_block(sock, buf, byte_count)?;
555 flush_out(sock, &mut out)?;
556 continue;
557 }
558 let data = match read_data_block(sock, buf, byte_count) {
559 Ok(d) => d,
560 Err(_) => {
561 let nr = is_noreply(&ascii_cmd);
562 if !nr {
563 out.extend_from_slice(b"CLIENT_ERROR bad data chunk\r\n");
564 }
565 flush_out(sock, &mut out)?;
566 continue;
567 }
568 };
569 dispatch_cmd(ascii_cmd, Some(&data), &mut out)?;
570 } else {
571 dispatch_cmd(ascii_cmd, None, &mut out)?;
572 }
573 }
574 CmdParseResult::UnknownCommand => {
575 out.extend_from_slice(b"ERROR\r\n");
576 }
577 CmdParseResult::ClientError(msg) => {
578 out.extend_from_slice(b"CLIENT_ERROR ");
579 out.extend_from_slice(msg.as_bytes());
580 out.extend_from_slice(b"\r\n");
581 }
582 }
583
584 flush_out(sock, &mut out)?;
585 continue;
586 }
587
588 if buf.len() > MAX_LINE_LEN + 2 {
590 out.extend_from_slice(b"CLIENT_ERROR line too long\r\n");
592 flush_out(sock, &mut out)?;
593 return Ok(());
594 }
595
596 let mut tmp = [0u8; 16384];
598 match sock.read(&mut tmp) {
599 Ok(0) => return Ok(()),
600 Ok(n) => buf.extend_from_slice(&tmp[..n]),
601 Err(ref e)
602 if e.kind() == std::io::ErrorKind::WouldBlock
603 || e.kind() == std::io::ErrorKind::TimedOut =>
604 {
605 return Err(std::io::Error::from(std::io::ErrorKind::TimedOut).into());
606 }
607 Err(e) => return Err(e.into()),
608 }
609 }
610}
611
612#[cfg(not(test))]
615fn flush_out(sock: &mut TcpStream, out: &mut Vec<u8>) -> Br<()> {
616 if !out.is_empty() {
617 use std::io::Write;
618 sock.write_all(out)?;
619 out.clear();
620 }
621 Ok(())
622}
623
624fn is_meta_command(line: &[u8]) -> bool {
628 if line.len() < 2 {
629 return false;
630 }
631 let prefix = &line[..2];
632 let is_meta_prefix = prefix == b"mg"
633 || prefix == b"ms"
634 || prefix == b"md"
635 || prefix == b"ma"
636 || prefix == b"mn"
637 || prefix == b"me";
638 if !is_meta_prefix {
639 return false;
640 }
641 line.len() == 2 || line[2] == b' ' || line[2] == b'\t'
643}
644
645fn needs_data_block(cmd: &AsciiCmd<'_>) -> bool {
647 matches!(
648 cmd,
649 AsciiCmd::Store { .. } | AsciiCmd::Cas { .. } | AsciiCmd::AppendPrepend { .. }
650 )
651}
652
653fn data_block_len(cmd: &AsciiCmd<'_>) -> u32 {
655 match cmd {
656 AsciiCmd::Store { bytes, .. } => *bytes,
657 AsciiCmd::Cas { bytes, .. } => *bytes,
658 AsciiCmd::AppendPrepend { bytes, .. } => *bytes,
659 _ => 0,
660 }
661}
662
663fn is_noreply(cmd: &AsciiCmd<'_>) -> bool {
665 match cmd {
666 AsciiCmd::Store { noreply, .. } => *noreply,
667 AsciiCmd::Cas { noreply, .. } => *noreply,
668 AsciiCmd::AppendPrepend { noreply, .. } => *noreply,
669 AsciiCmd::Delete { noreply, .. } => *noreply,
670 AsciiCmd::Counter { noreply, .. } => *noreply,
671 AsciiCmd::Touch { noreply, .. } => *noreply,
672 AsciiCmd::FlushAll { noreply, .. } => *noreply,
673 AsciiCmd::Verbosity { noreply, .. } => *noreply,
674 _ => false,
675 }
676}
677
678#[cfg(not(test))]
681fn read_data_block(
682 sock: &mut TcpStream,
683 buf: &mut BytesMut,
684 byte_count: u32,
685) -> Result<Vec<u8>, ()> {
686 let need = byte_count as usize + 2; while buf.len() < need {
689 let mut tmp = [0u8; 16384];
690 match sock.read(&mut tmp) {
691 Ok(0) => return Err(()),
692 Ok(n) => buf.extend_from_slice(&tmp[..n]),
693 Err(_) => return Err(()),
694 }
695 }
696 let data = buf[..byte_count as usize].to_vec();
697 let trail = &buf[byte_count as usize..byte_count as usize + 2];
699 let valid_terminator = trail == b"\r\n" || (trail[0] == b'\n'); if !valid_terminator {
701 buf.advance(need);
702 return Err(());
703 }
704 let skip = if trail[0] == b'\r' { 2 } else { 1 };
705 buf.advance(byte_count as usize + skip);
706 Ok(data)
707}
708
709#[cfg(not(test))]
711fn drain_data_block(sock: &mut TcpStream, buf: &mut BytesMut, byte_count: u32) -> Br<()> {
712 let need = byte_count as usize + 2;
713 while buf.len() < need {
714 let mut tmp = [0u8; 16384];
715 match sock.read(&mut tmp) {
716 Ok(0) => return Ok(()),
717 Ok(n) => buf.extend_from_slice(&tmp[..n]),
718 Err(e) => return Err(e.into()),
719 }
720 }
721 buf.advance(need);
722 Ok(())
723}
724
725#[cfg(not(test))]
728fn dispatch_cmd(cmd: AsciiCmd<'_>, data: Option<&[u8]>, out: &mut Vec<u8>) -> Br<()> {
729 match cmd {
730 AsciiCmd::Store {
731 cmd,
732 key,
733 flags,
734 exptime,
735 noreply,
736 ..
737 } => ascii_store(
738 &StoreParams {
739 op: cmd,
740 flags,
741 exptime,
742 cas: 0,
743 noreply,
744 },
745 key,
746 data.unwrap_or(&[]),
747 out,
748 ),
749 AsciiCmd::Cas {
750 key,
751 flags,
752 exptime,
753 cas_unique,
754 noreply,
755 ..
756 } => ascii_store(
757 &StoreParams {
758 op: StoreOp::Set,
759 flags,
760 exptime,
761 cas: cas_unique,
762 noreply,
763 },
764 key,
765 data.unwrap_or(&[]),
766 out,
767 ),
768 AsciiCmd::AppendPrepend {
769 is_prepend,
770 key,
771 noreply,
772 ..
773 } => ascii_append_prepend(is_prepend, key, data.unwrap_or(&[]), noreply, out),
774 AsciiCmd::Retrieval { cmd, exptime, keys } => ascii_retrieval(cmd, exptime, &keys, out),
775 AsciiCmd::Delete { key, noreply } => ascii_delete(key, noreply, out),
776 AsciiCmd::Counter {
777 is_decr,
778 key,
779 value,
780 noreply,
781 } => ascii_counter(is_decr, key, value, noreply, out),
782 AsciiCmd::Touch {
783 key,
784 exptime,
785 noreply,
786 } => ascii_touch(key, exptime, noreply, out),
787 AsciiCmd::FlushAll { noreply, .. } => ascii_flush(noreply, out),
788 AsciiCmd::Version => {
789 out.extend_from_slice(VERSION_STRING.as_bytes());
790 out.extend_from_slice(b"\r\n");
791 Ok(())
792 }
793 AsciiCmd::Stats { args } => ascii_stats(args, out),
794 AsciiCmd::Verbosity { noreply } => {
795 if !noreply {
796 out.extend_from_slice(b"OK\r\n");
797 }
798 Ok(())
799 }
800 AsciiCmd::Quit => {
801 Err(BridgeErr::Io(std::io::Error::new(
803 std::io::ErrorKind::ConnectionAborted,
804 "quit",
805 )))
806 }
807 }
808}
809
810#[cfg(not(test))]
814fn dispatch_meta_cmd(cmd: MetaCmd<'_>, data: Option<&[u8]>, out: &mut Vec<u8>) -> Br<()> {
815 match cmd {
816 MetaCmd::Noop { flags } => {
817 if let Err(e) = validate_mn_flags(&flags) {
818 out.extend_from_slice(b"CLIENT_ERROR ");
819 out.extend_from_slice(e.as_bytes());
820 out.extend_from_slice(b"\r\n");
821 return Ok(());
822 }
823 meta_noop(&flags, out)
824 }
825 MetaCmd::Get { key, flags } => {
826 if let Err(e) = validate_mg_flags(&flags) {
827 out.extend_from_slice(b"CLIENT_ERROR ");
828 out.extend_from_slice(e.as_bytes());
829 out.extend_from_slice(b"\r\n");
830 return Ok(());
831 }
832 meta_get(key, &flags, out)
833 }
834 MetaCmd::Set { key, flags, .. } => {
835 if let Err(e) = validate_ms_flags(&flags) {
836 out.extend_from_slice(b"CLIENT_ERROR ");
837 out.extend_from_slice(e.as_bytes());
838 out.extend_from_slice(b"\r\n");
839 return Ok(());
840 }
841 meta_set(key, data.unwrap_or(&[]), &flags, out)
842 }
843 MetaCmd::Delete { key, flags } => {
844 if let Err(e) = validate_md_flags(&flags) {
845 out.extend_from_slice(b"CLIENT_ERROR ");
846 out.extend_from_slice(e.as_bytes());
847 out.extend_from_slice(b"\r\n");
848 return Ok(());
849 }
850 meta_delete(key, &flags, out)
851 }
852 MetaCmd::Arithmetic { key, flags } => {
853 if let Err(e) = validate_ma_flags(&flags) {
854 out.extend_from_slice(b"CLIENT_ERROR ");
855 out.extend_from_slice(e.as_bytes());
856 out.extend_from_slice(b"\r\n");
857 return Ok(());
858 }
859 meta_arithmetic(key, &flags, out)
860 }
861 MetaCmd::Debug { key, flags } => {
862 if let Err(e) = validate_me_flags(&flags) {
863 out.extend_from_slice(b"CLIENT_ERROR ");
864 out.extend_from_slice(e.as_bytes());
865 out.extend_from_slice(b"\r\n");
866 return Ok(());
867 }
868 let quiet = has_flag(&flags, b'q');
870 if !quiet {
871 out.extend_from_slice(b"EN");
872 write_meta_flag_echo(out, &flags, key);
873 out.extend_from_slice(b"\r\n");
874 }
875 Ok(())
876 }
877 }
878}
879
880#[cfg(not(test))]
884fn ascii_store(params: &StoreParams, key: &[u8], value: &[u8], out: &mut Vec<u8>) -> Br<()> {
885 STAT_CMD_SET.fetch_add(1, Ordering::Relaxed);
886 let rk = make_redis_key(key);
887 let op_name = match params.op {
888 StoreOp::Set => "set",
889 StoreOp::Add => "add",
890 StoreOp::Replace => "replace",
891 };
892 let cas_str = params.cas.to_string();
893 let flags_str = params.flags.to_string();
894 let expiry_str = params.exptime.to_string();
895
896 let reply = with_ctx(|ctx| {
897 let keys_and_args: &[&[u8]] = &[
898 b"2",
899 rk.as_slice(),
900 CAS_COUNTER_KEY.as_bytes(),
901 op_name.as_bytes(),
902 value,
903 flags_str.as_bytes(),
904 cas_str.as_bytes(),
905 expiry_str.as_bytes(),
906 ];
907 eval_lua(ctx, &SCRIPT_STORE, keys_and_args)
908 })
909 .map_err(|e| BridgeErr::Redis(e.to_string()))?;
910
911 let noreply = params.noreply;
912
913 if is_redis_error(&reply) {
914 if !noreply {
915 out.extend_from_slice(b"SERVER_ERROR internal\r\n");
916 }
917 return Ok(());
918 }
919
920 let (status_code, _new_cas) = match &reply {
921 RedisValue::Array(arr) if arr.len() >= 2 => {
922 let st = eval_int(&arr[0]);
923 let cas_s = eval_str(&arr[1]);
924 let cas_val: u64 = cas_s.parse().unwrap_or(0);
925 (st, cas_val)
926 }
927 _ => {
928 if !noreply {
929 out.extend_from_slice(b"SERVER_ERROR internal\r\n");
930 }
931 return Ok(());
932 }
933 };
934
935 let is_cas_op = params.cas != 0;
936 match status_code {
937 0 => {
938 if is_cas_op {
939 STAT_CAS_HITS.fetch_add(1, Ordering::Relaxed);
940 }
941 if !noreply {
942 out.extend_from_slice(b"STORED\r\n");
943 }
944 }
945 -1 => {
946 if is_cas_op {
948 STAT_CAS_MISSES.fetch_add(1, Ordering::Relaxed);
949 }
950 if !noreply {
951 if is_cas_op {
952 out.extend_from_slice(b"NOT_FOUND\r\n");
953 } else {
954 out.extend_from_slice(b"NOT_STORED\r\n");
955 }
956 }
957 }
958 -2 => {
959 if is_cas_op {
961 STAT_CAS_BADVAL.fetch_add(1, Ordering::Relaxed);
962 }
963 if !noreply {
964 if is_cas_op {
965 out.extend_from_slice(b"EXISTS\r\n");
966 } else {
967 out.extend_from_slice(b"NOT_STORED\r\n");
968 }
969 }
970 }
971 _ => {
972 if !noreply {
973 out.extend_from_slice(b"SERVER_ERROR internal\r\n");
974 }
975 }
976 }
977 Ok(())
978}
979
980#[cfg(not(test))]
982fn ascii_retrieval(
983 cmd: RetrievalOp,
984 exptime: Option<u32>,
985 keys: &[&[u8]],
986 out: &mut Vec<u8>,
987) -> Br<()> {
988 let include_cas = matches!(cmd, RetrievalOp::Gets | RetrievalOp::Gats);
989 let is_gat = matches!(cmd, RetrievalOp::Gat | RetrievalOp::Gats);
990
991 for &key in keys {
992 STAT_CMD_GET.fetch_add(1, Ordering::Relaxed);
993 if is_gat {
994 STAT_CMD_TOUCH.fetch_add(1, Ordering::Relaxed);
995 }
996
997 let rk = make_redis_key(key);
998
999 let reply = if is_gat {
1000 let exp_str = exptime.unwrap_or(0).to_string();
1001 with_ctx(|ctx| {
1002 let keys_and_args: &[&[u8]] = &[
1003 b"2",
1004 rk.as_slice(),
1005 CAS_COUNTER_KEY.as_bytes(),
1006 exp_str.as_bytes(),
1007 ];
1008 eval_lua(ctx, &SCRIPT_GAT, keys_and_args)
1009 })
1010 .map_err(|e| BridgeErr::Redis(e.to_string()))?
1011 } else {
1012 with_ctx(|ctx| {
1013 let keys_and_args: &[&[u8]] = &[b"1", rk.as_slice()];
1014 eval_lua(ctx, &SCRIPT_GET, keys_and_args)
1015 })
1016 .map_err(|e| BridgeErr::Redis(e.to_string()))?
1017 };
1018
1019 if is_redis_error(&reply) {
1020 continue;
1021 }
1022
1023 let (status, hex_val, flags_str, cas_str) = match &reply {
1024 RedisValue::Array(arr) if arr.len() >= 4 => (
1025 eval_int(&arr[0]),
1026 eval_str(&arr[1]),
1027 eval_str(&arr[2]),
1028 eval_str(&arr[3]),
1029 ),
1030 _ => continue,
1031 };
1032
1033 if status == -1 {
1034 STAT_GET_MISSES.fetch_add(1, Ordering::Relaxed);
1035 continue;
1036 }
1037 STAT_GET_HITS.fetch_add(1, Ordering::Relaxed);
1038
1039 let value = hex_decode(&hex_val);
1040 out.extend_from_slice(b"VALUE ");
1042 out.extend_from_slice(key);
1043 out.push(b' ');
1044 out.extend_from_slice(flags_str.as_bytes());
1045 out.push(b' ');
1046 out.extend_from_slice(value.len().to_string().as_bytes());
1047 if include_cas {
1048 out.push(b' ');
1049 out.extend_from_slice(cas_str.as_bytes());
1050 }
1051 out.extend_from_slice(b"\r\n");
1052 out.extend_from_slice(&value);
1053 out.extend_from_slice(b"\r\n");
1054 }
1055 out.extend_from_slice(b"END\r\n");
1056 Ok(())
1057}
1058
1059#[cfg(not(test))]
1061fn ascii_delete(key: &[u8], noreply: bool, out: &mut Vec<u8>) -> Br<()> {
1062 let rk = make_redis_key(key);
1063 let reply = with_ctx(|ctx| ctx.call("DEL", &[rk.as_slice()]))
1066 .map_err(|e| BridgeErr::Redis(e.to_string()))?;
1067
1068 if is_redis_error(&reply) {
1069 if !noreply {
1070 out.extend_from_slice(b"SERVER_ERROR internal\r\n");
1071 }
1072 return Ok(());
1073 }
1074
1075 let deleted_count = match &reply {
1077 RedisValue::Integer(n) => *n,
1078 _ => {
1079 if !noreply {
1080 out.extend_from_slice(b"SERVER_ERROR internal\r\n");
1081 }
1082 return Ok(());
1083 }
1084 };
1085
1086 if deleted_count > 0 {
1087 STAT_DELETE_HITS.fetch_add(1, Ordering::Relaxed);
1088 if !noreply {
1089 out.extend_from_slice(b"DELETED\r\n");
1090 }
1091 } else {
1092 STAT_DELETE_MISSES.fetch_add(1, Ordering::Relaxed);
1093 if !noreply {
1094 out.extend_from_slice(b"NOT_FOUND\r\n");
1095 }
1096 }
1097 Ok(())
1098}
1099
1100#[cfg(not(test))]
1102fn ascii_counter(
1103 is_decr: bool,
1104 key: &[u8],
1105 delta: u64,
1106 noreply: bool,
1107 out: &mut Vec<u8>,
1108) -> Br<()> {
1109 let rk = make_redis_key(key);
1110 let delta_str = delta.to_string();
1111 let is_decr_str = if is_decr { "1" } else { "0" };
1112 let initial_str = "0";
1115 let expiry_str = "4294967295";
1116
1117 let reply = with_ctx(|ctx| {
1118 let keys_and_args: &[&[u8]] = &[
1119 b"2",
1120 rk.as_slice(),
1121 CAS_COUNTER_KEY.as_bytes(),
1122 delta_str.as_bytes(),
1123 is_decr_str.as_bytes(),
1124 initial_str.as_bytes(),
1125 expiry_str.as_bytes(),
1126 ];
1127 eval_lua(ctx, &SCRIPT_COUNTER, keys_and_args)
1128 })
1129 .map_err(|e| BridgeErr::Redis(e.to_string()))?;
1130
1131 if is_redis_error(&reply) {
1132 if !noreply {
1133 out.extend_from_slice(b"SERVER_ERROR internal\r\n");
1134 }
1135 return Ok(());
1136 }
1137
1138 let (status, value_str, _cas) = match &reply {
1139 RedisValue::Array(arr) if arr.len() >= 3 => {
1140 (eval_int(&arr[0]), eval_str(&arr[1]), eval_str(&arr[2]))
1141 }
1142 _ => {
1143 if !noreply {
1144 out.extend_from_slice(b"SERVER_ERROR internal\r\n");
1145 }
1146 return Ok(());
1147 }
1148 };
1149
1150 match status {
1151 0 => {
1152 if is_decr {
1153 STAT_DECR_HITS.fetch_add(1, Ordering::Relaxed);
1154 } else {
1155 STAT_INCR_HITS.fetch_add(1, Ordering::Relaxed);
1156 }
1157 if !noreply {
1158 out.extend_from_slice(value_str.as_bytes());
1159 out.extend_from_slice(b"\r\n");
1160 }
1161 }
1162 -1 => {
1163 if is_decr {
1164 STAT_DECR_MISSES.fetch_add(1, Ordering::Relaxed);
1165 } else {
1166 STAT_INCR_MISSES.fetch_add(1, Ordering::Relaxed);
1167 }
1168 if !noreply {
1169 out.extend_from_slice(b"NOT_FOUND\r\n");
1170 }
1171 }
1172 -3 => {
1173 if !noreply {
1175 out.extend_from_slice(
1176 b"CLIENT_ERROR cannot increment or decrement non-numeric value\r\n",
1177 );
1178 }
1179 }
1180 _ => {
1181 if !noreply {
1182 out.extend_from_slice(b"SERVER_ERROR internal\r\n");
1183 }
1184 }
1185 }
1186 Ok(())
1187}
1188
1189#[cfg(not(test))]
1191fn ascii_touch(key: &[u8], exptime: u32, noreply: bool, out: &mut Vec<u8>) -> Br<()> {
1192 STAT_CMD_TOUCH.fetch_add(1, Ordering::Relaxed);
1193 let rk = make_redis_key(key);
1194 let exp_str = exptime.to_string();
1195
1196 let reply = with_ctx(|ctx| {
1197 let keys_and_args: &[&[u8]] = &[
1198 b"2",
1199 rk.as_slice(),
1200 CAS_COUNTER_KEY.as_bytes(),
1201 exp_str.as_bytes(),
1202 ];
1203 eval_lua(ctx, &SCRIPT_TOUCH, keys_and_args)
1204 })
1205 .map_err(|e| BridgeErr::Redis(e.to_string()))?;
1206
1207 if is_redis_error(&reply) {
1208 if !noreply {
1209 out.extend_from_slice(b"SERVER_ERROR internal\r\n");
1210 }
1211 return Ok(());
1212 }
1213
1214 let status = match &reply {
1215 RedisValue::Array(arr) if !arr.is_empty() => eval_int(&arr[0]),
1216 _ => {
1217 if !noreply {
1218 out.extend_from_slice(b"SERVER_ERROR internal\r\n");
1219 }
1220 return Ok(());
1221 }
1222 };
1223
1224 match status {
1225 0 => {
1226 if !noreply {
1227 out.extend_from_slice(b"TOUCHED\r\n");
1228 }
1229 }
1230 _ => {
1231 if !noreply {
1232 out.extend_from_slice(b"NOT_FOUND\r\n");
1233 }
1234 }
1235 }
1236 Ok(())
1237}
1238
1239#[cfg(not(test))]
1241fn ascii_append_prepend(
1242 is_prepend: bool,
1243 key: &[u8],
1244 value: &[u8],
1245 noreply: bool,
1246 out: &mut Vec<u8>,
1247) -> Br<()> {
1248 STAT_CMD_SET.fetch_add(1, Ordering::Relaxed);
1249 let rk = make_redis_key(key);
1250 let script = if is_prepend {
1251 &SCRIPT_PREPEND
1252 } else {
1253 &SCRIPT_APPEND
1254 };
1255
1256 let reply = with_ctx(|ctx| {
1257 let keys_and_args: &[&[u8]] =
1258 &[b"2", rk.as_slice(), CAS_COUNTER_KEY.as_bytes(), value, b"0"];
1259 eval_lua(ctx, script, keys_and_args)
1260 })
1261 .map_err(|e| BridgeErr::Redis(e.to_string()))?;
1262
1263 if is_redis_error(&reply) {
1264 if !noreply {
1265 out.extend_from_slice(b"SERVER_ERROR internal\r\n");
1266 }
1267 return Ok(());
1268 }
1269
1270 let status = match &reply {
1271 RedisValue::Array(arr) if !arr.is_empty() => eval_int(&arr[0]),
1272 _ => {
1273 if !noreply {
1274 out.extend_from_slice(b"SERVER_ERROR internal\r\n");
1275 }
1276 return Ok(());
1277 }
1278 };
1279
1280 match status {
1281 0 => {
1282 if !noreply {
1283 out.extend_from_slice(b"STORED\r\n");
1284 }
1285 }
1286 -5 => {
1287 if !noreply {
1289 out.extend_from_slice(b"NOT_STORED\r\n");
1290 }
1291 }
1292 _ => {
1293 if !noreply {
1294 out.extend_from_slice(b"SERVER_ERROR internal\r\n");
1295 }
1296 }
1297 }
1298 Ok(())
1299}
1300
1301#[cfg(not(test))]
1303fn ascii_flush(noreply: bool, out: &mut Vec<u8>) -> Br<()> {
1304 STAT_CMD_FLUSH.fetch_add(1, Ordering::Relaxed);
1305
1306 with_ctx(|ctx| {
1308 let keys_and_args: &[&[u8]] = &[b"0"];
1309 eval_lua(ctx, &SCRIPT_FLUSH, keys_and_args)
1310 })
1311 .map_err(|e| BridgeErr::Redis(e.to_string()))?;
1312
1313 if !noreply {
1314 out.extend_from_slice(b"OK\r\n");
1315 }
1316 Ok(())
1317}
1318
1319#[cfg(not(test))]
1323fn meta_noop(flags: &[MetaFlag], out: &mut Vec<u8>) -> Br<()> {
1324 out.extend_from_slice(b"MN");
1325 if let Some(opaque) = get_flag_token(flags, b'O') {
1326 out.push(b' ');
1327 out.push(b'O');
1328 out.extend_from_slice(opaque.as_bytes());
1329 }
1330 out.extend_from_slice(b"\r\n");
1331 Ok(())
1332}
1333
1334#[cfg(not(test))]
1336fn meta_get(key: &[u8], flags: &[MetaFlag], out: &mut Vec<u8>) -> Br<()> {
1337 STAT_CMD_GET.fetch_add(1, Ordering::Relaxed);
1338
1339 let rk = make_redis_key(key);
1340 let want_ttl_update = get_flag_token(flags, b'T');
1341
1342 let reply = if let Some(ttl_str) = want_ttl_update {
1343 let exp_str = ttl_str.to_string();
1347 with_ctx(|ctx| {
1348 let touch_keys_and_args: &[&[u8]] = &[
1350 b"2",
1351 rk.as_slice(),
1352 CAS_COUNTER_KEY.as_bytes(),
1353 exp_str.as_bytes(),
1354 ];
1355 eval_lua(ctx, &SCRIPT_TOUCH, touch_keys_and_args)
1356 })
1357 .map_err(|e| BridgeErr::Redis(e.to_string()))?;
1358 with_ctx(|ctx| {
1360 let keys_and_args: &[&[u8]] = &[b"1", rk.as_slice()];
1361 eval_lua(ctx, &SCRIPT_META_GET, keys_and_args)
1362 })
1363 .map_err(|e| BridgeErr::Redis(e.to_string()))?
1364 } else {
1365 with_ctx(|ctx| {
1366 let keys_and_args: &[&[u8]] = &[b"1", rk.as_slice()];
1367 eval_lua(ctx, &SCRIPT_META_GET, keys_and_args)
1368 })
1369 .map_err(|e| BridgeErr::Redis(e.to_string()))?
1370 };
1371
1372 if is_redis_error(&reply) {
1373 out.extend_from_slice(b"SERVER_ERROR internal\r\n");
1374 return Ok(());
1375 }
1376
1377 let (status, hex_val, item_flags, cas_str, ttl, size) = match &reply {
1379 RedisValue::Array(arr) if arr.len() >= 6 => {
1380 let st = eval_int(&arr[0]);
1381 let hv = eval_str(&arr[1]);
1382 let fl = eval_str(&arr[2]);
1383 let cs = eval_str(&arr[3]);
1384 let t = eval_int(&arr[4]);
1385 let sz = eval_int(&arr[5]);
1386 (st, hv, fl, cs, t, sz)
1387 }
1388 _ => {
1389 out.extend_from_slice(b"SERVER_ERROR internal\r\n");
1390 return Ok(());
1391 }
1392 };
1393
1394 let quiet = has_flag(flags, b'q');
1395 if status == -1 {
1396 STAT_GET_MISSES.fetch_add(1, Ordering::Relaxed);
1397 if !quiet {
1398 out.extend_from_slice(b"EN");
1399 write_meta_flag_echo(out, flags, key);
1400 out.extend_from_slice(b"\r\n");
1401 }
1402 return Ok(());
1403 }
1404
1405 STAT_GET_HITS.fetch_add(1, Ordering::Relaxed);
1406
1407 let value = hex_decode(&hex_val);
1408 let want_value = has_flag(flags, b'v');
1409
1410 if want_value {
1411 out.extend_from_slice(b"VA ");
1413 out.extend_from_slice(value.len().to_string().as_bytes());
1414 } else {
1415 out.extend_from_slice(b"HD");
1417 }
1418
1419 if has_flag(flags, b'c') {
1421 out.push(b' ');
1422 out.push(b'c');
1423 out.extend_from_slice(cas_str.as_bytes());
1424 }
1425 if has_flag(flags, b'f') {
1426 out.push(b' ');
1427 out.push(b'f');
1428 out.extend_from_slice(item_flags.as_bytes());
1429 }
1430 if has_flag(flags, b's') {
1431 out.push(b' ');
1432 out.push(b's');
1433 out.extend_from_slice(size.to_string().as_bytes());
1434 }
1435 if has_flag(flags, b't') {
1436 out.push(b' ');
1437 out.push(b't');
1438 let ttl_val = if ttl == -1 { -1 } else { ttl };
1440 out.extend_from_slice(ttl_val.to_string().as_bytes());
1441 }
1442 write_meta_flag_echo(out, flags, key);
1443 out.extend_from_slice(b"\r\n");
1444
1445 if want_value {
1446 out.extend_from_slice(&value);
1447 out.extend_from_slice(b"\r\n");
1448 }
1449 Ok(())
1450}
1451
1452#[cfg(not(test))]
1454fn meta_set(key: &[u8], data: &[u8], flags: &[MetaFlag], out: &mut Vec<u8>) -> Br<()> {
1455 STAT_CMD_SET.fetch_add(1, Ordering::Relaxed);
1456
1457 let quiet = has_flag(flags, b'q');
1458 let mode = get_flag_token(flags, b'M').unwrap_or("S");
1459 let item_flags = get_flag_token(flags, b'F').unwrap_or("0");
1460 let ttl = get_flag_token(flags, b'T').unwrap_or("0");
1461 let cas = get_flag_token(flags, b'C').unwrap_or("0");
1462
1463 match mode {
1464 "A" | "P" => {
1465 let is_prepend = mode == "P";
1467 let script = if is_prepend {
1468 &SCRIPT_PREPEND
1469 } else {
1470 &SCRIPT_APPEND
1471 };
1472 let rk = make_redis_key(key);
1473 let reply = with_ctx(|ctx| {
1474 let keys_and_args: &[&[u8]] = &[
1475 b"2",
1476 rk.as_slice(),
1477 CAS_COUNTER_KEY.as_bytes(),
1478 data,
1479 cas.as_bytes(),
1480 ];
1481 eval_lua(ctx, script, keys_and_args)
1482 })
1483 .map_err(|e| BridgeErr::Redis(e.to_string()))?;
1484
1485 if is_redis_error(&reply) {
1486 if !quiet {
1487 out.extend_from_slice(b"SERVER_ERROR internal\r\n");
1488 }
1489 return Ok(());
1490 }
1491
1492 let (status, cas_val) = match &reply {
1493 RedisValue::Array(arr) if arr.len() >= 2 => (eval_int(&arr[0]), eval_str(&arr[1])),
1494 _ => {
1495 if !quiet {
1496 out.extend_from_slice(b"SERVER_ERROR internal\r\n");
1497 }
1498 return Ok(());
1499 }
1500 };
1501
1502 if !quiet {
1503 match status {
1504 0 => {
1505 out.extend_from_slice(b"HD");
1506 if has_flag(flags, b'c') || cas != "0" {
1507 out.push(b' ');
1508 out.push(b'c');
1509 out.extend_from_slice(cas_val.as_bytes());
1510 }
1511 write_meta_flag_echo(out, flags, key);
1512 out.extend_from_slice(b"\r\n");
1513 }
1514 -5 => {
1515 out.extend_from_slice(b"NS");
1517 write_meta_flag_echo(out, flags, key);
1518 out.extend_from_slice(b"\r\n");
1519 }
1520 -2 => {
1521 out.extend_from_slice(b"EX");
1523 write_meta_flag_echo(out, flags, key);
1524 out.extend_from_slice(b"\r\n");
1525 }
1526 _ => {
1527 out.extend_from_slice(b"SERVER_ERROR internal\r\n");
1528 }
1529 }
1530 }
1531 }
1532 "S" | "E" | "R" => {
1533 let op_name = match mode {
1535 "E" => "add",
1536 "R" => "replace",
1537 _ => "set",
1538 };
1539 let rk = make_redis_key(key);
1540 let cas_str = cas.to_string();
1541 let flags_str = item_flags.to_string();
1542 let expiry_str = ttl.to_string();
1543
1544 let reply = with_ctx(|ctx| {
1545 let keys_and_args: &[&[u8]] = &[
1546 b"2",
1547 rk.as_slice(),
1548 CAS_COUNTER_KEY.as_bytes(),
1549 op_name.as_bytes(),
1550 data,
1551 flags_str.as_bytes(),
1552 cas_str.as_bytes(),
1553 expiry_str.as_bytes(),
1554 ];
1555 eval_lua(ctx, &SCRIPT_STORE, keys_and_args)
1556 })
1557 .map_err(|e| BridgeErr::Redis(e.to_string()))?;
1558
1559 if is_redis_error(&reply) {
1560 if !quiet {
1561 out.extend_from_slice(b"SERVER_ERROR internal\r\n");
1562 }
1563 return Ok(());
1564 }
1565
1566 let (status, new_cas) = match &reply {
1567 RedisValue::Array(arr) if arr.len() >= 2 => (eval_int(&arr[0]), eval_str(&arr[1])),
1568 _ => {
1569 if !quiet {
1570 out.extend_from_slice(b"SERVER_ERROR internal\r\n");
1571 }
1572 return Ok(());
1573 }
1574 };
1575
1576 if !quiet {
1577 match status {
1578 0 => {
1579 STAT_CAS_HITS.fetch_add(1, Ordering::Relaxed);
1580 out.extend_from_slice(b"HD");
1581 if has_flag(flags, b'c') || cas != "0" {
1582 out.push(b' ');
1583 out.push(b'c');
1584 out.extend_from_slice(new_cas.as_bytes());
1585 }
1586 write_meta_flag_echo(out, flags, key);
1587 out.extend_from_slice(b"\r\n");
1588 }
1589 -1 => {
1590 out.extend_from_slice(b"NF");
1592 write_meta_flag_echo(out, flags, key);
1593 out.extend_from_slice(b"\r\n");
1594 }
1595 -2 => {
1596 if cas != "0" {
1598 STAT_CAS_BADVAL.fetch_add(1, Ordering::Relaxed);
1599 out.extend_from_slice(b"EX");
1600 } else {
1601 out.extend_from_slice(b"NS");
1602 }
1603 write_meta_flag_echo(out, flags, key);
1604 out.extend_from_slice(b"\r\n");
1605 }
1606 _ => {
1607 out.extend_from_slice(b"SERVER_ERROR internal\r\n");
1608 }
1609 }
1610 }
1611 }
1612 _ => {
1613 if !quiet {
1615 out.extend_from_slice(b"CLIENT_ERROR unsupported ms mode\r\n");
1616 }
1617 }
1618 }
1619 Ok(())
1620}
1621
1622#[cfg(not(test))]
1624fn meta_delete(key: &[u8], flags: &[MetaFlag], out: &mut Vec<u8>) -> Br<()> {
1625 let rk = make_redis_key(key);
1626 let cas = get_flag_token(flags, b'C').unwrap_or("0");
1627 let quiet = has_flag(flags, b'q');
1628
1629 let (reply, is_direct_del) = if cas == "0" {
1631 let r = with_ctx(|ctx| ctx.call("DEL", &[rk.as_slice()]))
1632 .map_err(|e| BridgeErr::Redis(e.to_string()))?;
1633 (r, true)
1634 } else {
1635 let r = with_ctx(|ctx| {
1636 let keys_and_args: &[&[u8]] = &[b"1", rk.as_slice(), cas.as_bytes()];
1637 eval_lua(ctx, &SCRIPT_DELETE, keys_and_args)
1638 })
1639 .map_err(|e| BridgeErr::Redis(e.to_string()))?;
1640 (r, false)
1641 };
1642
1643 if is_redis_error(&reply) {
1644 if !quiet {
1645 out.extend_from_slice(b"SERVER_ERROR internal\r\n");
1646 }
1647 return Ok(());
1648 }
1649
1650 let status = match &reply {
1653 RedisValue::Integer(n) => {
1654 if is_direct_del {
1655 if *n > 0 { 0 } else { -1 }
1657 } else {
1658 *n
1659 }
1660 }
1661 _ => {
1662 if !quiet {
1663 out.extend_from_slice(b"SERVER_ERROR internal\r\n");
1664 }
1665 return Ok(());
1666 }
1667 };
1668
1669 match status {
1670 0 => {
1671 STAT_DELETE_HITS.fetch_add(1, Ordering::Relaxed);
1672 if !quiet {
1673 out.extend_from_slice(b"HD");
1674 write_meta_flag_echo(out, flags, key);
1675 out.extend_from_slice(b"\r\n");
1676 }
1677 }
1678 -1 => {
1679 STAT_DELETE_MISSES.fetch_add(1, Ordering::Relaxed);
1680 if !quiet {
1681 out.extend_from_slice(b"NF");
1682 write_meta_flag_echo(out, flags, key);
1683 out.extend_from_slice(b"\r\n");
1684 }
1685 }
1686 -2 => {
1687 if !quiet {
1689 out.extend_from_slice(b"EX");
1690 write_meta_flag_echo(out, flags, key);
1691 out.extend_from_slice(b"\r\n");
1692 }
1693 }
1694 _ => {
1695 if !quiet {
1696 out.extend_from_slice(b"SERVER_ERROR internal\r\n");
1697 }
1698 }
1699 }
1700 Ok(())
1701}
1702
1703#[cfg(not(test))]
1705fn meta_arithmetic(key: &[u8], flags: &[MetaFlag], out: &mut Vec<u8>) -> Br<()> {
1706 let rk = make_redis_key(key);
1707 let quiet = has_flag(flags, b'q');
1708
1709 let delta_str = get_flag_token(flags, b'D').unwrap_or("1");
1710 let mode = get_flag_token(flags, b'M').unwrap_or("I");
1711 let is_decr = mode == "D";
1713 let is_decr_str = if is_decr { "1" } else { "0" };
1714 let initial = get_flag_token(flags, b'J').unwrap_or("0");
1715 let expiry = get_flag_token(flags, b'N').unwrap_or("4294967295");
1717
1718 let reply = with_ctx(|ctx| {
1719 let keys_and_args: &[&[u8]] = &[
1720 b"2",
1721 rk.as_slice(),
1722 CAS_COUNTER_KEY.as_bytes(),
1723 delta_str.as_bytes(),
1724 is_decr_str.as_bytes(),
1725 initial.as_bytes(),
1726 expiry.as_bytes(),
1727 ];
1728 eval_lua(ctx, &SCRIPT_COUNTER, keys_and_args)
1729 })
1730 .map_err(|e| BridgeErr::Redis(e.to_string()))?;
1731
1732 if is_redis_error(&reply) {
1733 if !quiet {
1734 out.extend_from_slice(b"SERVER_ERROR internal\r\n");
1735 }
1736 return Ok(());
1737 }
1738
1739 let (status, value_str, cas_str) = match &reply {
1740 RedisValue::Array(arr) if arr.len() >= 3 => {
1741 let st = eval_int(&arr[0]);
1742 let val = eval_str(&arr[1]);
1743 let cas_s = eval_str(&arr[2]);
1744 (st, val, cas_s)
1745 }
1746 _ => {
1747 if !quiet {
1748 out.extend_from_slice(b"SERVER_ERROR internal\r\n");
1749 }
1750 return Ok(());
1751 }
1752 };
1753
1754 if is_decr {
1755 if status == 0 {
1756 STAT_DECR_HITS.fetch_add(1, Ordering::Relaxed);
1757 } else {
1758 STAT_DECR_MISSES.fetch_add(1, Ordering::Relaxed);
1759 }
1760 } else {
1761 if status == 0 {
1762 STAT_INCR_HITS.fetch_add(1, Ordering::Relaxed);
1763 } else {
1764 STAT_INCR_MISSES.fetch_add(1, Ordering::Relaxed);
1765 }
1766 }
1767
1768 match status {
1769 0 => {
1770 let want_value = has_flag(flags, b'v');
1771 if !quiet {
1772 if want_value {
1773 out.extend_from_slice(b"VA ");
1774 out.extend_from_slice(value_str.len().to_string().as_bytes());
1775 } else {
1776 out.extend_from_slice(b"HD");
1777 }
1778 if has_flag(flags, b'c') {
1779 out.push(b' ');
1780 out.push(b'c');
1781 out.extend_from_slice(cas_str.as_bytes());
1782 }
1783 write_meta_flag_echo(out, flags, key);
1784 out.extend_from_slice(b"\r\n");
1785 if want_value {
1786 out.extend_from_slice(value_str.as_bytes());
1787 out.extend_from_slice(b"\r\n");
1788 }
1789 }
1790 }
1791 -1 => {
1792 if !quiet {
1794 out.extend_from_slice(b"NF");
1795 write_meta_flag_echo(out, flags, key);
1796 out.extend_from_slice(b"\r\n");
1797 }
1798 }
1799 -3 => {
1800 if !quiet {
1802 out.extend_from_slice(
1803 b"CLIENT_ERROR cannot increment or decrement non-numeric value\r\n",
1804 );
1805 }
1806 }
1807 _ => {
1808 if !quiet {
1809 out.extend_from_slice(b"SERVER_ERROR internal\r\n");
1810 }
1811 }
1812 }
1813 Ok(())
1814}
1815
1816#[cfg(not(test))]
1818fn ascii_stats(args: Option<&str>, out: &mut Vec<u8>) -> Br<()> {
1819 match args {
1820 None | Some("") => {
1821 let uptime = STARTUP_INSTANT
1823 .get()
1824 .map(|t| t.elapsed().as_secs())
1825 .unwrap_or(0);
1826 let pid = std::process::id();
1827
1828 let curr_items: u64 = match with_ctx(|ctx| {
1829 let keys_and_args: &[&[u8]] = &[b"0"];
1830 eval_lua(ctx, &SCRIPT_COUNT_ITEMS, keys_and_args)
1831 }) {
1832 Ok(RedisValue::Integer(n)) => n as u64,
1833 _ => 0,
1834 };
1835
1836 let stats: Vec<(&str, String)> = vec![
1837 ("pid", pid.to_string()),
1838 ("uptime", uptime.to_string()),
1839 (
1840 "time",
1841 std::time::SystemTime::now()
1842 .duration_since(std::time::UNIX_EPOCH)
1843 .map(|d| d.as_secs())
1844 .unwrap_or(0)
1845 .to_string(),
1846 ),
1847 ("version", "RedCouch 0.1.0".to_string()),
1848 ("curr_items", curr_items.to_string()),
1849 (
1850 "curr_connections",
1851 STAT_CURR_CONNECTIONS.load(Ordering::Relaxed).to_string(),
1852 ),
1853 (
1854 "total_connections",
1855 STAT_TOTAL_CONNECTIONS.load(Ordering::Relaxed).to_string(),
1856 ),
1857 ("cmd_get", STAT_CMD_GET.load(Ordering::Relaxed).to_string()),
1858 ("cmd_set", STAT_CMD_SET.load(Ordering::Relaxed).to_string()),
1859 (
1860 "cmd_flush",
1861 STAT_CMD_FLUSH.load(Ordering::Relaxed).to_string(),
1862 ),
1863 (
1864 "cmd_touch",
1865 STAT_CMD_TOUCH.load(Ordering::Relaxed).to_string(),
1866 ),
1867 (
1868 "get_hits",
1869 STAT_GET_HITS.load(Ordering::Relaxed).to_string(),
1870 ),
1871 (
1872 "get_misses",
1873 STAT_GET_MISSES.load(Ordering::Relaxed).to_string(),
1874 ),
1875 (
1876 "delete_hits",
1877 STAT_DELETE_HITS.load(Ordering::Relaxed).to_string(),
1878 ),
1879 (
1880 "delete_misses",
1881 STAT_DELETE_MISSES.load(Ordering::Relaxed).to_string(),
1882 ),
1883 (
1884 "incr_hits",
1885 STAT_INCR_HITS.load(Ordering::Relaxed).to_string(),
1886 ),
1887 (
1888 "incr_misses",
1889 STAT_INCR_MISSES.load(Ordering::Relaxed).to_string(),
1890 ),
1891 (
1892 "decr_hits",
1893 STAT_DECR_HITS.load(Ordering::Relaxed).to_string(),
1894 ),
1895 (
1896 "decr_misses",
1897 STAT_DECR_MISSES.load(Ordering::Relaxed).to_string(),
1898 ),
1899 (
1900 "cas_hits",
1901 STAT_CAS_HITS.load(Ordering::Relaxed).to_string(),
1902 ),
1903 (
1904 "cas_misses",
1905 STAT_CAS_MISSES.load(Ordering::Relaxed).to_string(),
1906 ),
1907 (
1908 "cas_badval",
1909 STAT_CAS_BADVAL.load(Ordering::Relaxed).to_string(),
1910 ),
1911 (
1912 "auth_cmds",
1913 STAT_AUTH_CMDS.load(Ordering::Relaxed).to_string(),
1914 ),
1915 (
1916 "auth_errors",
1917 STAT_AUTH_ERRORS.load(Ordering::Relaxed).to_string(),
1918 ),
1919 (
1920 "rejected_connections",
1921 STAT_REJECTED_CONNECTIONS
1922 .load(Ordering::Relaxed)
1923 .to_string(),
1924 ),
1925 ("max_connections", MAX_CONNECTIONS.to_string()),
1926 ];
1927
1928 for (name, value) in &stats {
1929 out.extend_from_slice(b"STAT ");
1930 out.extend_from_slice(name.as_bytes());
1931 out.push(b' ');
1932 out.extend_from_slice(value.as_bytes());
1933 out.extend_from_slice(b"\r\n");
1934 }
1935 }
1936 Some(_) => {}
1938 }
1939 out.extend_from_slice(b"END\r\n");
1940 Ok(())
1941}
1942
1943#[cfg(test)]
1948mod tests {
1949 use super::*;
1950
1951 #[test]
1954 fn line_end_crlf() {
1955 let buf = b"get foo\r\n";
1956 assert_eq!(find_line_end(buf), Some((7, 9)));
1957 }
1958
1959 #[test]
1960 fn line_end_bare_lf() {
1961 let buf = b"get foo\n";
1962 assert_eq!(find_line_end(buf), Some((7, 8)));
1963 }
1964
1965 #[test]
1966 fn line_end_no_terminator() {
1967 let buf = b"get foo";
1968 assert_eq!(find_line_end(buf), None);
1969 }
1970
1971 #[test]
1972 fn line_end_empty() {
1973 assert_eq!(find_line_end(b""), None);
1974 }
1975
1976 #[test]
1977 fn line_end_just_crlf() {
1978 assert_eq!(find_line_end(b"\r\n"), Some((0, 2)));
1979 }
1980
1981 #[test]
1984 fn key_valid() {
1985 assert!(validate_key(b"mykey").is_ok());
1986 }
1987
1988 #[test]
1989 fn key_empty() {
1990 assert!(validate_key(b"").is_err());
1991 }
1992
1993 #[test]
1994 fn key_too_long() {
1995 let long_key = vec![b'a'; MAX_KEY_LEN + 1];
1996 assert!(validate_key(&long_key).is_err());
1997 }
1998
1999 #[test]
2000 fn key_max_length_ok() {
2001 let key = vec![b'a'; MAX_KEY_LEN];
2002 assert!(validate_key(&key).is_ok());
2003 }
2004
2005 #[test]
2006 fn key_with_space() {
2007 assert!(validate_key(b"has space").is_err());
2008 }
2009
2010 #[test]
2011 fn key_with_control_char() {
2012 assert!(validate_key(b"has\x01ctrl").is_err());
2013 }
2014
2015 #[test]
2018 fn parse_set_basic() {
2019 match parse_command_line(b"set mykey 0 60 5") {
2020 CmdParseResult::Ok(AsciiCmd::Store {
2021 cmd,
2022 key,
2023 flags,
2024 exptime,
2025 bytes,
2026 noreply,
2027 }) => {
2028 assert_eq!(cmd, StoreOp::Set);
2029 assert_eq!(key, b"mykey");
2030 assert_eq!(flags, 0);
2031 assert_eq!(exptime, 60);
2032 assert_eq!(bytes, 5);
2033 assert!(!noreply);
2034 }
2035 other => panic!("unexpected: {other:?}"),
2036 }
2037 }
2038
2039 #[test]
2040 fn parse_set_noreply() {
2041 match parse_command_line(b"set k 1 0 3 noreply") {
2042 CmdParseResult::Ok(AsciiCmd::Store { noreply, .. }) => assert!(noreply),
2043 other => panic!("unexpected: {other:?}"),
2044 }
2045 }
2046
2047 #[test]
2048 fn parse_add() {
2049 match parse_command_line(b"add k 0 0 1") {
2050 CmdParseResult::Ok(AsciiCmd::Store {
2051 cmd: StoreOp::Add, ..
2052 }) => {}
2053 other => panic!("unexpected: {other:?}"),
2054 }
2055 }
2056
2057 #[test]
2058 fn parse_replace() {
2059 match parse_command_line(b"replace k 0 0 1") {
2060 CmdParseResult::Ok(AsciiCmd::Store {
2061 cmd: StoreOp::Replace,
2062 ..
2063 }) => {}
2064 other => panic!("unexpected: {other:?}"),
2065 }
2066 }
2067
2068 #[test]
2071 fn parse_cas_basic() {
2072 match parse_command_line(b"cas k 0 0 5 12345") {
2073 CmdParseResult::Ok(AsciiCmd::Cas {
2074 key,
2075 cas_unique,
2076 noreply,
2077 ..
2078 }) => {
2079 assert_eq!(key, b"k");
2080 assert_eq!(cas_unique, 12345);
2081 assert!(!noreply);
2082 }
2083 other => panic!("unexpected: {other:?}"),
2084 }
2085 }
2086
2087 #[test]
2088 fn parse_cas_noreply() {
2089 match parse_command_line(b"cas k 0 0 5 100 noreply") {
2090 CmdParseResult::Ok(AsciiCmd::Cas { noreply, .. }) => assert!(noreply),
2091 other => panic!("unexpected: {other:?}"),
2092 }
2093 }
2094
2095 #[test]
2098 fn parse_append() {
2099 match parse_command_line(b"append k 5") {
2100 CmdParseResult::Ok(AsciiCmd::AppendPrepend {
2101 is_prepend,
2102 key,
2103 bytes,
2104 noreply,
2105 }) => {
2106 assert!(!is_prepend);
2107 assert_eq!(key, b"k");
2108 assert_eq!(bytes, 5);
2109 assert!(!noreply);
2110 }
2111 other => panic!("unexpected: {other:?}"),
2112 }
2113 }
2114
2115 #[test]
2116 fn parse_prepend_noreply() {
2117 match parse_command_line(b"prepend k 3 noreply") {
2118 CmdParseResult::Ok(AsciiCmd::AppendPrepend {
2119 is_prepend,
2120 noreply,
2121 ..
2122 }) => {
2123 assert!(is_prepend);
2124 assert!(noreply);
2125 }
2126 other => panic!("unexpected: {other:?}"),
2127 }
2128 }
2129
2130 #[test]
2133 fn parse_get_single() {
2134 match parse_command_line(b"get foo") {
2135 CmdParseResult::Ok(AsciiCmd::Retrieval { cmd, exptime, keys }) => {
2136 assert_eq!(cmd, RetrievalOp::Get);
2137 assert!(exptime.is_none());
2138 assert_eq!(keys, vec![b"foo".as_slice()]);
2139 }
2140 other => panic!("unexpected: {other:?}"),
2141 }
2142 }
2143
2144 #[test]
2145 fn parse_get_multi() {
2146 match parse_command_line(b"get a b c") {
2147 CmdParseResult::Ok(AsciiCmd::Retrieval { keys, .. }) => {
2148 assert_eq!(keys.len(), 3);
2149 }
2150 other => panic!("unexpected: {other:?}"),
2151 }
2152 }
2153
2154 #[test]
2155 fn parse_gets() {
2156 match parse_command_line(b"gets k") {
2157 CmdParseResult::Ok(AsciiCmd::Retrieval {
2158 cmd: RetrievalOp::Gets,
2159 ..
2160 }) => {}
2161 other => panic!("unexpected: {other:?}"),
2162 }
2163 }
2164
2165 #[test]
2166 fn parse_gat() {
2167 match parse_command_line(b"gat 60 k1 k2") {
2168 CmdParseResult::Ok(AsciiCmd::Retrieval { cmd, exptime, keys }) => {
2169 assert_eq!(cmd, RetrievalOp::Gat);
2170 assert_eq!(exptime, Some(60));
2171 assert_eq!(keys.len(), 2);
2172 }
2173 other => panic!("unexpected: {other:?}"),
2174 }
2175 }
2176
2177 #[test]
2178 fn parse_gats() {
2179 match parse_command_line(b"gats 0 k") {
2180 CmdParseResult::Ok(AsciiCmd::Retrieval {
2181 cmd: RetrievalOp::Gats,
2182 ..
2183 }) => {}
2184 other => panic!("unexpected: {other:?}"),
2185 }
2186 }
2187
2188 #[test]
2191 fn parse_delete() {
2192 match parse_command_line(b"delete mykey") {
2193 CmdParseResult::Ok(AsciiCmd::Delete { key, noreply }) => {
2194 assert_eq!(key, b"mykey");
2195 assert!(!noreply);
2196 }
2197 other => panic!("unexpected: {other:?}"),
2198 }
2199 }
2200
2201 #[test]
2202 fn parse_delete_noreply() {
2203 match parse_command_line(b"delete k noreply") {
2204 CmdParseResult::Ok(AsciiCmd::Delete { noreply, .. }) => assert!(noreply),
2205 other => panic!("unexpected: {other:?}"),
2206 }
2207 }
2208
2209 #[test]
2210 fn parse_incr() {
2211 match parse_command_line(b"incr counter 5") {
2212 CmdParseResult::Ok(AsciiCmd::Counter {
2213 is_decr,
2214 key,
2215 value,
2216 noreply,
2217 }) => {
2218 assert!(!is_decr);
2219 assert_eq!(key, b"counter");
2220 assert_eq!(value, 5);
2221 assert!(!noreply);
2222 }
2223 other => panic!("unexpected: {other:?}"),
2224 }
2225 }
2226
2227 #[test]
2228 fn parse_decr_noreply() {
2229 match parse_command_line(b"decr c 10 noreply") {
2230 CmdParseResult::Ok(AsciiCmd::Counter {
2231 is_decr, noreply, ..
2232 }) => {
2233 assert!(is_decr);
2234 assert!(noreply);
2235 }
2236 other => panic!("unexpected: {other:?}"),
2237 }
2238 }
2239
2240 #[test]
2241 fn parse_touch() {
2242 match parse_command_line(b"touch k 300") {
2243 CmdParseResult::Ok(AsciiCmd::Touch {
2244 key,
2245 exptime,
2246 noreply,
2247 }) => {
2248 assert_eq!(key, b"k");
2249 assert_eq!(exptime, 300);
2250 assert!(!noreply);
2251 }
2252 other => panic!("unexpected: {other:?}"),
2253 }
2254 }
2255
2256 #[test]
2259 fn parse_flush_all() {
2260 match parse_command_line(b"flush_all") {
2261 CmdParseResult::Ok(AsciiCmd::FlushAll { _delay, noreply }) => {
2262 assert_eq!(_delay, 0);
2263 assert!(!noreply);
2264 }
2265 other => panic!("unexpected: {other:?}"),
2266 }
2267 }
2268
2269 #[test]
2270 fn parse_flush_all_delay_noreply() {
2271 match parse_command_line(b"flush_all 30 noreply") {
2272 CmdParseResult::Ok(AsciiCmd::FlushAll { _delay, noreply }) => {
2273 assert_eq!(_delay, 30);
2274 assert!(noreply);
2275 }
2276 other => panic!("unexpected: {other:?}"),
2277 }
2278 }
2279
2280 #[test]
2281 fn parse_version() {
2282 assert!(matches!(
2283 parse_command_line(b"version"),
2284 CmdParseResult::Ok(AsciiCmd::Version)
2285 ));
2286 }
2287
2288 #[test]
2289 fn parse_stats_bare() {
2290 match parse_command_line(b"stats") {
2291 CmdParseResult::Ok(AsciiCmd::Stats { args }) => assert!(args.is_none()),
2292 other => panic!("unexpected: {other:?}"),
2293 }
2294 }
2295
2296 #[test]
2297 fn parse_stats_with_arg() {
2298 match parse_command_line(b"stats items") {
2299 CmdParseResult::Ok(AsciiCmd::Stats { args }) => {
2300 assert_eq!(args, Some("items"));
2301 }
2302 other => panic!("unexpected: {other:?}"),
2303 }
2304 }
2305
2306 #[test]
2307 fn parse_verbosity() {
2308 match parse_command_line(b"verbosity 2") {
2309 CmdParseResult::Ok(AsciiCmd::Verbosity { noreply }) => assert!(!noreply),
2310 other => panic!("unexpected: {other:?}"),
2311 }
2312 }
2313
2314 #[test]
2315 fn parse_quit() {
2316 assert!(matches!(
2317 parse_command_line(b"quit"),
2318 CmdParseResult::Ok(AsciiCmd::Quit)
2319 ));
2320 }
2321
2322 #[test]
2325 fn parse_unknown_command() {
2326 assert!(matches!(
2327 parse_command_line(b"foobar key"),
2328 CmdParseResult::UnknownCommand
2329 ));
2330 }
2331
2332 #[test]
2333 fn parse_set_missing_args() {
2334 match parse_command_line(b"set k 0") {
2335 CmdParseResult::ClientError(msg) => assert!(!msg.is_empty()),
2336 other => panic!("expected ClientError, got {other:?}"),
2337 }
2338 }
2339
2340 #[test]
2341 fn parse_set_bad_flags() {
2342 assert!(matches!(
2343 parse_command_line(b"set k notanum 0 5"),
2344 CmdParseResult::ClientError(_)
2345 ));
2346 }
2347
2348 #[test]
2349 fn parse_get_no_keys() {
2350 assert!(matches!(
2351 parse_command_line(b"get"),
2352 CmdParseResult::ClientError(_)
2353 ));
2354 }
2355
2356 #[test]
2357 fn parse_incr_bad_value() {
2358 assert!(matches!(
2359 parse_command_line(b"incr k -5"),
2360 CmdParseResult::ClientError(_)
2361 ));
2362 }
2363
2364 #[test]
2365 fn parse_set_bad_noreply_token() {
2366 assert!(matches!(
2367 parse_command_line(b"set k 0 0 5 garbage"),
2368 CmdParseResult::ClientError(_)
2369 ));
2370 }
2371
2372 #[test]
2373 fn parse_delete_bad_extra() {
2374 assert!(matches!(
2375 parse_command_line(b"delete k extra"),
2376 CmdParseResult::ClientError(_)
2377 ));
2378 }
2379
2380 #[test]
2383 fn data_block_for_store() {
2384 let cmd = AsciiCmd::Store {
2385 cmd: StoreOp::Set,
2386 key: b"k",
2387 flags: 0,
2388 exptime: 0,
2389 bytes: 10,
2390 noreply: false,
2391 };
2392 assert!(needs_data_block(&cmd));
2393 assert_eq!(data_block_len(&cmd), 10);
2394 assert!(!is_noreply(&cmd));
2395 }
2396
2397 #[test]
2398 fn no_data_block_for_get() {
2399 let cmd = AsciiCmd::Retrieval {
2400 cmd: RetrievalOp::Get,
2401 exptime: None,
2402 keys: vec![b"k"],
2403 };
2404 assert!(!needs_data_block(&cmd));
2405 }
2406
2407 #[test]
2410 fn append_no_flags_exptime() {
2411 assert!(matches!(
2413 parse_command_line(b"append k 0 0 5"),
2414 CmdParseResult::ClientError(_),
2415 ));
2416 }
2417
2418 #[test]
2419 fn prepend_no_flags_exptime() {
2420 assert!(matches!(
2421 parse_command_line(b"prepend k 0 0 5"),
2422 CmdParseResult::ClientError(_),
2423 ));
2424 }
2425
2426 #[test]
2429 fn meta_get_is_meta() {
2430 assert!(is_meta_command(b"mg mykey"));
2431 }
2432
2433 #[test]
2434 fn meta_set_is_meta() {
2435 assert!(is_meta_command(b"ms mykey 5"));
2436 }
2437
2438 #[test]
2439 fn meta_delete_is_meta() {
2440 assert!(is_meta_command(b"md mykey"));
2441 }
2442
2443 #[test]
2444 fn meta_arithmetic_is_meta() {
2445 assert!(is_meta_command(b"ma mykey"));
2446 }
2447
2448 #[test]
2449 fn meta_noop_bare_is_meta() {
2450 assert!(is_meta_command(b"mn"));
2451 }
2452
2453 #[test]
2454 fn meta_debug_is_meta() {
2455 assert!(is_meta_command(b"me mykey"));
2456 }
2457
2458 #[test]
2459 fn classic_get_not_meta() {
2460 assert!(!is_meta_command(b"get foo"));
2461 }
2462
2463 #[test]
2464 fn classic_set_not_meta() {
2465 assert!(!is_meta_command(b"set foo 0 0 5"));
2466 }
2467
2468 #[test]
2469 fn short_line_not_meta() {
2470 assert!(!is_meta_command(b"m"));
2471 }
2472
2473 #[test]
2474 fn empty_line_not_meta() {
2475 assert!(!is_meta_command(b""));
2476 }
2477
2478 #[test]
2479 fn mg_without_space_not_meta() {
2480 assert!(!is_meta_command(b"mgx foo"));
2482 }
2483
2484 #[test]
2487 fn parse_empty_line() {
2488 assert!(matches!(
2489 parse_command_line(b""),
2490 CmdParseResult::UnknownCommand
2491 ));
2492 }
2493
2494 #[test]
2495 fn parse_whitespace_only() {
2496 assert!(matches!(
2497 parse_command_line(b" "),
2498 CmdParseResult::UnknownCommand
2499 ));
2500 }
2501
2502 #[test]
2503 fn parse_non_utf8_line() {
2504 assert!(matches!(
2505 parse_command_line(b"set \xff\xfe 0 0 5"),
2506 CmdParseResult::ClientError(_)
2507 ));
2508 }
2509
2510 #[test]
2513 fn parse_cas_too_few_args() {
2514 assert!(matches!(
2515 parse_command_line(b"cas k 0 0"),
2516 CmdParseResult::ClientError(_)
2517 ));
2518 }
2519
2520 #[test]
2521 fn parse_cas_too_many_args() {
2522 assert!(matches!(
2523 parse_command_line(b"cas k 0 0 5 100 noreply extra"),
2524 CmdParseResult::ClientError(_)
2525 ));
2526 }
2527
2528 #[test]
2529 fn parse_cas_bad_cas_value() {
2530 assert!(matches!(
2531 parse_command_line(b"cas k 0 0 5 notanum"),
2532 CmdParseResult::ClientError(_)
2533 ));
2534 }
2535
2536 #[test]
2537 fn parse_cas_bad_noreply_token() {
2538 assert!(matches!(
2539 parse_command_line(b"cas k 0 0 5 100 garbage"),
2540 CmdParseResult::ClientError(_)
2541 ));
2542 }
2543
2544 #[test]
2547 fn parse_incr_too_few_args() {
2548 assert!(matches!(
2549 parse_command_line(b"incr k"),
2550 CmdParseResult::ClientError(_)
2551 ));
2552 }
2553
2554 #[test]
2555 fn parse_decr_too_many_args() {
2556 assert!(matches!(
2557 parse_command_line(b"decr k 5 noreply extra"),
2558 CmdParseResult::ClientError(_)
2559 ));
2560 }
2561
2562 #[test]
2563 fn parse_incr_bad_noreply_token() {
2564 assert!(matches!(
2565 parse_command_line(b"incr k 5 garbage"),
2566 CmdParseResult::ClientError(_)
2567 ));
2568 }
2569
2570 #[test]
2573 fn parse_touch_too_few_args() {
2574 assert!(matches!(
2575 parse_command_line(b"touch k"),
2576 CmdParseResult::ClientError(_)
2577 ));
2578 }
2579
2580 #[test]
2581 fn parse_touch_too_many_args() {
2582 assert!(matches!(
2583 parse_command_line(b"touch k 60 noreply extra"),
2584 CmdParseResult::ClientError(_)
2585 ));
2586 }
2587
2588 #[test]
2589 fn parse_touch_bad_exptime() {
2590 assert!(matches!(
2591 parse_command_line(b"touch k notanum"),
2592 CmdParseResult::ClientError(_)
2593 ));
2594 }
2595
2596 #[test]
2597 fn parse_touch_bad_noreply_token() {
2598 assert!(matches!(
2599 parse_command_line(b"touch k 60 garbage"),
2600 CmdParseResult::ClientError(_)
2601 ));
2602 }
2603
2604 #[test]
2605 fn parse_touch_noreply() {
2606 match parse_command_line(b"touch k 60 noreply") {
2607 CmdParseResult::Ok(AsciiCmd::Touch { noreply, .. }) => assert!(noreply),
2608 other => panic!("unexpected: {other:?}"),
2609 }
2610 }
2611
2612 #[test]
2615 fn parse_gat_no_keys() {
2616 assert!(matches!(
2617 parse_command_line(b"gat 60"),
2618 CmdParseResult::ClientError(_)
2619 ));
2620 }
2621
2622 #[test]
2623 fn parse_gat_bad_exptime() {
2624 assert!(matches!(
2625 parse_command_line(b"gat notanum k"),
2626 CmdParseResult::ClientError(_)
2627 ));
2628 }
2629
2630 #[test]
2631 fn parse_gats_no_keys() {
2632 assert!(matches!(
2633 parse_command_line(b"gats 0"),
2634 CmdParseResult::ClientError(_)
2635 ));
2636 }
2637
2638 #[test]
2641 fn parse_flush_bad_arg() {
2642 assert!(matches!(
2643 parse_command_line(b"flush_all notanum"),
2644 CmdParseResult::ClientError(_)
2645 ));
2646 }
2647
2648 #[test]
2651 fn parse_delete_no_key() {
2652 assert!(matches!(
2653 parse_command_line(b"delete"),
2654 CmdParseResult::ClientError(_)
2655 ));
2656 }
2657
2658 #[test]
2659 fn parse_delete_too_many_args() {
2660 assert!(matches!(
2661 parse_command_line(b"delete k noreply extra"),
2662 CmdParseResult::ClientError(_)
2663 ));
2664 }
2665
2666 #[test]
2669 fn parse_set_bad_exptime() {
2670 assert!(matches!(
2671 parse_command_line(b"set k 0 notanum 5"),
2672 CmdParseResult::ClientError(_)
2673 ));
2674 }
2675
2676 #[test]
2677 fn parse_set_bad_bytes() {
2678 assert!(matches!(
2679 parse_command_line(b"set k 0 0 notanum"),
2680 CmdParseResult::ClientError(_)
2681 ));
2682 }
2683
2684 #[test]
2685 fn parse_set_too_many_args() {
2686 assert!(matches!(
2687 parse_command_line(b"set k 0 0 5 noreply extra"),
2688 CmdParseResult::ClientError(_)
2689 ));
2690 }
2691
2692 #[test]
2695 fn parse_append_too_few_args() {
2696 assert!(matches!(
2697 parse_command_line(b"append k"),
2698 CmdParseResult::ClientError(_)
2699 ));
2700 }
2701
2702 #[test]
2703 fn parse_append_too_many_args() {
2704 assert!(matches!(
2705 parse_command_line(b"append k 5 noreply extra"),
2706 CmdParseResult::ClientError(_)
2707 ));
2708 }
2709
2710 #[test]
2711 fn parse_append_bad_bytes() {
2712 assert!(matches!(
2713 parse_command_line(b"append k notanum"),
2714 CmdParseResult::ClientError(_)
2715 ));
2716 }
2717
2718 #[test]
2719 fn parse_append_bad_noreply_token() {
2720 assert!(matches!(
2721 parse_command_line(b"append k 5 garbage"),
2722 CmdParseResult::ClientError(_)
2723 ));
2724 }
2725
2726 #[test]
2729 fn parse_verbosity_noreply() {
2730 match parse_command_line(b"verbosity 2 noreply") {
2731 CmdParseResult::Ok(AsciiCmd::Verbosity { noreply }) => assert!(noreply),
2732 other => panic!("unexpected: {other:?}"),
2733 }
2734 }
2735
2736 #[test]
2739 fn parse_gets_multi() {
2740 match parse_command_line(b"gets a b c") {
2741 CmdParseResult::Ok(AsciiCmd::Retrieval {
2742 cmd: RetrievalOp::Gets,
2743 keys,
2744 ..
2745 }) => assert_eq!(keys.len(), 3),
2746 other => panic!("unexpected: {other:?}"),
2747 }
2748 }
2749
2750 #[test]
2751 fn parse_gets_no_keys() {
2752 assert!(matches!(
2753 parse_command_line(b"gets"),
2754 CmdParseResult::ClientError(_)
2755 ));
2756 }
2757
2758 #[test]
2761 fn data_block_for_cas() {
2762 let cmd = AsciiCmd::Cas {
2763 key: b"k",
2764 flags: 0,
2765 exptime: 0,
2766 bytes: 7,
2767 cas_unique: 1,
2768 noreply: true,
2769 };
2770 assert!(needs_data_block(&cmd));
2771 assert_eq!(data_block_len(&cmd), 7);
2772 assert!(is_noreply(&cmd));
2773 }
2774
2775 #[test]
2776 fn data_block_for_append_prepend() {
2777 let cmd = AsciiCmd::AppendPrepend {
2778 is_prepend: true,
2779 key: b"k",
2780 bytes: 3,
2781 noreply: false,
2782 };
2783 assert!(needs_data_block(&cmd));
2784 assert_eq!(data_block_len(&cmd), 3);
2785 assert!(!is_noreply(&cmd));
2786 }
2787
2788 #[test]
2789 fn is_noreply_for_non_noreply_variants() {
2790 assert!(!is_noreply(&AsciiCmd::Version));
2791 assert!(!is_noreply(&AsciiCmd::Quit));
2792 assert!(!is_noreply(&AsciiCmd::Stats { args: None }));
2793 assert!(!is_noreply(&AsciiCmd::Retrieval {
2794 cmd: RetrievalOp::Get,
2795 exptime: None,
2796 keys: vec![b"k"],
2797 }));
2798 }
2799
2800 #[test]
2801 fn data_block_len_zero_for_non_store() {
2802 assert_eq!(
2803 data_block_len(&AsciiCmd::Delete {
2804 key: b"k",
2805 noreply: false
2806 }),
2807 0
2808 );
2809 }
2810
2811 #[test]
2814 fn key_with_tab() {
2815 assert!(validate_key(b"has\ttab").is_err());
2816 }
2817
2818 #[test]
2819 fn key_with_del() {
2820 assert!(validate_key(b"has\x7fchar").is_err());
2821 }
2822
2823 #[test]
2824 fn key_high_bytes_ok() {
2825 assert!(validate_key(b"\x80\xff").is_ok());
2827 }
2828
2829 #[test]
2832 fn meta_with_tab_is_meta() {
2833 assert!(is_meta_command(b"mg\tkey"));
2834 }
2835}