Skip to main content

memcached_async/codec/
ascii.rs

1use bytes::{Buf, BufMut, Bytes, BytesMut};
2
3use crate::codec::DecodeOutcome;
4use crate::error::{Error, ErrorKind};
5use crate::response::Response;
6use crate::types::{
7    MetaCode, MetaFlags, MetaResponse, Op, Protocol, ReplyMode, Request, RequestMeta, StatLine,
8    ValueEntry,
9};
10
11/// ASCII/meta decode limits.
12#[derive(Debug, Clone, Copy)]
13pub struct AsciiLimits {
14    pub max_line_len: usize,
15    pub max_blob_len: usize,
16}
17
18impl Default for AsciiLimits {
19    fn default() -> Self {
20        Self {
21            max_line_len: 4096,
22            max_blob_len: 1 << 20,
23        }
24    }
25}
26
27#[derive(Debug, Clone)]
28struct BlobState {
29    len: usize,
30    req: Request,
31    meta: RequestMeta,
32}
33
34#[derive(Debug, Clone)]
35enum AsciiState {
36    Line,
37    Blob(Box<BlobState>),
38}
39
40/// Streaming ASCII/meta decoder.
41#[derive(Debug, Clone)]
42pub struct AsciiDecoder {
43    state: AsciiState,
44    discarding_line: bool,
45}
46
47impl Default for AsciiDecoder {
48    fn default() -> Self {
49        Self::new()
50    }
51}
52
53impl AsciiDecoder {
54    pub fn new() -> Self {
55        Self {
56            state: AsciiState::Line,
57            discarding_line: false,
58        }
59    }
60
61    pub fn decode(&mut self, buf: &mut BytesMut, limits: AsciiLimits) -> Option<DecodeOutcome> {
62        let state = std::mem::replace(&mut self.state, AsciiState::Line);
63        match state {
64            AsciiState::Line => {
65                self.state = AsciiState::Line;
66                self.decode_line(buf, limits)
67            }
68            AsciiState::Blob(blob) => {
69                let BlobState { len, mut req, meta } = *blob;
70                if buf.len() < len + 2 {
71                    self.state = AsciiState::Blob(Box::new(BlobState { len, req, meta }));
72                    return None;
73                }
74                let payload = buf.split_to(len).freeze();
75                let cr = buf.get_u8();
76                let lf = buf.get_u8();
77                if cr != b'\r' || lf != b'\n' {
78                    let response = Response::Error(Error::client("bad data chunk"));
79                    self.state = AsciiState::Line;
80                    return Some(DecodeOutcome::Response(meta, response));
81                }
82                req.value = Some(payload);
83                self.state = AsciiState::Line;
84                Some(DecodeOutcome::Request(req, meta))
85            }
86        }
87    }
88
89    fn decode_line(&mut self, buf: &mut BytesMut, limits: AsciiLimits) -> Option<DecodeOutcome> {
90        if self.discarding_line {
91            if let Some(pos) = find_crlf(buf) {
92                buf.advance(pos + 2);
93                self.discarding_line = false;
94                let meta = RequestMeta::ascii();
95                let response = Response::Error(Error::client("line too long"));
96                return Some(DecodeOutcome::Response(meta, response));
97            }
98            if buf.len() > limits.max_line_len {
99                buf.clear();
100            }
101            return None;
102        }
103
104        let pos = match find_crlf(buf) {
105            Some(pos) => pos,
106            None => {
107                if buf.len() > limits.max_line_len {
108                    self.discarding_line = true;
109                }
110                return None;
111            }
112        };
113
114        if pos > limits.max_line_len {
115            buf.advance(pos + 2);
116            let meta = RequestMeta::ascii();
117            let response = Response::Error(Error::client("line too long"));
118            return Some(DecodeOutcome::Response(meta, response));
119        }
120
121        let line = buf.split_to(pos).freeze();
122        buf.advance(2);
123
124        if line.is_empty() {
125            let meta = RequestMeta::ascii();
126            let response = Response::Error(Error::client("empty command"));
127            return Some(DecodeOutcome::Response(meta, response));
128        }
129
130        match parse_line(line, limits.max_blob_len) {
131            Ok(LineParse::Line(req, meta)) => Some(DecodeOutcome::Request(req, meta)),
132            Ok(LineParse::Blob { len, mut req, meta }) => {
133                if buf.len() >= len + 2 {
134                    let payload = buf.split_to(len).freeze();
135                    let cr = buf.get_u8();
136                    let lf = buf.get_u8();
137                    if cr != b'\r' || lf != b'\n' {
138                        let response = Response::Error(Error::client("bad data chunk"));
139                        return Some(DecodeOutcome::Response(meta, response));
140                    }
141                    req.value = Some(payload);
142                    return Some(DecodeOutcome::Request(req, meta));
143                }
144                self.state = AsciiState::Blob(Box::new(BlobState { len, req, meta }));
145                None
146            }
147            Err(err) => Some(DecodeOutcome::Response(
148                RequestMeta::ascii(),
149                Response::Error(err),
150            )),
151        }
152    }
153}
154
155enum LineParse {
156    Line(Request, RequestMeta),
157    Blob {
158        len: usize,
159        req: Request,
160        meta: RequestMeta,
161    },
162}
163
164fn parse_line(line: Bytes, max_blob_len: usize) -> Result<LineParse, Error> {
165    let tokens = split_tokens(&line);
166    if tokens.is_empty() {
167        return Err(Error::client("empty command"));
168    }
169
170    let cmd = tokens[0].as_ref();
171    let op = parse_op(cmd);
172    let mut meta = RequestMeta::ascii();
173    let mut req = Request::new(op);
174
175    match op {
176        Op::Get | Op::Gets => {
177            if tokens.len() < 2 {
178                return Err(Error::client("missing key"));
179            }
180            let keys = parse_keys(&tokens[1..])?;
181            if keys.len() == 1 {
182                req.key = Some(keys[0].clone());
183            } else {
184                req.keys = keys;
185            }
186        }
187        Op::Gat | Op::Gats => {
188            if tokens.len() < 3 {
189                return Err(Error::client("missing exptime or key"));
190            }
191            let exptime =
192                parse_i64(tokens[1].as_ref()).ok_or_else(|| Error::client("invalid exptime"))?;
193            req.exptime = Some(exptime);
194            let keys = parse_keys(&tokens[2..])?;
195            if keys.len() == 1 {
196                req.key = Some(keys[0].clone());
197            } else {
198                req.keys = keys;
199            }
200        }
201        Op::Set | Op::Add | Op::Replace | Op::Append | Op::Prepend | Op::Cas => {
202            let min = if matches!(op, Op::Cas) { 6 } else { 5 };
203            if tokens.len() < min {
204                return Err(Error::client("missing arguments"));
205            }
206            let mut end = tokens.len();
207            let mut reply = ReplyMode::Always;
208            if tokens.len() > min && tokens.last().map(|t| t.as_ref()) == Some(b"noreply") {
209                reply = ReplyMode::SuppressSuccess;
210                end -= 1;
211            }
212            if end != min {
213                return Err(Error::client("invalid arguments"));
214            }
215            let key = tokens[1].clone();
216            validate_key(&key)?;
217            req.key = Some(key);
218            req.flags =
219                Some(parse_u32(tokens[2].as_ref()).ok_or_else(|| Error::client("invalid flags"))?);
220            req.exptime = Some(
221                parse_i64(tokens[3].as_ref()).ok_or_else(|| Error::client("invalid exptime"))?,
222            );
223            let bytes =
224                parse_usize(tokens[4].as_ref()).ok_or_else(|| Error::client("invalid bytes"))?;
225            if bytes > max_blob_len {
226                return Err(Error::server("value too large"));
227            }
228            if matches!(op, Op::Cas) {
229                req.cas = Some(
230                    parse_u64(tokens[5].as_ref()).ok_or_else(|| Error::client("invalid cas"))?,
231                );
232            }
233            meta.reply = reply;
234            return Ok(LineParse::Blob {
235                len: bytes,
236                req,
237                meta,
238            });
239        }
240        Op::Delete => {
241            if tokens.len() < 2 {
242                return Err(Error::client("missing key"));
243            }
244            let mut reply = ReplyMode::Always;
245            if tokens.len() > 2 && tokens.last().map(|t| t.as_ref()) == Some(b"noreply") {
246                reply = ReplyMode::SuppressSuccess;
247                if tokens.len() != 3 {
248                    return Err(Error::client("invalid arguments"));
249                }
250            } else if tokens.len() != 2 {
251                return Err(Error::client("invalid arguments"));
252            }
253            let key = tokens[1].clone();
254            validate_key(&key)?;
255            req.key = Some(key);
256            meta.reply = reply;
257        }
258        Op::Incr | Op::Decr => {
259            if tokens.len() < 3 {
260                return Err(Error::client("missing arguments"));
261            }
262            let mut reply = ReplyMode::Always;
263            if tokens.len() > 3 && tokens.last().map(|t| t.as_ref()) == Some(b"noreply") {
264                reply = ReplyMode::SuppressSuccess;
265                if tokens.len() != 4 {
266                    return Err(Error::client("invalid arguments"));
267                }
268            } else if tokens.len() != 3 {
269                return Err(Error::client("invalid arguments"));
270            }
271            let key = tokens[1].clone();
272            validate_key(&key)?;
273            req.key = Some(key);
274            req.delta =
275                Some(parse_u64(tokens[2].as_ref()).ok_or_else(|| Error::client("invalid delta"))?);
276            meta.reply = reply;
277        }
278        Op::Touch => {
279            if tokens.len() < 3 {
280                return Err(Error::client("missing arguments"));
281            }
282            let mut reply = ReplyMode::Always;
283            if tokens.len() > 3 && tokens.last().map(|t| t.as_ref()) == Some(b"noreply") {
284                reply = ReplyMode::SuppressSuccess;
285                if tokens.len() != 4 {
286                    return Err(Error::client("invalid arguments"));
287                }
288            } else if tokens.len() != 3 {
289                return Err(Error::client("invalid arguments"));
290            }
291            let key = tokens[1].clone();
292            validate_key(&key)?;
293            req.key = Some(key);
294            req.exptime = Some(
295                parse_i64(tokens[2].as_ref()).ok_or_else(|| Error::client("invalid exptime"))?,
296            );
297            meta.reply = reply;
298        }
299        Op::Stats => {
300            if tokens.len() > 1 {
301                let keys = parse_keys(&tokens[1..])?;
302                req.keys = keys;
303            }
304        }
305        Op::Version | Op::Quit => {
306            if tokens.len() != 1 {
307                return Err(Error::client("invalid arguments"));
308            }
309        }
310        Op::MetaGet
311        | Op::MetaSet
312        | Op::MetaDelete
313        | Op::MetaArithmetic
314        | Op::MetaDebug
315        | Op::MetaNoop => {
316            meta.protocol = Protocol::Meta;
317            if op == Op::MetaNoop {
318                if tokens.len() != 1 {
319                    return Err(Error::client("invalid arguments"));
320                }
321                return Ok(LineParse::Line(req, meta));
322            }
323            if tokens.len() < 2 {
324                return Err(Error::client("missing key"));
325            }
326            let mut idx = 2;
327            let mut datalen = None;
328            if op == Op::MetaSet {
329                if tokens.len() < 3 {
330                    return Err(Error::client("missing data length"));
331                }
332                datalen = Some(
333                    parse_usize(tokens[2].as_ref())
334                        .ok_or_else(|| Error::client("invalid data length"))?,
335                );
336                idx = 3;
337            }
338            let meta_flags = parse_meta_flags(&tokens[idx..]);
339            if meta_flags.has(b'q') {
340                meta.reply = if op == Op::MetaGet {
341                    ReplyMode::SuppressMiss
342                } else {
343                    ReplyMode::SuppressSuccess
344                };
345            }
346            let mut key = tokens[1].clone();
347            if meta_flags.has(b'b') {
348                let decoded = decode_base64(key.as_ref())?;
349                if decoded.len() > 250 {
350                    return Err(Error::client("key too long"));
351                }
352                key = Bytes::from(decoded);
353            } else {
354                validate_key(&key)?;
355            }
356            req.key = Some(key);
357            if let Some(delta) = meta_flags
358                .token(b'D')
359                .and_then(|token| parse_u64(token.as_ref()))
360            {
361                req.delta = Some(delta);
362            }
363            if let Some(initial) = meta_flags
364                .token(b'J')
365                .and_then(|token| parse_u64(token.as_ref()))
366            {
367                req.initial = Some(initial);
368            }
369            if let Some(cas) = meta_flags
370                .token(b'C')
371                .and_then(|token| parse_u64(token.as_ref()))
372            {
373                req.cas = Some(cas);
374            }
375            req.meta = Some(meta_flags.clone());
376            if op == Op::MetaSet {
377                let mut length = datalen.unwrap_or(0);
378                if let Some(slen) = meta_flags
379                    .token(b'S')
380                    .and_then(|token| parse_usize(token.as_ref()))
381                {
382                    length = slen;
383                }
384                if length > max_blob_len {
385                    return Err(Error::server("value too large"));
386                }
387                if length == 0 {
388                    return Ok(LineParse::Line(req, meta));
389                }
390                return Ok(LineParse::Blob {
391                    len: length,
392                    req,
393                    meta,
394                });
395            }
396        }
397        Op::Noop | Op::Unknown => {
398            if op == Op::Unknown {
399                req.op = Op::Unknown;
400            }
401        }
402    }
403
404    Ok(LineParse::Line(req, meta))
405}
406
407fn parse_op(cmd: &[u8]) -> Op {
408    match cmd {
409        b"get" => Op::Get,
410        b"gets" => Op::Gets,
411        b"gat" => Op::Gat,
412        b"gats" => Op::Gats,
413        b"set" => Op::Set,
414        b"add" => Op::Add,
415        b"replace" => Op::Replace,
416        b"append" => Op::Append,
417        b"prepend" => Op::Prepend,
418        b"cas" => Op::Cas,
419        b"delete" => Op::Delete,
420        b"incr" => Op::Incr,
421        b"decr" => Op::Decr,
422        b"touch" => Op::Touch,
423        b"stats" => Op::Stats,
424        b"version" => Op::Version,
425        b"quit" => Op::Quit,
426        b"mg" => Op::MetaGet,
427        b"ms" => Op::MetaSet,
428        b"md" => Op::MetaDelete,
429        b"ma" => Op::MetaArithmetic,
430        b"me" => Op::MetaDebug,
431        b"mn" => Op::MetaNoop,
432        _ => Op::Unknown,
433    }
434}
435
436fn parse_keys(tokens: &[Bytes]) -> Result<Vec<Bytes>, Error> {
437    let mut keys = Vec::with_capacity(tokens.len());
438    for token in tokens {
439        validate_key(token)?;
440        keys.push(token.clone());
441    }
442    Ok(keys)
443}
444
445fn validate_key(key: &Bytes) -> Result<(), Error> {
446    if key.is_empty() {
447        return Err(Error::client("empty key"));
448    }
449    if key.len() > 250 {
450        return Err(Error::client("key too long"));
451    }
452    for &b in key.as_ref() {
453        if b <= b' ' || b == 0x7f {
454            return Err(Error::client("invalid key"));
455        }
456    }
457    Ok(())
458}
459
460fn split_tokens(line: &Bytes) -> Vec<Bytes> {
461    let mut tokens = Vec::new();
462    let mut start = 0;
463    let bytes = line.as_ref();
464    while start < bytes.len() {
465        while start < bytes.len() && bytes[start] == b' ' {
466            start += 1;
467        }
468        if start >= bytes.len() {
469            break;
470        }
471        let mut end = start;
472        while end < bytes.len() && bytes[end] != b' ' {
473            end += 1;
474        }
475        tokens.push(line.slice(start..end));
476        start = end;
477    }
478    tokens
479}
480
481fn find_crlf(buf: &BytesMut) -> Option<usize> {
482    let bytes = buf.as_ref();
483    if bytes.len() < 2 {
484        return None;
485    }
486    let mut i = 0;
487    while i + 1 < bytes.len() {
488        if bytes[i] == b'\r' && bytes[i + 1] == b'\n' {
489            return Some(i);
490        }
491        i += 1;
492    }
493    None
494}
495
496fn parse_u32(token: &[u8]) -> Option<u32> {
497    parse_u64(token).and_then(|value| u32::try_from(value).ok())
498}
499
500fn parse_usize(token: &[u8]) -> Option<usize> {
501    parse_u64(token).and_then(|value| usize::try_from(value).ok())
502}
503
504fn parse_u64(token: &[u8]) -> Option<u64> {
505    if token.is_empty() {
506        return None;
507    }
508    let mut value: u64 = 0;
509    for &b in token {
510        if !b.is_ascii_digit() {
511            return None;
512        }
513        value = value.checked_mul(10)?;
514        value = value.checked_add((b - b'0') as u64)?;
515    }
516    Some(value)
517}
518
519fn parse_i64(token: &[u8]) -> Option<i64> {
520    if token.is_empty() {
521        return None;
522    }
523    let (neg, rest) = if token[0] == b'-' {
524        (true, &token[1..])
525    } else {
526        (false, token)
527    };
528    let value = parse_u64(rest)? as i64;
529    if neg { Some(-value) } else { Some(value) }
530}
531
532fn parse_meta_flags(tokens: &[Bytes]) -> MetaFlags {
533    let mut ordered = Vec::with_capacity(tokens.len());
534    for token in tokens {
535        if token.is_empty() {
536            continue;
537        }
538        let code = token.as_ref()[0];
539        let rest = if token.len() > 1 {
540            Some(token.slice(1..))
541        } else {
542            None
543        };
544        ordered.push(crate::types::MetaFlag { code, token: rest });
545    }
546    MetaFlags::new(ordered)
547}
548
549fn decode_base64(input: &[u8]) -> Result<Vec<u8>, Error> {
550    if !input.len().is_multiple_of(4) {
551        return Err(Error::client("invalid base64"));
552    }
553    let mut out = Vec::with_capacity(input.len() / 4 * 3);
554    let mut i = 0;
555    while i < input.len() {
556        let a = decode_base64_val(input[i])?;
557        let b = decode_base64_val(input[i + 1])?;
558        let c = input[i + 2];
559        let d = input[i + 3];
560        let c_val = if c == b'=' {
561            None
562        } else {
563            Some(decode_base64_val(c)?)
564        };
565        let d_val = if d == b'=' {
566            None
567        } else {
568            Some(decode_base64_val(d)?)
569        };
570
571        out.push((a << 2) | (b >> 4));
572        if let Some(c_val) = c_val {
573            out.push((b << 4) | (c_val >> 2));
574            if let Some(d_val) = d_val {
575                out.push((c_val << 6) | d_val);
576            }
577        }
578        i += 4;
579    }
580    Ok(out)
581}
582
583fn decode_base64_val(byte: u8) -> Result<u8, Error> {
584    match byte {
585        b'A'..=b'Z' => Ok(byte - b'A'),
586        b'a'..=b'z' => Ok(byte - b'a' + 26),
587        b'0'..=b'9' => Ok(byte - b'0' + 52),
588        b'+' => Ok(62),
589        b'/' => Ok(63),
590        b'=' => Ok(0),
591        _ => Err(Error::client("invalid base64")),
592    }
593}
594
595pub fn should_suppress_ascii(meta: RequestMeta, response: &Response) -> bool {
596    match meta.reply {
597        ReplyMode::Always => false,
598        ReplyMode::SuppressMiss => {
599            if let Response::Meta(meta_resp) = response {
600                meta_resp.code.is_miss()
601            } else {
602                false
603            }
604        }
605        ReplyMode::SuppressSuccess => !matches!(response, Response::Error(_)),
606        ReplyMode::QuietBuffered => false,
607    }
608}
609
610pub fn should_suppress_meta(meta: RequestMeta, response: &Response) -> bool {
611    match meta.reply {
612        ReplyMode::Always => false,
613        ReplyMode::SuppressMiss => {
614            if let Response::Meta(meta_resp) = response {
615                meta_resp.code.is_miss()
616            } else {
617                false
618            }
619        }
620        ReplyMode::SuppressSuccess => {
621            if let Response::Meta(meta_resp) = response {
622                meta_resp.code.is_success()
623            } else {
624                false
625            }
626        }
627        ReplyMode::QuietBuffered => false,
628    }
629}
630
631pub fn encode_ascii_response(
632    req: &Request,
633    meta: RequestMeta,
634    response: &Response,
635    out: &mut BytesMut,
636) -> bool {
637    if should_suppress_ascii(meta, response) {
638        return false;
639    }
640    match response {
641        Response::Stored => out.extend_from_slice(b"STORED\r\n"),
642        Response::NotStored => out.extend_from_slice(b"NOT_STORED\r\n"),
643        Response::Exists => out.extend_from_slice(b"EXISTS\r\n"),
644        Response::NotFound => out.extend_from_slice(b"NOT_FOUND\r\n"),
645        Response::Deleted => out.extend_from_slice(b"DELETED\r\n"),
646        Response::Touched => out.extend_from_slice(b"TOUCHED\r\n"),
647        Response::Numeric(value) => {
648            write_u64(out, *value);
649            out.extend_from_slice(b"\r\n");
650        }
651        Response::Value(entry) => {
652            let include_cas = matches!(req.op, Op::Gets | Op::Gats);
653            encode_value_entry(entry, include_cas, out);
654            out.extend_from_slice(b"END\r\n");
655        }
656        Response::Values(entries) => {
657            let include_cas = matches!(req.op, Op::Gets | Op::Gats);
658            for entry in entries {
659                encode_value_entry(entry, include_cas, out);
660            }
661            out.extend_from_slice(b"END\r\n");
662        }
663        Response::Stats(lines) => {
664            for line in lines {
665                encode_stat_line(line, out);
666            }
667            out.extend_from_slice(b"END\r\n");
668        }
669        Response::Version(version) => {
670            out.extend_from_slice(b"VERSION ");
671            out.extend_from_slice(version);
672            out.extend_from_slice(b"\r\n");
673        }
674        Response::Noop => {
675            if req.op == Op::MetaNoop {
676                out.extend_from_slice(b"MN\r\n");
677            }
678        }
679        Response::Error(err) => match err.kind {
680            ErrorKind::UnknownCommand => out.extend_from_slice(b"ERROR\r\n"),
681            ErrorKind::Client => {
682                out.extend_from_slice(b"CLIENT_ERROR ");
683                out.extend_from_slice(err.message.as_ref());
684                out.extend_from_slice(b"\r\n");
685            }
686            ErrorKind::Server => {
687                out.extend_from_slice(b"SERVER_ERROR ");
688                out.extend_from_slice(err.message.as_ref());
689                out.extend_from_slice(b"\r\n");
690            }
691        },
692        Response::Meta(_) | Response::ValuesStream(_) | Response::StatsStream(_) => {}
693    }
694    true
695}
696
697pub fn encode_meta_response(
698    req: &Request,
699    meta: RequestMeta,
700    response: &MetaResponse,
701    out: &mut BytesMut,
702) {
703    if should_suppress_meta(meta, &Response::Meta(response.clone())) {
704        return;
705    }
706    out.extend_from_slice(response.code.as_bytes());
707    if response.code == MetaCode::Va {
708        out.extend_from_slice(b" ");
709        let size = response
710            .size
711            .or_else(|| response.value.as_ref().map(|value| value.len()))
712            .unwrap_or(0);
713        write_usize(out, size);
714    }
715
716    if let Some(flags) = req.meta.as_ref() {
717        for flag in &flags.ordered {
718            append_meta_token(req, response, flag.code, out);
719        }
720    }
721
722    if let Some(win) = response.extra.won {
723        out.extend_from_slice(match win {
724            crate::types::WinState::Won => b" W",
725            crate::types::WinState::AlreadyWon => b" Z",
726        });
727    }
728    if response.extra.stale {
729        out.extend_from_slice(b" X");
730    }
731
732    out.extend_from_slice(b"\r\n");
733
734    let want_value = req
735        .meta
736        .as_ref()
737        .map(|flags| flags.has(b'v'))
738        .unwrap_or(false);
739    if want_value && let Some(value) = response.value.as_ref() {
740        out.extend_from_slice(value);
741        out.extend_from_slice(b"\r\n");
742    }
743}
744
745pub fn encode_meta_debug(
746    req: &Request,
747    lines: impl IntoIterator<Item = StatLine>,
748    out: &mut BytesMut,
749) {
750    let key = match req.key.as_ref() {
751        Some(key) => key.as_ref(),
752        None => b"",
753    };
754    out.extend_from_slice(b"ME ");
755    out.extend_from_slice(key);
756    for line in lines {
757        out.extend_from_slice(b" ");
758        out.extend_from_slice(line.key.as_ref());
759        out.extend_from_slice(b"=");
760        out.extend_from_slice(line.value.as_ref());
761    }
762    out.extend_from_slice(b"\r\n");
763}
764
765pub fn encode_value_entry(entry: &ValueEntry, include_cas: bool, out: &mut BytesMut) {
766    out.extend_from_slice(b"VALUE ");
767    out.extend_from_slice(entry.key.as_ref());
768    out.extend_from_slice(b" ");
769    write_u32(out, entry.flags);
770    out.extend_from_slice(b" ");
771    write_usize(out, entry.value.len());
772    if include_cas {
773        out.extend_from_slice(b" ");
774        write_u64(out, entry.cas.unwrap_or(0));
775    }
776    out.extend_from_slice(b"\r\n");
777    out.extend_from_slice(entry.value.as_ref());
778    out.extend_from_slice(b"\r\n");
779}
780
781pub fn encode_stat_line(line: &StatLine, out: &mut BytesMut) {
782    out.extend_from_slice(b"STAT ");
783    out.extend_from_slice(line.key.as_ref());
784    out.extend_from_slice(b" ");
785    out.extend_from_slice(line.value.as_ref());
786    out.extend_from_slice(b"\r\n");
787}
788
789fn append_meta_token(req: &Request, response: &MetaResponse, code: u8, out: &mut BytesMut) {
790    match code {
791        b'O' => {
792            if let Some(token) = req.meta.as_ref().and_then(|flags| flags.token(b'O')) {
793                out.extend_from_slice(b" O");
794                out.extend_from_slice(token.as_ref());
795            }
796        }
797        b'k' => {
798            if let Some(key) = req.key.as_ref() {
799                out.extend_from_slice(b" k");
800                out.extend_from_slice(key.as_ref());
801            }
802        }
803        b'c' => {
804            if let Some(cas) = response.cas {
805                out.extend_from_slice(b" c");
806                write_u64(out, cas);
807            }
808        }
809        b't' => {
810            if let Some(ttl) = response.ttl {
811                out.extend_from_slice(b" t");
812                write_i64(out, ttl);
813            }
814        }
815        b'f' => {
816            if let Some(flags) = response.flags {
817                out.extend_from_slice(b" f");
818                write_u32(out, flags);
819            }
820        }
821        b's' => {
822            if let Some(size) = response.size {
823                out.extend_from_slice(b" s");
824                write_usize(out, size);
825            }
826        }
827        b'h' => {
828            if let Some(hit) = response.hit {
829                out.extend_from_slice(b" h");
830                out.extend_from_slice(if hit { b"1" } else { b"0" });
831            }
832        }
833        b'l' => {
834            if let Some(last) = response.last_access {
835                out.extend_from_slice(b" l");
836                write_u64(out, last);
837            }
838        }
839        _ => {}
840    }
841}
842
843fn write_u32(out: &mut BytesMut, value: u32) {
844    write_u64(out, value as u64)
845}
846
847fn write_usize(out: &mut BytesMut, value: usize) {
848    write_u64(out, value as u64)
849}
850
851fn write_u64(out: &mut BytesMut, mut value: u64) {
852    let mut buf = [0u8; 20];
853    let mut i = buf.len();
854    if value == 0 {
855        out.put_u8(b'0');
856        return;
857    }
858    while value > 0 {
859        i -= 1;
860        buf[i] = b'0' + (value % 10) as u8;
861        value /= 10;
862    }
863    out.extend_from_slice(&buf[i..]);
864}
865
866fn write_i64(out: &mut BytesMut, value: i64) {
867    if value < 0 {
868        out.put_u8(b'-');
869        write_u64(out, (-value) as u64);
870    } else {
871        write_u64(out, value as u64);
872    }
873}
874
875#[cfg(test)]
876mod tests {
877    use super::*;
878    use bytes::BytesMut;
879
880    #[test]
881    fn decode_set_with_value() {
882        let mut decoder = AsciiDecoder::new();
883        let mut buf = BytesMut::from("set key 1 10 5\r\nhello\r\n");
884        let limits = AsciiLimits::default();
885
886        let outcome = decoder.decode(&mut buf, limits);
887        let (req, meta) = match outcome {
888            Some(DecodeOutcome::Request(req, meta)) => (req, meta),
889            _ => panic!("unexpected decode outcome"),
890        };
891        assert_eq!(req.op, Op::Set);
892        assert_eq!(req.value.unwrap(), Bytes::from_static(b"hello"));
893        assert_eq!(meta.reply, ReplyMode::Always);
894    }
895
896    #[test]
897    fn decode_multi_get() {
898        let mut decoder = AsciiDecoder::new();
899        let mut buf = BytesMut::from("get k1 k2\r\n");
900        let outcome = decoder.decode(&mut buf, AsciiLimits::default());
901        let (req, _) = match outcome {
902            Some(DecodeOutcome::Request(req, meta)) => (req, meta),
903            _ => panic!("unexpected decode outcome"),
904        };
905        assert_eq!(req.op, Op::Get);
906        assert_eq!(req.keys.len(), 2);
907    }
908
909    #[test]
910    fn decode_meta_set_s_token() {
911        let mut decoder = AsciiDecoder::new();
912        let mut buf = BytesMut::from("ms key 5 S3\r\nabc\r\n");
913        let limits = AsciiLimits::default();
914
915        let outcome = decoder.decode(&mut buf, limits);
916        let (req, meta) = match outcome {
917            Some(DecodeOutcome::Request(req, meta)) => (req, meta),
918            _ => panic!("unexpected decode outcome"),
919        };
920        assert_eq!(meta.protocol, Protocol::Meta);
921        assert_eq!(req.value.unwrap(), Bytes::from_static(b"abc"));
922    }
923}