red_couch/
protocol.rs

1//! Pure protocol types, parsing, and response building for the memcached
2//! binary protocol.  This module has **no** dependency on `redis-module` and
3//! can be tested in a normal `cargo test` process.
4
5use byteorder::{BigEndian, ByteOrder};
6use std::io::{self, Write};
7
8// ── Magic bytes ──────────────────────────────────────────────────────
9/// Magic byte for a binary-protocol **request** frame (`0x80`).
10pub const MAGIC_REQ: u8 = 0x80;
11/// Magic byte for a binary-protocol **response** frame (`0x81`).
12pub const MAGIC_RES: u8 = 0x81;
13
14// ── Status codes ─────────────────────────────────────────────────────
15/// Success status (`0x0000`).
16pub const ST_OK: u16 = 0x0000;
17/// Key not found (`0x0001`).
18pub const ST_NF: u16 = 0x0001;
19/// Key exists (used by `ADD` when key already present) (`0x0002`).
20pub const ST_IX: u16 = 0x0002;
21/// Invalid arguments (`0x0004`).
22pub const ST_ARGS: u16 = 0x0004;
23/// Item not stored (CAS mismatch or `REPLACE` on missing key) (`0x0005`).
24pub const ST_NOT_STORED: u16 = 0x0005;
25/// Authentication error (`0x0020`).
26pub const ST_AUTH_ERROR: u16 = 0x0020;
27/// Authentication continue (multi-step SASL) (`0x0021`).
28pub const ST_AUTH_CONTINUE: u16 = 0x0021;
29/// Unknown command (`0x0081`).
30pub const ST_UNK: u16 = 0x0081;
31
32// ── Size limits ─────────────────────────────────────────────────────
33/// Maximum allowed body length per frame.  Memcached's default
34/// `item_size_max` is 1 MiB; we allow up to 20 MiB to be generous
35/// while still preventing multi-gigabyte allocations from a single
36/// malicious or buggy frame.
37pub const MAX_BODY_LEN: u32 = 20 * 1024 * 1024; // 20 MiB
38
39/// Maximum allowed key length.  The memcached binary protocol uses a
40/// u16 for key_len (max 65535), but the traditional memcached limit is
41/// 250 bytes.  We enforce the 250-byte limit for compatibility.
42pub const MAX_KEY_LEN: u16 = 250;
43
44// ── CAS policy ──────────────────────────────────────────────────────
45/// CAS value used in error and control responses.
46///
47/// CAS is tracked per-item via a Redis-backed monotonic counter
48/// (`redcouch:sys:cas_counter`).  Every mutation generates a new CAS
49/// value from this counter and stores it in the item's hash field `c`.
50pub const CAS_ZERO: u64 = 0;
51
52// ── Opcodes ──────────────────────────────────────────────────────────
53/// Memcached binary-protocol opcode.
54///
55/// Covers the full set of opcodes supported by the RedCouch bridge,
56/// including quiet variants, SASL auth, stats, and touch/GAT commands.
57#[repr(u8)]
58#[derive(Copy, Clone, Debug, PartialEq, Eq)]
59pub enum Opcode {
60    Get = 0x00,
61    Set = 0x01,
62    Add = 0x02,
63    Replace = 0x03,
64    Delete = 0x04,
65    Increment = 0x05,
66    Decrement = 0x06,
67    Quit = 0x07,
68    Flush = 0x08,
69    GetQ = 0x09,
70    Noop = 0x0a,
71    Version = 0x0b,
72    GetK = 0x0c,
73    GetKQ = 0x0d,
74    Append = 0x0e,
75    Prepend = 0x0f,
76    SetQ = 0x11,
77    AddQ = 0x12,
78    ReplaceQ = 0x13,
79    DeleteQ = 0x14,
80    IncrementQ = 0x15,
81    DecrementQ = 0x16,
82    QuitQ = 0x17,
83    FlushQ = 0x18,
84    AppendQ = 0x19,
85    PrependQ = 0x1a,
86    Stat = 0x10,
87    Verbosity = 0x1b,
88    Touch = 0x1c,
89    GAT = 0x1d,
90    GATQ = 0x1e,
91    SaslListMechs = 0x20,
92    SaslAuth = 0x21,
93    SaslStep = 0x22,
94}
95
96impl Opcode {
97    /// Parse a raw opcode byte into an [`Opcode`], returning `None` for
98    /// unrecognised values.
99    pub fn parse(b: u8) -> Option<Self> {
100        use Opcode::*;
101        Some(match b {
102            0x00 => Get,
103            0x01 => Set,
104            0x02 => Add,
105            0x03 => Replace,
106            0x04 => Delete,
107            0x05 => Increment,
108            0x06 => Decrement,
109            0x07 => Quit,
110            0x08 => Flush,
111            0x09 => GetQ,
112            0x0a => Noop,
113            0x0b => Version,
114            0x0c => GetK,
115            0x0d => GetKQ,
116            0x0e => Append,
117            0x0f => Prepend,
118            0x11 => SetQ,
119            0x12 => AddQ,
120            0x13 => ReplaceQ,
121            0x14 => DeleteQ,
122            0x15 => IncrementQ,
123            0x16 => DecrementQ,
124            0x17 => QuitQ,
125            0x18 => FlushQ,
126            0x19 => AppendQ,
127            0x1a => PrependQ,
128            0x10 => Stat,
129            0x1b => Verbosity,
130            0x1c => Touch,
131            0x1d => GAT,
132            0x1e => GATQ,
133            0x20 => SaslListMechs,
134            0x21 => SaslAuth,
135            0x22 => SaslStep,
136            _ => return None,
137        })
138    }
139
140    /// Returns `true` for quiet variants that suppress certain responses.
141    /// GET quiet variants suppress miss responses; mutation quiet variants
142    /// suppress success responses (errors are still sent).
143    pub fn is_quiet(self) -> bool {
144        use Opcode::*;
145        matches!(
146            self,
147            GetQ | GetKQ
148                | SetQ
149                | AddQ
150                | ReplaceQ
151                | DeleteQ
152                | IncrementQ
153                | DecrementQ
154                | QuitQ
155                | FlushQ
156                | AppendQ
157                | PrependQ
158                | GATQ
159        )
160    }
161
162    /// Returns `true` for GETK/GETKQ/GAT/GATQ which echo the key in the response.
163    pub fn includes_key(self) -> bool {
164        matches!(
165            self,
166            Opcode::GetK | Opcode::GetKQ | Opcode::GAT | Opcode::GATQ
167        )
168    }
169
170    /// Returns the "loud" base opcode for a quiet variant, or self if
171    /// already loud.  Useful for grouping quiet and loud variants in
172    /// match arms.
173    pub fn base(self) -> Self {
174        use Opcode::*;
175        match self {
176            SetQ => Set,
177            AddQ => Add,
178            ReplaceQ => Replace,
179            DeleteQ => Delete,
180            IncrementQ => Increment,
181            DecrementQ => Decrement,
182            QuitQ => Quit,
183            FlushQ => Flush,
184            GetQ => Get,
185            GetKQ => GetK,
186            AppendQ => Append,
187            PrependQ => Prepend,
188            GATQ => GAT,
189            other => other,
190        }
191    }
192}
193
194// ── Request header ───────────────────────────────────────────────────
195/// Length of a binary-protocol request/response header in bytes.
196pub const HEADER_LEN: usize = 24;
197
198/// Parsed memcached binary-protocol request header.
199///
200/// Contains both the parsed [`Opcode`] (if recognised) and the raw
201/// opcode byte, plus all fixed-width header fields.
202#[derive(Debug)]
203pub struct Header {
204    /// Parsed opcode, or `None` if the opcode byte is not recognised.
205    pub opcode: Option<Opcode>,
206    /// Raw opcode byte from the wire — always available even when the
207    /// opcode is unknown, so we can echo it in error responses.
208    pub opcode_byte: u8,
209    pub key_len: u16,
210    pub extras_len: u8,
211    pub body_len: u32,
212    pub opaque: u32,
213    pub cas: u64,
214}
215
216impl Header {
217    /// Parse a request header from the front of `buf`.
218    ///
219    /// Returns `None` only when the buffer is too short to contain a
220    /// header.  A wrong magic byte or unknown opcode is reported via
221    /// [`ParseResult`] by [`try_parse_request`].
222    pub fn parse(buf: &[u8]) -> Option<Self> {
223        if buf.len() < HEADER_LEN {
224            return None;
225        }
226        Some(Self {
227            opcode: Opcode::parse(buf[1]),
228            opcode_byte: buf[1],
229            key_len: BigEndian::read_u16(&buf[2..4]),
230            extras_len: buf[4],
231            body_len: BigEndian::read_u32(&buf[8..12]),
232            opaque: BigEndian::read_u32(&buf[12..16]),
233            cas: BigEndian::read_u64(&buf[16..24]),
234        })
235    }
236}
237
238// ── Parsed request ───────────────────────────────────────────────────
239/// A fully parsed binary-protocol request, borrowing from the input buffer.
240///
241/// Produced by [`try_parse_request`] on a successful parse.
242#[derive(Debug)]
243pub struct Request<'a> {
244    /// The request header.
245    pub hdr: Header,
246    /// Extra data (e.g. flags + expiry for store commands).
247    pub extras: &'a [u8],
248    /// The key bytes.
249    pub key: &'a [u8],
250    /// The value/body bytes.
251    pub value: &'a [u8],
252}
253
254/// Outcome of [`try_parse_request`].
255#[derive(Debug, PartialEq, Eq)]
256pub enum ParseResult<T> {
257    /// A complete, well-formed frame was consumed.
258    Ok(T),
259    /// Not enough bytes yet — caller should read more data.
260    Incomplete,
261    /// The first byte is not MAGIC_REQ (0x80).  The frame is
262    /// unrecoverable; the caller should close the connection.
263    BadMagic,
264    /// The header was parseable but `extras_len + key_len > body_len`.
265    /// The caller should skip `bytes_to_skip` bytes and respond with
266    /// an error.
267    MalformedFrame {
268        opaque: u32,
269        opcode_byte: u8,
270        bytes_to_skip: usize,
271    },
272    /// The frame header declares a body_len that exceeds MAX_BODY_LEN
273    /// or a key_len that exceeds MAX_KEY_LEN.  The connection should be
274    /// closed because we cannot safely skip past a potentially huge body
275    /// without reading and discarding it.
276    OversizedFrame { opaque: u32, opcode_byte: u8 },
277}
278
279/// Try to parse one complete request from `buf`.
280///
281/// Returns a [`ParseResult`] that distinguishes "need more data"
282/// (`Incomplete`), "unrecoverable framing error" (`BadMagic`), and
283/// "parseable header but invalid body layout" (`MalformedFrame`) from
284/// a successful parse (`Ok`).
285pub fn try_parse_request(buf: &[u8]) -> ParseResult<(Request<'_>, usize)> {
286    if buf.is_empty() {
287        return ParseResult::Incomplete;
288    }
289    // Check magic before anything else.
290    if buf[0] != MAGIC_REQ {
291        return ParseResult::BadMagic;
292    }
293    let hdr = match Header::parse(buf) {
294        Some(h) => h,
295        None => return ParseResult::Incomplete,
296    };
297    // Reject oversized frames before attempting to buffer them.
298    if hdr.body_len > MAX_BODY_LEN || hdr.key_len > MAX_KEY_LEN {
299        return ParseResult::OversizedFrame {
300            opaque: hdr.opaque,
301            opcode_byte: hdr.opcode_byte,
302        };
303    }
304    let total = HEADER_LEN + hdr.body_len as usize;
305    if buf.len() < total {
306        return ParseResult::Incomplete;
307    }
308    let extras_end = HEADER_LEN + hdr.extras_len as usize;
309    let key_end = extras_end + hdr.key_len as usize;
310    if key_end > total || extras_end > total {
311        return ParseResult::MalformedFrame {
312            opaque: hdr.opaque,
313            opcode_byte: hdr.opcode_byte,
314            bytes_to_skip: total,
315        };
316    }
317    ParseResult::Ok((
318        Request {
319            hdr,
320            extras: &buf[HEADER_LEN..extras_end],
321            key: &buf[extras_end..key_end],
322            value: &buf[key_end..total],
323        },
324        total,
325    ))
326}
327
328/// Legacy convenience wrapper — returns `None` for any non-Ok result.
329/// Prefer [`try_parse_request`] in new code.
330pub fn parse_request(buf: &[u8]) -> Option<(Request<'_>, usize)> {
331    match try_parse_request(buf) {
332        ParseResult::Ok(pair) => Some(pair),
333        _ => None,
334    }
335}
336
337// ── Response building ────────────────────────────────────────────────
338
339/// Groups the protocol-level metadata fields that every binary response
340/// carries.  Using a struct avoids exceeding clippy's argument limit on
341/// the response-writing functions.
342pub struct ResponseMeta {
343    /// Status code (e.g. [`ST_OK`], [`ST_NF`]).
344    pub status: u16,
345    /// Opaque value echoed from the request.
346    pub opaque: u32,
347    /// CAS value for the response.
348    pub cas: u64,
349}
350
351/// Write a complete binary-protocol response to `w` using a raw opcode byte.
352/// This is the low-level writer; prefer [`write_response`] when you have
353/// a known `Opcode`.
354pub fn write_raw_response(
355    w: &mut impl Write,
356    opcode_byte: u8,
357    meta: &ResponseMeta,
358    extras: &[u8],
359    key: &[u8],
360    value: &[u8],
361) -> io::Result<()> {
362    let total_body = extras.len() as u32 + key.len() as u32 + value.len() as u32;
363    let mut hdr = [0u8; HEADER_LEN];
364    hdr[0] = MAGIC_RES;
365    hdr[1] = opcode_byte;
366    BigEndian::write_u16(&mut hdr[2..4], key.len() as u16);
367    hdr[4] = extras.len() as u8;
368    BigEndian::write_u16(&mut hdr[6..8], meta.status);
369    BigEndian::write_u32(&mut hdr[8..12], total_body);
370    BigEndian::write_u32(&mut hdr[12..16], meta.opaque);
371    BigEndian::write_u64(&mut hdr[16..24], meta.cas);
372    w.write_all(&hdr)?;
373    w.write_all(extras)?;
374    w.write_all(key)?;
375    w.write_all(value)?;
376    Ok(())
377}
378
379/// Write a complete binary-protocol response to `w`.
380pub fn write_response(
381    w: &mut impl Write,
382    opcode: Opcode,
383    meta: &ResponseMeta,
384    extras: &[u8],
385    key: &[u8],
386    value: &[u8],
387) -> io::Result<()> {
388    write_raw_response(w, opcode as u8, meta, extras, key, value)
389}
390
391/// Convenience: write a simple response with no extras or key.
392pub fn write_simple_response(
393    w: &mut impl Write,
394    opcode: Opcode,
395    status: u16,
396    opaque: u32,
397    cas: u64,
398    body: &[u8],
399) -> io::Result<()> {
400    write_response(
401        w,
402        opcode,
403        &ResponseMeta {
404            status,
405            opaque,
406            cas,
407        },
408        &[],
409        &[],
410        body,
411    )
412}
413
414/// Write an error response for an unknown or malformed opcode, using
415/// the raw opcode byte from the wire.
416pub fn write_error_for_raw_opcode(
417    w: &mut impl Write,
418    opcode_byte: u8,
419    status: u16,
420    opaque: u32,
421    body: &[u8],
422) -> io::Result<()> {
423    write_raw_response(
424        w,
425        opcode_byte,
426        &ResponseMeta {
427            status,
428            opaque,
429            cas: CAS_ZERO,
430        },
431        &[],
432        &[],
433        body,
434    )
435}
436
437// ── Helper to build a raw request frame ──────────────────────────────
438/// Build a binary-protocol request frame from parts.  Useful for tests
439/// and for any code that needs to construct wire-format requests.
440pub fn build_request_frame(
441    opcode: Opcode,
442    opaque: u32,
443    cas: u64,
444    extras: &[u8],
445    key: &[u8],
446    value: &[u8],
447) -> Vec<u8> {
448    build_raw_request_frame(opcode as u8, opaque, cas, extras, key, value)
449}
450
451/// Build a request frame using a raw opcode byte.  Useful for testing
452/// unknown-opcode handling.
453pub fn build_raw_request_frame(
454    opcode_byte: u8,
455    opaque: u32,
456    cas: u64,
457    extras: &[u8],
458    key: &[u8],
459    value: &[u8],
460) -> Vec<u8> {
461    let body_len = extras.len() + key.len() + value.len();
462    let mut frame = vec![0u8; HEADER_LEN + body_len];
463    frame[0] = MAGIC_REQ;
464    frame[1] = opcode_byte;
465    BigEndian::write_u16(&mut frame[2..4], key.len() as u16);
466    frame[4] = extras.len() as u8;
467    BigEndian::write_u32(&mut frame[8..12], body_len as u32);
468    BigEndian::write_u32(&mut frame[12..16], opaque);
469    BigEndian::write_u64(&mut frame[16..24], cas);
470    frame[HEADER_LEN..HEADER_LEN + extras.len()].copy_from_slice(extras);
471    let key_start = HEADER_LEN + extras.len();
472    frame[key_start..key_start + key.len()].copy_from_slice(key);
473    let val_start = key_start + key.len();
474    frame[val_start..val_start + value.len()].copy_from_slice(value);
475    frame
476}
477
478#[cfg(test)]
479mod tests {
480    use super::*;
481
482    #[test]
483    fn opcode_round_trip() {
484        for &(byte, expected) in &[
485            (0x00, Opcode::Get),
486            (0x01, Opcode::Set),
487            (0x02, Opcode::Add),
488            (0x03, Opcode::Replace),
489            (0x04, Opcode::Delete),
490            (0x05, Opcode::Increment),
491            (0x06, Opcode::Decrement),
492            (0x07, Opcode::Quit),
493            (0x08, Opcode::Flush),
494            (0x09, Opcode::GetQ),
495            (0x0a, Opcode::Noop),
496            (0x0b, Opcode::Version),
497            (0x0c, Opcode::GetK),
498            (0x0d, Opcode::GetKQ),
499            (0x0e, Opcode::Append),
500            (0x0f, Opcode::Prepend),
501            (0x10, Opcode::Stat),
502            (0x11, Opcode::SetQ),
503            (0x12, Opcode::AddQ),
504            (0x13, Opcode::ReplaceQ),
505            (0x14, Opcode::DeleteQ),
506            (0x15, Opcode::IncrementQ),
507            (0x16, Opcode::DecrementQ),
508            (0x17, Opcode::QuitQ),
509            (0x18, Opcode::FlushQ),
510            (0x19, Opcode::AppendQ),
511            (0x1a, Opcode::PrependQ),
512            (0x1b, Opcode::Verbosity),
513            (0x1c, Opcode::Touch),
514            (0x1d, Opcode::GAT),
515            (0x1e, Opcode::GATQ),
516            (0x20, Opcode::SaslListMechs),
517            (0x21, Opcode::SaslAuth),
518            (0x22, Opcode::SaslStep),
519        ] {
520            assert_eq!(Opcode::parse(byte), Some(expected));
521            assert_eq!(expected as u8, byte);
522        }
523    }
524
525    #[test]
526    fn opcode_unknown_returns_none() {
527        assert!(Opcode::parse(0xFF).is_none());
528        assert!(Opcode::parse(0x80).is_none());
529    }
530
531    #[test]
532    fn quiet_and_key_flags() {
533        assert!(Opcode::GetQ.is_quiet());
534        assert!(Opcode::GetKQ.is_quiet());
535        assert!(Opcode::SetQ.is_quiet());
536        assert!(Opcode::AddQ.is_quiet());
537        assert!(Opcode::ReplaceQ.is_quiet());
538        assert!(Opcode::DeleteQ.is_quiet());
539        assert!(Opcode::IncrementQ.is_quiet());
540        assert!(Opcode::DecrementQ.is_quiet());
541        assert!(Opcode::QuitQ.is_quiet());
542        assert!(Opcode::FlushQ.is_quiet());
543        assert!(Opcode::AppendQ.is_quiet());
544        assert!(Opcode::PrependQ.is_quiet());
545        assert!(Opcode::GATQ.is_quiet());
546        assert!(!Opcode::Get.is_quiet());
547        assert!(!Opcode::Set.is_quiet());
548        assert!(!Opcode::Touch.is_quiet());
549        assert!(!Opcode::Append.is_quiet());
550        assert!(!Opcode::Stat.is_quiet());
551        assert!(!Opcode::Verbosity.is_quiet());
552        assert!(!Opcode::SaslListMechs.is_quiet());
553        assert!(!Opcode::SaslAuth.is_quiet());
554        assert!(!Opcode::SaslStep.is_quiet());
555        assert!(Opcode::GetK.includes_key());
556        assert!(Opcode::GetKQ.includes_key());
557        assert!(Opcode::GAT.includes_key());
558        assert!(Opcode::GATQ.includes_key());
559        assert!(!Opcode::Get.includes_key());
560        assert!(!Opcode::Touch.includes_key());
561    }
562
563    #[test]
564    fn opcode_base() {
565        assert_eq!(Opcode::SetQ.base(), Opcode::Set);
566        assert_eq!(Opcode::AddQ.base(), Opcode::Add);
567        assert_eq!(Opcode::ReplaceQ.base(), Opcode::Replace);
568        assert_eq!(Opcode::DeleteQ.base(), Opcode::Delete);
569        assert_eq!(Opcode::IncrementQ.base(), Opcode::Increment);
570        assert_eq!(Opcode::DecrementQ.base(), Opcode::Decrement);
571        assert_eq!(Opcode::QuitQ.base(), Opcode::Quit);
572        assert_eq!(Opcode::FlushQ.base(), Opcode::Flush);
573        assert_eq!(Opcode::AppendQ.base(), Opcode::Append);
574        assert_eq!(Opcode::PrependQ.base(), Opcode::Prepend);
575        assert_eq!(Opcode::GATQ.base(), Opcode::GAT);
576        assert_eq!(Opcode::Get.base(), Opcode::Get);
577        assert_eq!(Opcode::Noop.base(), Opcode::Noop);
578        assert_eq!(Opcode::Touch.base(), Opcode::Touch);
579        assert_eq!(Opcode::GAT.base(), Opcode::GAT);
580        assert_eq!(Opcode::Stat.base(), Opcode::Stat);
581        assert_eq!(Opcode::Verbosity.base(), Opcode::Verbosity);
582        assert_eq!(Opcode::SaslListMechs.base(), Opcode::SaslListMechs);
583        assert_eq!(Opcode::SaslAuth.base(), Opcode::SaslAuth);
584        assert_eq!(Opcode::SaslStep.base(), Opcode::SaslStep);
585    }
586
587    #[test]
588    fn header_parse_valid() {
589        let frame = build_request_frame(Opcode::Get, 42, 0, &[], b"mykey", &[]);
590        let hdr = Header::parse(&frame).expect("should parse");
591        assert_eq!(hdr.opcode, Some(Opcode::Get));
592        assert_eq!(hdr.opcode_byte, 0x00);
593        assert_eq!(hdr.key_len, 5);
594        assert_eq!(hdr.extras_len, 0);
595        assert_eq!(hdr.body_len, 5);
596        assert_eq!(hdr.opaque, 42);
597        assert_eq!(hdr.cas, 0);
598    }
599
600    #[test]
601    fn header_parse_unknown_opcode() {
602        let frame = build_raw_request_frame(0xFE, 99, 0, &[], b"key", &[]);
603        let hdr = Header::parse(&frame).expect("should parse even unknown opcode");
604        assert_eq!(hdr.opcode, None);
605        assert_eq!(hdr.opcode_byte, 0xFE);
606        assert_eq!(hdr.opaque, 99);
607    }
608
609    #[test]
610    fn header_rejects_short_buffer() {
611        assert!(Header::parse(&[0x80; 10]).is_none());
612    }
613
614    #[test]
615    fn header_parses_any_magic() {
616        // Header::parse no longer rejects wrong magic — that is
617        // try_parse_request's job.
618        let mut frame = build_request_frame(Opcode::Noop, 0, 0, &[], &[], &[]);
619        frame[0] = 0x00;
620        let hdr = Header::parse(&frame);
621        assert!(hdr.is_some());
622    }
623
624    // ── try_parse_request tests ─────────────────────────────────────
625
626    #[test]
627    fn try_parse_empty_is_incomplete() {
628        assert!(matches!(try_parse_request(&[]), ParseResult::Incomplete));
629    }
630
631    #[test]
632    fn try_parse_bad_magic() {
633        let mut frame = build_request_frame(Opcode::Get, 0, 0, &[], b"k", &[]);
634        frame[0] = 0x42;
635        assert!(matches!(try_parse_request(&frame), ParseResult::BadMagic));
636    }
637
638    #[test]
639    fn try_parse_incomplete_header() {
640        assert!(matches!(
641            try_parse_request(&[MAGIC_REQ, 0x00, 0x00]),
642            ParseResult::Incomplete,
643        ));
644    }
645
646    #[test]
647    fn try_parse_incomplete_body() {
648        let frame = build_request_frame(Opcode::Get, 0, 0, &[], b"key", &[]);
649        assert!(matches!(
650            try_parse_request(&frame[..frame.len() - 1]),
651            ParseResult::Incomplete,
652        ));
653    }
654
655    #[test]
656    fn try_parse_malformed_frame() {
657        // Manually craft a frame with inconsistent lengths:
658        // body_len=2, extras_len=1, key_len=2 → 1+2=3 > 2.
659        let mut bad = vec![0u8; HEADER_LEN + 2];
660        bad[0] = MAGIC_REQ;
661        bad[1] = 0x00; // Get
662        BigEndian::write_u16(&mut bad[2..4], 2); // key_len=2
663        bad[4] = 1; // extras_len=1
664        BigEndian::write_u32(&mut bad[8..12], 2); // body_len=2
665        BigEndian::write_u32(&mut bad[12..16], 77); // opaque
666        match try_parse_request(&bad) {
667            ParseResult::MalformedFrame {
668                opaque,
669                opcode_byte,
670                bytes_to_skip,
671            } => {
672                assert_eq!(opaque, 77);
673                assert_eq!(opcode_byte, 0x00);
674                assert_eq!(bytes_to_skip, HEADER_LEN + 2);
675            }
676            other => panic!("expected MalformedFrame, got {other:?}"),
677        }
678    }
679
680    #[test]
681    fn try_parse_oversized_body() {
682        // Craft a header that claims body_len > MAX_BODY_LEN.
683        let mut frame = vec![0u8; HEADER_LEN];
684        frame[0] = MAGIC_REQ;
685        frame[1] = 0x01; // Set
686        BigEndian::write_u32(&mut frame[8..12], MAX_BODY_LEN + 1);
687        BigEndian::write_u32(&mut frame[12..16], 55); // opaque
688        match try_parse_request(&frame) {
689            ParseResult::OversizedFrame {
690                opaque,
691                opcode_byte,
692            } => {
693                assert_eq!(opaque, 55);
694                assert_eq!(opcode_byte, 0x01);
695            }
696            other => panic!("expected OversizedFrame, got {other:?}"),
697        }
698    }
699
700    #[test]
701    fn try_parse_oversized_key() {
702        // Craft a header with key_len > MAX_KEY_LEN.
703        let key_len = MAX_KEY_LEN + 1;
704        let body_len = key_len as u32;
705        let mut frame = vec![0u8; HEADER_LEN + body_len as usize];
706        frame[0] = MAGIC_REQ;
707        frame[1] = 0x00; // Get
708        BigEndian::write_u16(&mut frame[2..4], key_len);
709        BigEndian::write_u32(&mut frame[8..12], body_len);
710        BigEndian::write_u32(&mut frame[12..16], 66);
711        match try_parse_request(&frame) {
712            ParseResult::OversizedFrame {
713                opaque,
714                opcode_byte,
715            } => {
716                assert_eq!(opaque, 66);
717                assert_eq!(opcode_byte, 0x00);
718            }
719            other => panic!("expected OversizedFrame, got {other:?}"),
720        }
721    }
722
723    #[test]
724    fn try_parse_unknown_opcode_still_parses() {
725        let frame = build_raw_request_frame(0xFE, 42, 0, &[], b"k", &[]);
726        match try_parse_request(&frame) {
727            ParseResult::Ok((req, consumed)) => {
728                assert_eq!(consumed, HEADER_LEN + 1);
729                assert!(req.hdr.opcode.is_none());
730                assert_eq!(req.hdr.opcode_byte, 0xFE);
731                assert_eq!(req.hdr.opaque, 42);
732                assert_eq!(req.key, b"k");
733            }
734            other => panic!("expected Ok, got {other:?}"),
735        }
736    }
737
738    // ── Legacy parse_request still works ────────────────────────────
739
740    #[test]
741    fn parse_request_get() {
742        let frame = build_request_frame(Opcode::Get, 7, 0, &[], b"hello", &[]);
743        let (req, consumed) = parse_request(&frame).expect("should parse");
744        assert_eq!(consumed, HEADER_LEN + 5);
745        assert_eq!(req.hdr.opcode, Some(Opcode::Get));
746        assert_eq!(req.key, b"hello");
747        assert!(req.extras.is_empty());
748        assert!(req.value.is_empty());
749    }
750
751    #[test]
752    fn parse_request_set_with_extras_and_value() {
753        let extras = [0u8; 8];
754        let frame = build_request_frame(Opcode::Set, 1, 0, &extras, b"k", b"val");
755        let (req, consumed) = parse_request(&frame).expect("should parse");
756        assert_eq!(consumed, HEADER_LEN + 8 + 1 + 3);
757        assert_eq!(req.extras.len(), 8);
758        assert_eq!(req.key, b"k");
759        assert_eq!(req.value, b"val");
760    }
761
762    #[test]
763    fn parse_request_incomplete_returns_none() {
764        let frame = build_request_frame(Opcode::Get, 0, 0, &[], b"key", &[]);
765        assert!(parse_request(&frame[..frame.len() - 1]).is_none());
766    }
767
768    #[test]
769    fn parse_request_two_in_buffer() {
770        let f1 = build_request_frame(Opcode::Noop, 1, 0, &[], &[], &[]);
771        let f2 = build_request_frame(Opcode::Quit, 2, 0, &[], &[], &[]);
772        let mut buf = f1.clone();
773        buf.extend_from_slice(&f2);
774
775        let (req1, c1) = parse_request(&buf).expect("first");
776        assert_eq!(req1.hdr.opcode, Some(Opcode::Noop));
777        assert_eq!(req1.hdr.opaque, 1);
778
779        let (req2, c2) = parse_request(&buf[c1..]).expect("second");
780        assert_eq!(req2.hdr.opcode, Some(Opcode::Quit));
781        assert_eq!(req2.hdr.opaque, 2);
782        assert_eq!(c1 + c2, buf.len());
783    }
784
785    // ── Response writing tests ──────────────────────────────────────
786
787    #[test]
788    fn write_response_simple() {
789        let mut out = Vec::new();
790        write_simple_response(&mut out, Opcode::Noop, ST_OK, 99, 0, &[]).expect("write");
791        assert_eq!(out.len(), HEADER_LEN);
792        assert_eq!(out[0], MAGIC_RES);
793        assert_eq!(out[1], Opcode::Noop as u8);
794        assert_eq!(BigEndian::read_u16(&out[6..8]), ST_OK);
795        assert_eq!(BigEndian::read_u32(&out[12..16]), 99);
796    }
797
798    #[test]
799    fn write_response_with_body() {
800        let mut out = Vec::new();
801        let extras = 0u32.to_be_bytes();
802        write_response(
803            &mut out,
804            Opcode::Get,
805            &ResponseMeta {
806                status: ST_OK,
807                opaque: 5,
808                cas: 100,
809            },
810            &extras,
811            &[],
812            b"val",
813        )
814        .expect("write");
815        assert_eq!(out.len(), HEADER_LEN + 4 + 3);
816        assert_eq!(out[4], 4);
817        assert_eq!(BigEndian::read_u32(&out[8..12]), 7);
818        assert_eq!(BigEndian::read_u64(&out[16..24]), 100);
819        assert_eq!(&out[HEADER_LEN + 4..], b"val");
820    }
821
822    #[test]
823    fn write_response_getk_with_key() {
824        let mut out = Vec::new();
825        let extras = 0u32.to_be_bytes();
826        write_response(
827            &mut out,
828            Opcode::GetK,
829            &ResponseMeta {
830                status: ST_OK,
831                opaque: 0,
832                cas: 1,
833            },
834            &extras,
835            b"mykey",
836            b"myval",
837        )
838        .expect("write");
839        let key_len = BigEndian::read_u16(&out[2..4]);
840        assert_eq!(key_len, 5);
841        let key_start = HEADER_LEN + 4;
842        assert_eq!(&out[key_start..key_start + 5], b"mykey");
843        assert_eq!(&out[key_start + 5..], b"myval");
844    }
845
846    #[test]
847    fn write_error_for_unknown_opcode() {
848        let mut out = Vec::new();
849        let msg = b"Unknown command";
850        write_error_for_raw_opcode(&mut out, 0xFE, ST_UNK, 42, msg).expect("write");
851        assert_eq!(out[0], MAGIC_RES);
852        assert_eq!(out[1], 0xFE);
853        assert_eq!(BigEndian::read_u16(&out[6..8]), ST_UNK);
854        assert_eq!(BigEndian::read_u32(&out[12..16]), 42);
855        assert_eq!(BigEndian::read_u64(&out[16..24]), CAS_ZERO);
856        assert_eq!(&out[HEADER_LEN..], msg);
857    }
858
859    // ================================================================
860    // Regression tests — previously fixed wire-visible behaviors
861    // ================================================================
862
863    /// Regression: BadMagic must be detected on the very first byte,
864    /// even when the rest of the buffer looks like a valid frame.
865    #[test]
866    fn regression_bad_magic_with_valid_body() {
867        let mut frame = build_request_frame(Opcode::Get, 1, 0, &[], b"key", &[]);
868        // Corrupt only the magic byte.
869        frame[0] = 0x81; // response magic, not request
870        assert!(matches!(try_parse_request(&frame), ParseResult::BadMagic));
871        frame[0] = 0x00;
872        assert!(matches!(try_parse_request(&frame), ParseResult::BadMagic));
873    }
874
875    /// Regression: MalformedFrame when extras_len + key_len > body_len.
876    /// The parser must return MalformedFrame rather than panicking on
877    /// slice bounds.
878    #[test]
879    fn regression_malformed_extras_key_overflow() {
880        // extras_len=4, key_len=4, body_len=6 → 4+4=8 > 6
881        let mut frame = vec![0u8; HEADER_LEN + 6];
882        frame[0] = MAGIC_REQ;
883        frame[1] = Opcode::Set as u8;
884        BigEndian::write_u16(&mut frame[2..4], 4); // key_len
885        frame[4] = 4; // extras_len
886        BigEndian::write_u32(&mut frame[8..12], 6); // body_len
887        BigEndian::write_u32(&mut frame[12..16], 123);
888        match try_parse_request(&frame) {
889            ParseResult::MalformedFrame {
890                opaque,
891                bytes_to_skip,
892                ..
893            } => {
894                assert_eq!(opaque, 123);
895                assert_eq!(bytes_to_skip, HEADER_LEN + 6);
896            }
897            other => panic!("expected MalformedFrame, got {other:?}"),
898        }
899    }
900
901    /// Regression: OversizedFrame at the exact boundary (MAX_BODY_LEN + 1)
902    /// and exactly at MAX_BODY_LEN (should be accepted, not rejected).
903    #[test]
904    fn regression_oversized_at_exact_boundary() {
905        // body_len == MAX_BODY_LEN should be accepted
906        let mut ok_frame = vec![0u8; HEADER_LEN];
907        ok_frame[0] = MAGIC_REQ;
908        ok_frame[1] = Opcode::Set as u8;
909        BigEndian::write_u32(&mut ok_frame[8..12], MAX_BODY_LEN);
910        // Not enough bytes to complete the frame, so we get Incomplete
911        assert!(matches!(
912            try_parse_request(&ok_frame),
913            ParseResult::Incomplete
914        ));
915
916        // body_len == MAX_BODY_LEN + 1 → OversizedFrame
917        let mut bad_frame = vec![0u8; HEADER_LEN];
918        bad_frame[0] = MAGIC_REQ;
919        bad_frame[1] = Opcode::Set as u8;
920        BigEndian::write_u32(&mut bad_frame[8..12], MAX_BODY_LEN + 1);
921        assert!(matches!(
922            try_parse_request(&bad_frame),
923            ParseResult::OversizedFrame { .. }
924        ));
925    }
926
927    /// Regression: key_len == MAX_KEY_LEN (250) should be accepted;
928    /// key_len == 251 should be OversizedFrame.
929    #[test]
930    fn regression_key_len_at_exact_boundary() {
931        // key_len = 250, body_len = 250 — valid but incomplete
932        let mut ok_hdr = vec![0u8; HEADER_LEN];
933        ok_hdr[0] = MAGIC_REQ;
934        ok_hdr[1] = Opcode::Get as u8;
935        BigEndian::write_u16(&mut ok_hdr[2..4], MAX_KEY_LEN); // 250
936        BigEndian::write_u32(&mut ok_hdr[8..12], MAX_KEY_LEN as u32);
937        assert!(matches!(
938            try_parse_request(&ok_hdr),
939            ParseResult::Incomplete
940        ));
941
942        // key_len = 251 → OversizedFrame
943        let mut bad_hdr = vec![0u8; HEADER_LEN];
944        bad_hdr[0] = MAGIC_REQ;
945        bad_hdr[1] = Opcode::Get as u8;
946        BigEndian::write_u16(&mut bad_hdr[2..4], MAX_KEY_LEN + 1);
947        BigEndian::write_u32(&mut bad_hdr[8..12], (MAX_KEY_LEN + 1) as u32);
948        assert!(matches!(
949            try_parse_request(&bad_hdr),
950            ParseResult::OversizedFrame { .. }
951        ));
952    }
953
954    /// Regression: DELETE success CAS — the response builder must put
955    /// a non-zero CAS in the response for successful delete.  This is a
956    /// wire-format invariant: field offset [16..24] in the response.
957    #[test]
958    fn regression_delete_success_cas_nonzero_in_response() {
959        let mut out = Vec::new();
960        let delete_cas: u64 = 42;
961        write_simple_response(&mut out, Opcode::Delete, ST_OK, 1, delete_cas, &[]).expect("write");
962        let response_cas = BigEndian::read_u64(&out[16..24]);
963        assert_ne!(response_cas, 0, "DELETE success must carry a non-zero CAS");
964        assert_eq!(response_cas, 42);
965    }
966
967    /// Regression: unknown opcode should still parse successfully and
968    /// preserve the raw opcode byte for error echoing.
969    #[test]
970    fn regression_unknown_opcode_preserves_raw_byte() {
971        for raw_byte in [0x30, 0x7F, 0xAA, 0xFF] {
972            let frame = build_raw_request_frame(raw_byte, 99, 0, &[], b"k", &[]);
973            match try_parse_request(&frame) {
974                ParseResult::Ok((req, _)) => {
975                    assert!(req.hdr.opcode.is_none());
976                    assert_eq!(req.hdr.opcode_byte, raw_byte);
977                }
978                other => panic!("byte 0x{raw_byte:02x}: expected Ok, got {other:?}"),
979            }
980        }
981    }
982
983    // ================================================================
984    // Binary-safe value and key edge cases
985    // ================================================================
986
987    /// Null bytes in keys and values must survive round-trip.
988    #[test]
989    fn binary_safe_null_bytes_in_key_and_value() {
990        let key = b"key\x00with\x00nulls";
991        let value = b"\x00\x00\x00";
992        let frame = build_request_frame(Opcode::Set, 1, 0, &[0u8; 8], key, value);
993        let (req, consumed) = parse_request(&frame).expect("should parse");
994        assert_eq!(consumed, HEADER_LEN + 8 + key.len() + value.len());
995        assert_eq!(req.key, key);
996        assert_eq!(req.value, value);
997    }
998
999    /// High bytes (0xFF) in keys and values.
1000    #[test]
1001    fn binary_safe_high_bytes() {
1002        let key = b"\xff\xfe\xfd";
1003        let value = b"\xff\xff\xff\xff";
1004        let frame = build_request_frame(Opcode::Set, 1, 0, &[0u8; 8], key, value);
1005        let (req, _) = parse_request(&frame).expect("should parse");
1006        assert_eq!(req.key, key);
1007        assert_eq!(req.value, value);
1008    }
1009
1010    /// Empty key and empty value — valid for NOOP-like opcodes.
1011    #[test]
1012    fn binary_safe_empty_key_and_value() {
1013        let frame = build_request_frame(Opcode::Noop, 1, 0, &[], &[], &[]);
1014        let (req, consumed) = parse_request(&frame).expect("should parse");
1015        assert_eq!(consumed, HEADER_LEN);
1016        assert!(req.key.is_empty());
1017        assert!(req.value.is_empty());
1018        assert!(req.extras.is_empty());
1019    }
1020
1021    /// Maximum-length key (250 bytes) must be accepted.
1022    #[test]
1023    fn binary_safe_max_key_length() {
1024        let key = vec![b'A'; MAX_KEY_LEN as usize];
1025        let frame = build_request_frame(Opcode::Get, 1, 0, &[], &key, &[]);
1026        let (req, _) = parse_request(&frame).expect("should parse");
1027        assert_eq!(req.key.len(), 250);
1028    }
1029
1030    /// Value with all 256 byte values present.
1031    #[test]
1032    fn binary_safe_all_byte_values() {
1033        let value: Vec<u8> = (0..=255u8).collect();
1034        let frame = build_request_frame(Opcode::Set, 1, 0, &[0u8; 8], b"k", &value);
1035        let (req, _) = parse_request(&frame).expect("should parse");
1036        assert_eq!(req.value.len(), 256);
1037        assert_eq!(req.value, value.as_slice());
1038    }
1039
1040    // ================================================================
1041    // Response builder round-trip and invariant tests
1042    // ================================================================
1043
1044    /// Every response must start with MAGIC_RES (0x81).
1045    #[test]
1046    fn response_magic_byte() {
1047        for opcode in [Opcode::Get, Opcode::Set, Opcode::Noop, Opcode::Quit] {
1048            let mut out = Vec::new();
1049            write_simple_response(&mut out, opcode, ST_OK, 0, 0, &[]).unwrap();
1050            assert_eq!(
1051                out[0], MAGIC_RES,
1052                "opcode {opcode:?} response must start with 0x81"
1053            );
1054        }
1055    }
1056
1057    /// Error responses must carry CAS_ZERO.
1058    #[test]
1059    fn response_error_carries_cas_zero() {
1060        for status in [ST_NF, ST_IX, ST_ARGS, ST_NOT_STORED, ST_UNK] {
1061            let mut out = Vec::new();
1062            write_simple_response(&mut out, Opcode::Get, status, 0, CAS_ZERO, &[]).unwrap();
1063            let cas = BigEndian::read_u64(&out[16..24]);
1064            assert_eq!(cas, CAS_ZERO, "status 0x{status:04x} must carry CAS_ZERO");
1065        }
1066    }
1067
1068    /// Response body_len field must equal extras.len() + key.len() + value.len().
1069    #[test]
1070    fn response_body_len_field_correct() {
1071        let extras = [1u8, 2, 3, 4];
1072        let key = b"mykey";
1073        let value = b"myvalue";
1074        let mut out = Vec::new();
1075        write_response(
1076            &mut out,
1077            Opcode::GetK,
1078            &ResponseMeta {
1079                status: ST_OK,
1080                opaque: 0,
1081                cas: 1,
1082            },
1083            &extras,
1084            key,
1085            value,
1086        )
1087        .unwrap();
1088        let body_len = BigEndian::read_u32(&out[8..12]);
1089        assert_eq!(body_len as usize, extras.len() + key.len() + value.len());
1090    }
1091
1092    /// Response opaque must echo the request opaque.
1093    #[test]
1094    fn response_opaque_echo() {
1095        for opaque in [0u32, 1, 0x12345678, u32::MAX] {
1096            let mut out = Vec::new();
1097            write_simple_response(&mut out, Opcode::Noop, ST_OK, opaque, 0, &[]).unwrap();
1098            assert_eq!(BigEndian::read_u32(&out[12..16]), opaque);
1099        }
1100    }
1101
1102    /// Response key_len field must match actual key length.
1103    #[test]
1104    fn response_key_len_field_correct() {
1105        let key = b"testkey";
1106        let mut out = Vec::new();
1107        write_response(
1108            &mut out,
1109            Opcode::GetK,
1110            &ResponseMeta {
1111                status: ST_OK,
1112                opaque: 0,
1113                cas: 1,
1114            },
1115            &[0u8; 4],
1116            key,
1117            b"val",
1118        )
1119        .unwrap();
1120        let key_len = BigEndian::read_u16(&out[2..4]);
1121        assert_eq!(key_len as usize, key.len());
1122    }
1123
1124    /// Response extras_len field must match actual extras length.
1125    #[test]
1126    fn response_extras_len_field_correct() {
1127        let extras = [0u8; 4];
1128        let mut out = Vec::new();
1129        write_response(
1130            &mut out,
1131            Opcode::Get,
1132            &ResponseMeta {
1133                status: ST_OK,
1134                opaque: 0,
1135                cas: 1,
1136            },
1137            &extras,
1138            &[],
1139            b"val",
1140        )
1141        .unwrap();
1142        assert_eq!(out[4], 4);
1143    }
1144
1145    /// write_raw_response echoes the exact opcode byte provided.
1146    #[test]
1147    fn response_raw_opcode_echo() {
1148        for raw in [0x00u8, 0xFE, 0xFF, 0x42] {
1149            let mut out = Vec::new();
1150            write_raw_response(
1151                &mut out,
1152                raw,
1153                &ResponseMeta {
1154                    status: ST_OK,
1155                    opaque: 0,
1156                    cas: 0,
1157                },
1158                &[],
1159                &[],
1160                &[],
1161            )
1162            .unwrap();
1163            assert_eq!(out[1], raw);
1164        }
1165    }
1166
1167    // ================================================================
1168    // Property-style framing checks
1169    // ================================================================
1170
1171    /// For any valid opcode with any key/value/extras, consumed bytes
1172    /// must equal HEADER_LEN + body_len.
1173    #[test]
1174    fn property_consumed_equals_header_plus_body() {
1175        let opcodes = [
1176            Opcode::Get,
1177            Opcode::Set,
1178            Opcode::Delete,
1179            Opcode::Noop,
1180            Opcode::Increment,
1181            Opcode::Append,
1182            Opcode::Touch,
1183            Opcode::GAT,
1184            Opcode::Flush,
1185            Opcode::Version,
1186            Opcode::Stat,
1187        ];
1188        let extras_sizes = [0, 4, 8, 20];
1189        let key_sizes = [0, 1, 5, 50];
1190        let value_sizes = [0, 1, 10, 100];
1191
1192        for &op in &opcodes {
1193            for &elen in &extras_sizes {
1194                for &klen in &key_sizes {
1195                    for &vlen in &value_sizes {
1196                        let extras = vec![0u8; elen];
1197                        let key = vec![b'k'; klen];
1198                        let value = vec![b'v'; vlen];
1199                        let frame = build_request_frame(op, 0, 0, &extras, &key, &value);
1200
1201                        let expected_body = elen + klen + vlen;
1202                        let expected_total = HEADER_LEN + expected_body;
1203                        assert_eq!(
1204                            frame.len(),
1205                            expected_total,
1206                            "opcode={op:?} e={elen} k={klen} v={vlen}"
1207                        );
1208
1209                        match try_parse_request(&frame) {
1210                            ParseResult::Ok((req, consumed)) => {
1211                                assert_eq!(
1212                                    consumed, expected_total,
1213                                    "opcode={op:?} consumed mismatch"
1214                                );
1215                                assert_eq!(req.extras.len(), elen);
1216                                assert_eq!(req.key.len(), klen);
1217                                assert_eq!(req.value.len(), vlen);
1218                            }
1219                            other => panic!("opcode={op:?} e={elen} k={klen} v={vlen}: {other:?}"),
1220                        }
1221                    }
1222                }
1223            }
1224        }
1225    }
1226
1227    /// Extras + key must never exceed body_len — the builder enforces
1228    /// this invariant.  Verify that build_request_frame always produces
1229    /// parseable frames.
1230    #[test]
1231    fn property_build_always_parseable() {
1232        for op_byte in 0..=0x22u8 {
1233            let op = match Opcode::parse(op_byte) {
1234                Some(o) => o,
1235                None => continue,
1236            };
1237            let frame = build_request_frame(op, 0xDEAD, 0x1234, &[1, 2], b"abc", b"xyz");
1238            match try_parse_request(&frame) {
1239                ParseResult::Ok((req, consumed)) => {
1240                    assert_eq!(consumed, HEADER_LEN + 2 + 3 + 3);
1241                    assert_eq!(req.hdr.opaque, 0xDEAD);
1242                    assert_eq!(req.hdr.cas, 0x1234);
1243                    assert_eq!(req.extras, &[1, 2]);
1244                    assert_eq!(req.key, b"abc");
1245                    assert_eq!(req.value, b"xyz");
1246                }
1247                other => panic!("opcode {op:?}: expected Ok, got {other:?}"),
1248            }
1249        }
1250    }
1251
1252    /// Multi-frame extraction: parsing two concatenated frames must
1253    /// yield both correctly and consume the entire buffer.
1254    #[test]
1255    fn property_multi_frame_extraction() {
1256        let f1 = build_request_frame(Opcode::Set, 1, 100, &[0u8; 8], b"k1", b"v1");
1257        let f2 = build_request_frame(Opcode::Get, 2, 0, &[], b"k2", &[]);
1258        let mut buf = f1.clone();
1259        buf.extend_from_slice(&f2);
1260
1261        let (r1, c1) = parse_request(&buf).expect("first frame");
1262        assert_eq!(r1.hdr.opaque, 1);
1263        assert_eq!(r1.hdr.cas, 100);
1264        assert_eq!(r1.key, b"k1");
1265        assert_eq!(r1.value, b"v1");
1266
1267        let (r2, c2) = parse_request(&buf[c1..]).expect("second frame");
1268        assert_eq!(r2.hdr.opaque, 2);
1269        assert_eq!(r2.key, b"k2");
1270
1271        assert_eq!(
1272            c1 + c2,
1273            buf.len(),
1274            "total consumed must equal buffer length"
1275        );
1276    }
1277
1278    /// Trailing bytes after a valid frame don't affect parsing of
1279    /// the first frame — they remain as residual for the next parse.
1280    #[test]
1281    fn property_trailing_bytes_ignored() {
1282        let frame = build_request_frame(Opcode::Noop, 7, 0, &[], &[], &[]);
1283        let mut buf = frame.clone();
1284        buf.extend_from_slice(&[0xDE, 0xAD]); // trailing garbage
1285
1286        let (req, consumed) = parse_request(&buf).expect("should parse first frame");
1287        assert_eq!(consumed, HEADER_LEN);
1288        assert_eq!(req.hdr.opaque, 7);
1289        // Remaining 2 bytes are not consumed.
1290        assert_eq!(buf.len() - consumed, 2);
1291    }
1292
1293    // ================================================================
1294    // Counter / numeric precision limitation tests
1295    // ================================================================
1296
1297    /// The LUA_COUNTER script documentation states that counter values
1298    /// are exact for [0, 2^53).  This test verifies the Rust-side u64
1299    /// parse round-trip for values near the precision boundary.
1300    ///
1301    /// Precision note: Lua 5.1 uses IEEE 754 doubles.  Integers above
1302    /// 2^53 (9007199254740992) may lose precision and round.  This is
1303    /// NOT wraparound — it is floating-point precision loss.  The GA
1304    /// explicitly documents this as a known limitation.
1305    #[test]
1306    fn counter_u64_parse_exact_below_2_53() {
1307        // Values below 2^53 should round-trip exactly through
1308        // string formatting.
1309        let exact_values: &[u64] = &[
1310            0,
1311            1,
1312            u32::MAX as u64,
1313            (1u64 << 53) - 1, // 9007199254740991 — largest exact integer
1314        ];
1315        for &val in exact_values {
1316            let s = format!("{val}");
1317            let parsed: u64 = s.parse().expect("should parse");
1318            assert_eq!(parsed, val, "value {val} must round-trip exactly");
1319        }
1320    }
1321
1322    /// Values at or above 2^53 may lose precision when processed
1323    /// through Lua's f64 representation.  This test documents the
1324    /// precision loss behavior (NOT wraparound).
1325    #[test]
1326    fn counter_precision_loss_above_2_53() {
1327        let boundary = 1u64 << 53; // 9007199254740992
1328        // At exactly 2^53, f64 can still represent it.
1329        let f = boundary as f64;
1330        assert_eq!(f as u64, boundary, "2^53 itself is exact in f64");
1331
1332        // 2^53 + 1 loses precision in f64.
1333        let above = boundary + 1;
1334        let f_above = above as f64;
1335        // This is the precision loss: f64 rounds 2^53+1 back to 2^53.
1336        assert_ne!(
1337            f_above as u64, above,
1338            "2^53+1 should NOT round-trip exactly through f64 — \
1339             this is precision loss, not wraparound"
1340        );
1341        assert_eq!(
1342            f_above as u64, boundary,
1343            "2^53+1 rounds to 2^53 in f64 — precision loss"
1344        );
1345    }
1346
1347    /// The string format "%.0f" used by the Lua counter script
1348    /// produces precision-lossy (rounded) output for large values,
1349    /// not wrapped values.
1350    #[test]
1351    fn counter_lua_format_is_rounding_not_wraparound() {
1352        // Simulating what Lua's string.format('%.0f', num) does:
1353        // it converts the f64 to a string with no decimal places.
1354        let val: u64 = (1u64 << 53) + 1;
1355        let as_f64 = val as f64;
1356        let formatted = format!("{:.0}", as_f64);
1357        let back: u64 = formatted.parse().unwrap();
1358        // The result is 2^53, not some wrapped value.
1359        assert_eq!(back, 1u64 << 53);
1360        // Crucially, it's NOT 0 or some negative number — it's a
1361        // nearby value, demonstrating precision loss not wraparound.
1362        assert!(
1363            back > 0,
1364            "large counter values round, they don't wrap to zero"
1365        );
1366    }
1367
1368    /// Counter response is an 8-byte big-endian u64 in the value field.
1369    /// Verify the encoding for representative values.
1370    #[test]
1371    fn counter_response_encoding() {
1372        let test_values: &[u64] = &[0, 1, 255, 256, u32::MAX as u64, u64::MAX];
1373        for &val in test_values {
1374            let encoded = val.to_be_bytes();
1375            assert_eq!(encoded.len(), 8);
1376            let decoded = u64::from_be_bytes(encoded);
1377            assert_eq!(decoded, val);
1378        }
1379    }
1380
1381    // ================================================================
1382    // Additional edge-case tests
1383    // ================================================================
1384
1385    /// A single 0x80 byte (just the magic) should be Incomplete,
1386    /// not a parse error.
1387    #[test]
1388    fn edge_single_magic_byte_is_incomplete() {
1389        assert!(matches!(
1390            try_parse_request(&[MAGIC_REQ]),
1391            ParseResult::Incomplete
1392        ));
1393    }
1394
1395    /// 23 bytes (one short of a header) should be Incomplete.
1396    #[test]
1397    fn edge_one_byte_short_of_header() {
1398        let mut buf = vec![MAGIC_REQ; 23];
1399        buf[0] = MAGIC_REQ;
1400        assert!(matches!(try_parse_request(&buf), ParseResult::Incomplete));
1401    }
1402
1403    /// Exactly HEADER_LEN bytes with body_len=0 should parse as a
1404    /// complete frame (e.g. NOOP).
1405    #[test]
1406    fn edge_exact_header_zero_body() {
1407        let frame = build_request_frame(Opcode::Noop, 0, 0, &[], &[], &[]);
1408        assert_eq!(frame.len(), HEADER_LEN);
1409        match try_parse_request(&frame) {
1410            ParseResult::Ok((req, consumed)) => {
1411                assert_eq!(consumed, HEADER_LEN);
1412                assert_eq!(req.hdr.body_len, 0);
1413            }
1414            other => panic!("expected Ok, got {other:?}"),
1415        }
1416    }
1417
1418    /// CAS field in request header must be preserved exactly.
1419    #[test]
1420    fn edge_cas_preserved_in_header() {
1421        let cas_values: &[u64] = &[0, 1, u64::MAX, 0xDEADBEEFCAFEBABE];
1422        for &cas in cas_values {
1423            let frame = build_request_frame(Opcode::Set, 0, cas, &[0u8; 8], b"k", b"v");
1424            let (req, _) = parse_request(&frame).expect("should parse");
1425            assert_eq!(req.hdr.cas, cas, "CAS 0x{cas:016x} must be preserved");
1426        }
1427    }
1428
1429    /// Opaque field must be preserved for all u32 values.
1430    #[test]
1431    fn edge_opaque_preserved() {
1432        for opaque in [0u32, 1, u32::MAX, 0x12345678] {
1433            let frame = build_request_frame(Opcode::Get, opaque, 0, &[], b"k", &[]);
1434            let (req, _) = parse_request(&frame).expect("should parse");
1435            assert_eq!(req.hdr.opaque, opaque);
1436        }
1437    }
1438
1439    /// Verify that the response status field is at bytes [6..8].
1440    #[test]
1441    fn response_status_field_position() {
1442        let statuses = [
1443            ST_OK,
1444            ST_NF,
1445            ST_IX,
1446            ST_ARGS,
1447            ST_NOT_STORED,
1448            ST_AUTH_ERROR,
1449            ST_AUTH_CONTINUE,
1450            ST_UNK,
1451        ];
1452        for &status in &statuses {
1453            let mut out = Vec::new();
1454            write_simple_response(&mut out, Opcode::Get, status, 0, 0, &[]).unwrap();
1455            assert_eq!(
1456                BigEndian::read_u16(&out[6..8]),
1457                status,
1458                "status 0x{status:04x} at wrong position"
1459            );
1460        }
1461    }
1462
1463    /// Quiet variants of GET suppress miss responses (not hits).
1464    /// Quiet variants of mutations suppress success responses (not errors).
1465    /// This verifies the is_quiet classification is consistent with base().
1466    #[test]
1467    fn quiet_variants_have_loud_base() {
1468        let quiet_loud_pairs = [
1469            (Opcode::GetQ, Opcode::Get),
1470            (Opcode::GetKQ, Opcode::GetK),
1471            (Opcode::SetQ, Opcode::Set),
1472            (Opcode::AddQ, Opcode::Add),
1473            (Opcode::ReplaceQ, Opcode::Replace),
1474            (Opcode::DeleteQ, Opcode::Delete),
1475            (Opcode::IncrementQ, Opcode::Increment),
1476            (Opcode::DecrementQ, Opcode::Decrement),
1477            (Opcode::QuitQ, Opcode::Quit),
1478            (Opcode::FlushQ, Opcode::Flush),
1479            (Opcode::AppendQ, Opcode::Append),
1480            (Opcode::PrependQ, Opcode::Prepend),
1481            (Opcode::GATQ, Opcode::GAT),
1482        ];
1483        for (quiet, loud) in quiet_loud_pairs {
1484            assert!(quiet.is_quiet(), "{quiet:?} must be quiet");
1485            assert!(!loud.is_quiet(), "{loud:?} must not be quiet");
1486            assert_eq!(quiet.base(), loud, "{quiet:?}.base() must be {loud:?}");
1487            assert_eq!(loud.base(), loud, "{loud:?}.base() must be self");
1488        }
1489    }
1490
1491    /// Verify that no opcode byte in the valid range 0x00..=0x22 produces
1492    /// a gap — every valid byte maps to Some(opcode).
1493    #[test]
1494    fn opcode_coverage_no_gaps_in_valid_range() {
1495        let valid_bytes: Vec<u8> = vec![
1496            0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
1497            0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
1498            0x1c, 0x1d, 0x1e, 0x20, 0x21, 0x22,
1499        ];
1500        for b in valid_bytes {
1501            assert!(
1502                Opcode::parse(b).is_some(),
1503                "byte 0x{b:02x} must map to a known opcode"
1504            );
1505        }
1506        // Gap at 0x1f must be None.
1507        assert!(Opcode::parse(0x1f).is_none(), "0x1f must be unknown");
1508    }
1509
1510    /// Verify that response CAS field is at bytes [16..24] and can
1511    /// hold the full u64 range.
1512    #[test]
1513    fn response_cas_field_full_range() {
1514        for cas in [0u64, 1, u64::MAX, 0xCAFEBABEDEADBEEF] {
1515            let mut out = Vec::new();
1516            write_simple_response(&mut out, Opcode::Get, ST_OK, 0, cas, &[]).unwrap();
1517            assert_eq!(BigEndian::read_u64(&out[16..24]), cas);
1518        }
1519    }
1520
1521    /// Verify build_raw_request_frame with opcode byte 0x00 produces the
1522    /// same result as build_request_frame with Opcode::Get.
1523    #[test]
1524    fn build_raw_matches_build_typed() {
1525        let typed = build_request_frame(Opcode::Get, 42, 100, &[1, 2], b"key", b"val");
1526        let raw = build_raw_request_frame(0x00, 42, 100, &[1, 2], b"key", b"val");
1527        assert_eq!(typed, raw);
1528    }
1529
1530    // ── parse_request legacy wrapper ────────────────────────────────
1531
1532    #[test]
1533    fn parse_request_returns_some_for_valid() {
1534        let frame = build_request_frame(Opcode::Get, 0, 0, &[], b"key", &[]);
1535        let result = parse_request(&frame);
1536        assert!(result.is_some());
1537        let (req, consumed) = result.unwrap();
1538        assert_eq!(req.key, b"key");
1539        assert_eq!(consumed, frame.len());
1540    }
1541
1542    #[test]
1543    fn parse_request_returns_none_for_bad_magic() {
1544        let mut frame = build_request_frame(Opcode::Get, 0, 0, &[], b"key", &[]);
1545        frame[0] = 0xFF; // bad magic
1546        assert!(parse_request(&frame).is_none());
1547    }
1548
1549    #[test]
1550    fn parse_request_returns_none_for_incomplete() {
1551        assert!(parse_request(&[0x80, 0x00]).is_none());
1552    }
1553
1554    #[test]
1555    fn parse_request_returns_none_for_empty() {
1556        assert!(parse_request(&[]).is_none());
1557    }
1558
1559    // ── write_simple_response ───────────────────────────────────────
1560
1561    #[test]
1562    fn write_simple_response_no_body() {
1563        let mut out = Vec::new();
1564        write_simple_response(&mut out, Opcode::Noop, ST_OK, 0, 0, &[]).unwrap();
1565        assert_eq!(out.len(), HEADER_LEN);
1566        assert_eq!(out[0], MAGIC_RES);
1567        assert_eq!(out[1], Opcode::Noop as u8);
1568        assert_eq!(BigEndian::read_u32(&out[8..12]), 0); // body_len
1569    }
1570
1571    #[test]
1572    fn write_simple_response_with_body() {
1573        let mut out = Vec::new();
1574        write_simple_response(&mut out, Opcode::Version, ST_OK, 0, 0, b"1.0").unwrap();
1575        assert_eq!(out.len(), HEADER_LEN + 3);
1576        assert_eq!(BigEndian::read_u32(&out[8..12]), 3); // body_len
1577        assert_eq!(&out[HEADER_LEN..], b"1.0");
1578    }
1579
1580    // ── write_error_for_raw_opcode ──────────────────────────────────
1581
1582    #[test]
1583    fn write_error_for_raw_opcode_sets_cas_zero() {
1584        let mut out = Vec::new();
1585        write_error_for_raw_opcode(&mut out, 0xFF, ST_UNK, 42, b"err").unwrap();
1586        assert_eq!(BigEndian::read_u64(&out[16..24]), CAS_ZERO);
1587        assert_eq!(out[1], 0xFF); // raw opcode preserved
1588        assert_eq!(BigEndian::read_u32(&out[12..16]), 42); // opaque
1589        assert_eq!(BigEndian::read_u16(&out[6..8]), ST_UNK); // status
1590    }
1591
1592    // ── Opcode includes_key coverage ────────────────────────────────
1593
1594    #[test]
1595    fn includes_key_for_all_variants() {
1596        // Key-including opcodes
1597        assert!(Opcode::GetK.includes_key());
1598        assert!(Opcode::GetKQ.includes_key());
1599        assert!(Opcode::GAT.includes_key());
1600        assert!(Opcode::GATQ.includes_key());
1601        // Non-key-including opcodes
1602        assert!(!Opcode::Get.includes_key());
1603        assert!(!Opcode::Set.includes_key());
1604        assert!(!Opcode::Delete.includes_key());
1605        assert!(!Opcode::Noop.includes_key());
1606        assert!(!Opcode::GetQ.includes_key());
1607    }
1608
1609    // ── SASL opcodes in quiet/base ──────────────────────────────────
1610
1611    #[test]
1612    fn sasl_opcodes_not_quiet() {
1613        assert!(!Opcode::SaslListMechs.is_quiet());
1614        assert!(!Opcode::SaslAuth.is_quiet());
1615        assert!(!Opcode::SaslStep.is_quiet());
1616    }
1617
1618    #[test]
1619    fn sasl_opcodes_base_is_self() {
1620        assert_eq!(Opcode::SaslListMechs.base(), Opcode::SaslListMechs);
1621        assert_eq!(Opcode::SaslAuth.base(), Opcode::SaslAuth);
1622        assert_eq!(Opcode::SaslStep.base(), Opcode::SaslStep);
1623    }
1624
1625    // ── Admin opcodes in quiet/base ─────────────────────────────────
1626
1627    #[test]
1628    fn stat_verbosity_not_quiet() {
1629        assert!(!Opcode::Stat.is_quiet());
1630        assert!(!Opcode::Verbosity.is_quiet());
1631        assert!(!Opcode::Version.is_quiet());
1632    }
1633
1634    #[test]
1635    fn touch_not_quiet() {
1636        assert!(!Opcode::Touch.is_quiet());
1637    }
1638
1639    // ── build_request_frame field layout ────────────────────────────
1640
1641    #[test]
1642    fn build_request_frame_empty_all() {
1643        let frame = build_request_frame(Opcode::Noop, 0, 0, &[], &[], &[]);
1644        assert_eq!(frame.len(), HEADER_LEN);
1645        assert_eq!(frame[0], MAGIC_REQ);
1646        assert_eq!(frame[1], Opcode::Noop as u8);
1647        assert_eq!(BigEndian::read_u16(&frame[2..4]), 0); // key_len
1648        assert_eq!(frame[4], 0); // extras_len
1649        assert_eq!(BigEndian::read_u32(&frame[8..12]), 0); // body_len
1650    }
1651
1652    #[test]
1653    fn build_request_frame_with_extras_key_value() {
1654        let extras = vec![1, 2, 3, 4];
1655        let key = b"testkey";
1656        let value = b"testvalue";
1657        let frame = build_request_frame(Opcode::Set, 99, 55, &extras, key, value);
1658        assert_eq!(frame.len(), HEADER_LEN + 4 + 7 + 9);
1659        assert_eq!(BigEndian::read_u16(&frame[2..4]), 7); // key_len
1660        assert_eq!(frame[4], 4); // extras_len
1661        assert_eq!(BigEndian::read_u32(&frame[8..12]), 20); // body_len
1662        assert_eq!(BigEndian::read_u32(&frame[12..16]), 99); // opaque
1663        assert_eq!(BigEndian::read_u64(&frame[16..24]), 55); // cas
1664        // Verify field layout: extras then key then value
1665        assert_eq!(&frame[HEADER_LEN..HEADER_LEN + 4], &extras);
1666        assert_eq!(&frame[HEADER_LEN + 4..HEADER_LEN + 11], key);
1667        assert_eq!(&frame[HEADER_LEN + 11..], value);
1668    }
1669
1670    // ── try_parse_request: MalformedFrame opaque/opcode preserved ───
1671
1672    #[test]
1673    fn malformed_frame_preserves_opaque_and_opcode() {
1674        // Create a frame where extras_len + key_len > body_len
1675        let mut frame = [0u8; HEADER_LEN + 4];
1676        frame[0] = MAGIC_REQ;
1677        frame[1] = Opcode::Set as u8;
1678        BigEndian::write_u16(&mut frame[2..4], 10); // key_len = 10
1679        frame[4] = 10; // extras_len = 10 — total 20 but body_len = 4
1680        BigEndian::write_u32(&mut frame[8..12], 4); // body_len = 4
1681        BigEndian::write_u32(&mut frame[12..16], 0xDEAD); // opaque
1682        match try_parse_request(&frame) {
1683            ParseResult::MalformedFrame {
1684                opaque,
1685                opcode_byte,
1686                bytes_to_skip,
1687            } => {
1688                assert_eq!(opaque, 0xDEAD);
1689                assert_eq!(opcode_byte, Opcode::Set as u8);
1690                assert_eq!(bytes_to_skip, HEADER_LEN + 4);
1691            }
1692            other => panic!("expected MalformedFrame, got {other:?}"),
1693        }
1694    }
1695
1696    // ── OversizedFrame: key at exact MAX_KEY_LEN+1 ──────────────────
1697
1698    #[test]
1699    fn oversized_key_at_boundary_plus_one() {
1700        let mut frame = [0u8; HEADER_LEN];
1701        frame[0] = MAGIC_REQ;
1702        frame[1] = Opcode::Get as u8;
1703        BigEndian::write_u16(&mut frame[2..4], MAX_KEY_LEN + 1);
1704        BigEndian::write_u32(&mut frame[8..12], (MAX_KEY_LEN + 1) as u32);
1705        BigEndian::write_u32(&mut frame[12..16], 0xBEEF);
1706        match try_parse_request(&frame) {
1707            ParseResult::OversizedFrame {
1708                opaque,
1709                opcode_byte,
1710            } => {
1711                assert_eq!(opaque, 0xBEEF);
1712                assert_eq!(opcode_byte, Opcode::Get as u8);
1713            }
1714            other => panic!("expected OversizedFrame, got {other:?}"),
1715        }
1716    }
1717}