Skip to main content

fast_cache/protocol/
fast.rs

1use crate::{FastCacheError, Result};
2
3/// Native fast-cache protocol, version 2.
4///
5/// Requests use an 8-byte raw-binary header:
6/// `[magic:u8, version:u8, command:u8, flags:u8, body_len:u32le]`.
7/// Optional routing fields live at the start of the body when their flag bits
8/// are set, followed by command-specific binary fields. Field order is:
9/// `key_hash:u64`, `route_shard:u32`, `key_tag:u64`. RESP remains supported
10/// separately by the server ingress path.
11pub const FAST_REQUEST_MAGIC: u8 = 0xFA;
12pub const FAST_RESPONSE_MAGIC: u8 = 0xFB;
13pub const FAST_PROTOCOL_VERSION: u8 = 2;
14
15pub const FAST_FLAG_KEY_HASH: u8 = 0x01;
16pub const FAST_FLAG_ROUTE_SHARD: u8 = 0x02;
17pub const FAST_FLAG_KEY_TAG: u8 = 0x04;
18
19const REQUEST_HEADER_LEN: usize = 8;
20const RESPONSE_HEADER_LEN: usize = 8;
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23#[repr(u8)]
24pub enum FastCommandKind {
25    Get = 1,
26    Set = 2,
27    SetEx = 3,
28    GetEx = 4,
29    Delete = 5,
30    Exists = 6,
31    Ttl = 7,
32    Expire = 8,
33    Ping = 9,
34    MGet = 10,
35    MSet = 11,
36    Auth = 60,
37    Hello = 61,
38    Select = 62,
39    Quit = 63,
40    Echo = 64,
41    Info = 65,
42    Command = 66,
43    CommandDocs = 67,
44    ConfigGet = 68,
45    DbSize = 69,
46    Time = 70,
47    ClientGetName = 71,
48    ClientSetName = 72,
49    ClientId = 73,
50    ClientList = 74,
51    ClientKill = 75,
52    HSet = 20,
53    HGet = 21,
54    HDel = 22,
55    HLen = 23,
56    HMGet = 24,
57    LPush = 30,
58    RPush = 31,
59    LPop = 32,
60    RPop = 33,
61    LLen = 34,
62    LIndex = 35,
63    LRange = 36,
64    SAdd = 40,
65    SRem = 41,
66    SIsMember = 42,
67    SCard = 43,
68    SMembers = 44,
69    ZAdd = 50,
70    ZRem = 51,
71    ZScore = 52,
72    ZCard = 53,
73    ZRange = 54,
74    RespCommand = 200,
75}
76
77impl FastCommandKind {
78    fn from_u8(value: u8) -> Result<Self> {
79        match value {
80            1 => Ok(Self::Get),
81            2 => Ok(Self::Set),
82            3 => Ok(Self::SetEx),
83            4 => Ok(Self::GetEx),
84            5 => Ok(Self::Delete),
85            6 => Ok(Self::Exists),
86            7 => Ok(Self::Ttl),
87            8 => Ok(Self::Expire),
88            9 => Ok(Self::Ping),
89            10 => Ok(Self::MGet),
90            11 => Ok(Self::MSet),
91            60 => Ok(Self::Auth),
92            61 => Ok(Self::Hello),
93            62 => Ok(Self::Select),
94            63 => Ok(Self::Quit),
95            64 => Ok(Self::Echo),
96            65 => Ok(Self::Info),
97            66 => Ok(Self::Command),
98            67 => Ok(Self::CommandDocs),
99            68 => Ok(Self::ConfigGet),
100            69 => Ok(Self::DbSize),
101            70 => Ok(Self::Time),
102            71 => Ok(Self::ClientGetName),
103            72 => Ok(Self::ClientSetName),
104            73 => Ok(Self::ClientId),
105            74 => Ok(Self::ClientList),
106            75 => Ok(Self::ClientKill),
107            20 => Ok(Self::HSet),
108            21 => Ok(Self::HGet),
109            22 => Ok(Self::HDel),
110            23 => Ok(Self::HLen),
111            24 => Ok(Self::HMGet),
112            30 => Ok(Self::LPush),
113            31 => Ok(Self::RPush),
114            32 => Ok(Self::LPop),
115            33 => Ok(Self::RPop),
116            34 => Ok(Self::LLen),
117            35 => Ok(Self::LIndex),
118            36 => Ok(Self::LRange),
119            40 => Ok(Self::SAdd),
120            41 => Ok(Self::SRem),
121            42 => Ok(Self::SIsMember),
122            43 => Ok(Self::SCard),
123            44 => Ok(Self::SMembers),
124            50 => Ok(Self::ZAdd),
125            51 => Ok(Self::ZRem),
126            52 => Ok(Self::ZScore),
127            53 => Ok(Self::ZCard),
128            54 => Ok(Self::ZRange),
129            200 => Ok(Self::RespCommand),
130            other => Err(FastCacheError::Protocol(format!(
131                "unsupported fast command id: {other}"
132            ))),
133        }
134    }
135}
136
137#[derive(Debug, Clone, PartialEq)]
138pub enum FastCommand<'a> {
139    Ping(Option<&'a [u8]>),
140    Auth,
141    Hello {
142        proto: Option<u64>,
143    },
144    Select {
145        db: u64,
146    },
147    Quit,
148    Echo {
149        payload: &'a [u8],
150    },
151    Info,
152    Command,
153    CommandDocs,
154    ConfigGet {
155        patterns: Vec<&'a [u8]>,
156    },
157    DbSize,
158    Time,
159    ClientGetName,
160    ClientSetName {
161        name: &'a [u8],
162    },
163    ClientId,
164    ClientList,
165    ClientKill,
166    Get {
167        key: &'a [u8],
168    },
169    Set {
170        key: &'a [u8],
171        value: &'a [u8],
172    },
173    SetEx {
174        key: &'a [u8],
175        value: &'a [u8],
176        ttl_ms: u64,
177    },
178    GetEx {
179        key: &'a [u8],
180        ttl_ms: u64,
181    },
182    Delete {
183        key: &'a [u8],
184    },
185    Exists {
186        key: &'a [u8],
187    },
188    Ttl {
189        key: &'a [u8],
190    },
191    Expire {
192        key: &'a [u8],
193        ttl_ms: u64,
194    },
195    MGet {
196        keys: Vec<&'a [u8]>,
197    },
198    MSet {
199        items: Vec<(&'a [u8], &'a [u8])>,
200    },
201    HSet {
202        key: &'a [u8],
203        field: &'a [u8],
204        value: &'a [u8],
205    },
206    HGet {
207        key: &'a [u8],
208        field: &'a [u8],
209    },
210    HDel {
211        key: &'a [u8],
212        field: &'a [u8],
213    },
214    HLen {
215        key: &'a [u8],
216    },
217    HMGet {
218        key: &'a [u8],
219        fields: Vec<&'a [u8]>,
220    },
221    LPush {
222        key: &'a [u8],
223        values: Vec<&'a [u8]>,
224    },
225    RPush {
226        key: &'a [u8],
227        values: Vec<&'a [u8]>,
228    },
229    LPop {
230        key: &'a [u8],
231    },
232    RPop {
233        key: &'a [u8],
234    },
235    LLen {
236        key: &'a [u8],
237    },
238    LIndex {
239        key: &'a [u8],
240        index: i64,
241    },
242    LRange {
243        key: &'a [u8],
244        start: i64,
245        stop: i64,
246    },
247    SAdd {
248        key: &'a [u8],
249        members: Vec<&'a [u8]>,
250    },
251    SRem {
252        key: &'a [u8],
253        members: Vec<&'a [u8]>,
254    },
255    SIsMember {
256        key: &'a [u8],
257        member: &'a [u8],
258    },
259    SCard {
260        key: &'a [u8],
261    },
262    SMembers {
263        key: &'a [u8],
264    },
265    ZAdd {
266        key: &'a [u8],
267        score: f64,
268        member: &'a [u8],
269    },
270    ZRem {
271        key: &'a [u8],
272        member: &'a [u8],
273    },
274    ZScore {
275        key: &'a [u8],
276        member: &'a [u8],
277    },
278    ZCard {
279        key: &'a [u8],
280    },
281    ZRange {
282        key: &'a [u8],
283        start: i64,
284        stop: i64,
285    },
286    /// Generic FCNP wrapper for the Redis command surface.
287    ///
288    /// The request body is a length-prefixed list of command parts, starting
289    /// with the command name. The server returns the RESP reply bytes as an
290    /// FCNP value so clients can use one native ingress path for the complete
291    /// compatibility surface while hot commands keep their typed opcodes.
292    RespCommand {
293        parts: Vec<&'a [u8]>,
294    },
295}
296
297impl<'a> FastCommand<'a> {
298    pub fn kind(&self) -> FastCommandKind {
299        match self {
300            Self::Get { .. } => FastCommandKind::Get,
301            Self::Set { .. } => FastCommandKind::Set,
302            Self::SetEx { .. } => FastCommandKind::SetEx,
303            Self::GetEx { .. } => FastCommandKind::GetEx,
304            Self::Delete { .. } => FastCommandKind::Delete,
305            Self::Exists { .. } => FastCommandKind::Exists,
306            Self::Ttl { .. } => FastCommandKind::Ttl,
307            Self::Expire { .. } => FastCommandKind::Expire,
308            Self::Ping(_) => FastCommandKind::Ping,
309            Self::MGet { .. } => FastCommandKind::MGet,
310            Self::MSet { .. } => FastCommandKind::MSet,
311            Self::Auth => FastCommandKind::Auth,
312            Self::Hello { .. } => FastCommandKind::Hello,
313            Self::Select { .. } => FastCommandKind::Select,
314            Self::Quit => FastCommandKind::Quit,
315            Self::Echo { .. } => FastCommandKind::Echo,
316            Self::Info => FastCommandKind::Info,
317            Self::Command => FastCommandKind::Command,
318            Self::CommandDocs => FastCommandKind::CommandDocs,
319            Self::ConfigGet { .. } => FastCommandKind::ConfigGet,
320            Self::DbSize => FastCommandKind::DbSize,
321            Self::Time => FastCommandKind::Time,
322            Self::ClientGetName => FastCommandKind::ClientGetName,
323            Self::ClientSetName { .. } => FastCommandKind::ClientSetName,
324            Self::ClientId => FastCommandKind::ClientId,
325            Self::ClientList => FastCommandKind::ClientList,
326            Self::ClientKill => FastCommandKind::ClientKill,
327            Self::HSet { .. } => FastCommandKind::HSet,
328            Self::HGet { .. } => FastCommandKind::HGet,
329            Self::HDel { .. } => FastCommandKind::HDel,
330            Self::HLen { .. } => FastCommandKind::HLen,
331            Self::HMGet { .. } => FastCommandKind::HMGet,
332            Self::LPush { .. } => FastCommandKind::LPush,
333            Self::RPush { .. } => FastCommandKind::RPush,
334            Self::LPop { .. } => FastCommandKind::LPop,
335            Self::RPop { .. } => FastCommandKind::RPop,
336            Self::LLen { .. } => FastCommandKind::LLen,
337            Self::LIndex { .. } => FastCommandKind::LIndex,
338            Self::LRange { .. } => FastCommandKind::LRange,
339            Self::SAdd { .. } => FastCommandKind::SAdd,
340            Self::SRem { .. } => FastCommandKind::SRem,
341            Self::SIsMember { .. } => FastCommandKind::SIsMember,
342            Self::SCard { .. } => FastCommandKind::SCard,
343            Self::SMembers { .. } => FastCommandKind::SMembers,
344            Self::ZAdd { .. } => FastCommandKind::ZAdd,
345            Self::ZRem { .. } => FastCommandKind::ZRem,
346            Self::ZScore { .. } => FastCommandKind::ZScore,
347            Self::ZCard { .. } => FastCommandKind::ZCard,
348            Self::ZRange { .. } => FastCommandKind::ZRange,
349            Self::RespCommand { .. } => FastCommandKind::RespCommand,
350        }
351    }
352
353    pub fn route_key(&self) -> Option<&'a [u8]> {
354        match self {
355            Self::Ping(_)
356            | Self::Auth
357            | Self::Hello { .. }
358            | Self::Select { .. }
359            | Self::Quit
360            | Self::Echo { .. }
361            | Self::Info
362            | Self::Command
363            | Self::CommandDocs
364            | Self::ConfigGet { .. }
365            | Self::DbSize
366            | Self::Time
367            | Self::ClientGetName
368            | Self::ClientSetName { .. }
369            | Self::ClientId
370            | Self::ClientList
371            | Self::ClientKill => None,
372            Self::RespCommand { parts } => parts.get(1).copied(),
373            Self::Get { key }
374            | Self::Set { key, .. }
375            | Self::SetEx { key, .. }
376            | Self::GetEx { key, .. }
377            | Self::Delete { key }
378            | Self::Exists { key }
379            | Self::Ttl { key }
380            | Self::Expire { key, .. }
381            | Self::HSet { key, .. }
382            | Self::HGet { key, .. }
383            | Self::HDel { key, .. }
384            | Self::HLen { key }
385            | Self::HMGet { key, .. }
386            | Self::LPush { key, .. }
387            | Self::RPush { key, .. }
388            | Self::LPop { key }
389            | Self::RPop { key }
390            | Self::LLen { key }
391            | Self::LIndex { key, .. }
392            | Self::LRange { key, .. }
393            | Self::SAdd { key, .. }
394            | Self::SRem { key, .. }
395            | Self::SIsMember { key, .. }
396            | Self::SCard { key }
397            | Self::SMembers { key }
398            | Self::ZAdd { key, .. }
399            | Self::ZRem { key, .. }
400            | Self::ZScore { key, .. }
401            | Self::ZCard { key }
402            | Self::ZRange { key, .. } => Some(key),
403            Self::MGet { keys } => keys.first().copied(),
404            Self::MSet { items } => items.first().map(|(key, _)| *key),
405        }
406    }
407}
408
409#[derive(Debug, Clone, PartialEq)]
410pub struct FastRequest<'a> {
411    pub key_hash: Option<u64>,
412    pub route_shard: Option<u32>,
413    pub key_tag: Option<u64>,
414    pub command: FastCommand<'a>,
415}
416
417#[derive(Debug, Clone, PartialEq)]
418pub enum FastResponse {
419    Ok,
420    Null,
421    Error(Vec<u8>),
422    Integer(i64),
423    Value(Vec<u8>),
424    Boolean(bool),
425    Array(Vec<Option<Vec<u8>>>),
426    Float(f64),
427}
428
429pub type FastRequestDecodeResult<'a> = Option<(FastRequest<'a>, usize)>;
430pub type FastResponseDecodeResult = Option<(FastResponse, usize)>;
431
432#[derive(Debug, Default, Clone, Copy)]
433pub struct FastCodec;
434
435impl FastCodec {
436    pub fn is_fast_request_prefix(byte: u8) -> bool {
437        byte == FAST_REQUEST_MAGIC
438    }
439
440    pub fn decode_request(buffer: &[u8]) -> Result<FastRequestDecodeResult<'_>> {
441        if buffer.is_empty() {
442            return Ok(None);
443        }
444        if buffer[0] != FAST_REQUEST_MAGIC {
445            return Err(FastCacheError::Protocol(
446                "invalid fast request magic byte".into(),
447            ));
448        }
449        if buffer.len() < REQUEST_HEADER_LEN {
450            return Ok(None);
451        }
452
453        let version = buffer[1];
454        if version != FAST_PROTOCOL_VERSION {
455            return Err(FastCacheError::Protocol(format!(
456                "unsupported fast protocol version: {version}"
457            )));
458        }
459
460        let kind = FastCommandKind::from_u8(buffer[2])?;
461        let flags = buffer[3];
462        if flags & !(FAST_FLAG_KEY_HASH | FAST_FLAG_ROUTE_SHARD | FAST_FLAG_KEY_TAG) != 0 {
463            return Err(FastCacheError::Protocol(format!(
464                "unsupported fast flags: {flags:#04x}"
465            )));
466        }
467
468        let body_len = u32::from_le_bytes(buffer[4..8].try_into().unwrap()) as usize;
469        if buffer.len() < REQUEST_HEADER_LEN + body_len {
470            return Ok(None);
471        }
472
473        let body = &buffer[REQUEST_HEADER_LEN..REQUEST_HEADER_LEN + body_len];
474        let mut cursor = 0usize;
475        let key_hash = if flags & FAST_FLAG_KEY_HASH != 0 {
476            Some(take_u64(body, &mut cursor, "fast key hash")?)
477        } else {
478            None
479        };
480        let route_shard = if flags & FAST_FLAG_ROUTE_SHARD != 0 {
481            Some(take_u32(body, &mut cursor, "fast route shard")?)
482        } else {
483            None
484        };
485        let key_tag = if flags & FAST_FLAG_KEY_TAG != 0 {
486            Some(take_u64(body, &mut cursor, "fast key tag")?)
487        } else {
488            None
489        };
490
491        let command = match kind {
492            FastCommandKind::Ping => {
493                let payload = (!body[cursor..].is_empty()).then_some(&body[cursor..]);
494                cursor = body.len();
495                FastCommand::Ping(payload)
496            }
497            FastCommandKind::Auth => FastCommand::Auth,
498            FastCommandKind::Hello => {
499                let proto = if body.len().saturating_sub(cursor) >= 8 {
500                    Some(take_u64(body, &mut cursor, "fast HELLO proto")?)
501                } else {
502                    None
503                };
504                FastCommand::Hello { proto }
505            }
506            FastCommandKind::Select => FastCommand::Select {
507                db: take_u64(body, &mut cursor, "fast SELECT db")?,
508            },
509            FastCommandKind::Quit => FastCommand::Quit,
510            FastCommandKind::Echo => FastCommand::Echo {
511                payload: take_len_prefixed_slice(body, &mut cursor, "fast ECHO payload")?,
512            },
513            FastCommandKind::Info => FastCommand::Info,
514            FastCommandKind::Command => FastCommand::Command,
515            FastCommandKind::CommandDocs => FastCommand::CommandDocs,
516            FastCommandKind::ConfigGet => FastCommand::ConfigGet {
517                patterns: take_len_prefixed_list(body, &mut cursor, "fast CONFIG GET patterns")?,
518            },
519            FastCommandKind::DbSize => FastCommand::DbSize,
520            FastCommandKind::Time => FastCommand::Time,
521            FastCommandKind::ClientGetName => FastCommand::ClientGetName,
522            FastCommandKind::ClientSetName => FastCommand::ClientSetName {
523                name: take_len_prefixed_slice(body, &mut cursor, "fast CLIENT SETNAME name")?,
524            },
525            FastCommandKind::ClientId => FastCommand::ClientId,
526            FastCommandKind::ClientList => FastCommand::ClientList,
527            FastCommandKind::ClientKill => FastCommand::ClientKill,
528            FastCommandKind::Get => FastCommand::Get {
529                key: take_len_prefixed_slice(body, &mut cursor, "fast GET key")?,
530            },
531            FastCommandKind::Set => {
532                let (key, value) = take_key_value(body, &mut cursor, "fast SET")?;
533                FastCommand::Set { key, value }
534            }
535            FastCommandKind::SetEx => {
536                let ttl_ms = take_u64(body, &mut cursor, "fast SETEX ttl")?;
537                let (key, value) = take_key_value(body, &mut cursor, "fast SETEX")?;
538                FastCommand::SetEx { key, value, ttl_ms }
539            }
540            FastCommandKind::GetEx => {
541                let ttl_ms = take_u64(body, &mut cursor, "fast GETEX ttl")?;
542                FastCommand::GetEx {
543                    key: take_len_prefixed_slice(body, &mut cursor, "fast GETEX key")?,
544                    ttl_ms,
545                }
546            }
547            FastCommandKind::Delete => FastCommand::Delete {
548                key: take_len_prefixed_slice(body, &mut cursor, "fast DELETE key")?,
549            },
550            FastCommandKind::Exists => FastCommand::Exists {
551                key: take_len_prefixed_slice(body, &mut cursor, "fast EXISTS key")?,
552            },
553            FastCommandKind::Ttl => FastCommand::Ttl {
554                key: take_len_prefixed_slice(body, &mut cursor, "fast TTL key")?,
555            },
556            FastCommandKind::Expire => {
557                let ttl_ms = take_u64(body, &mut cursor, "fast EXPIRE ttl")?;
558                FastCommand::Expire {
559                    key: take_len_prefixed_slice(body, &mut cursor, "fast EXPIRE key")?,
560                    ttl_ms,
561                }
562            }
563            FastCommandKind::MGet => FastCommand::MGet {
564                keys: take_len_prefixed_list(body, &mut cursor, "fast MGET keys")?,
565            },
566            FastCommandKind::MSet => FastCommand::MSet {
567                items: take_key_value_list(body, &mut cursor, "fast MSET items")?,
568            },
569            FastCommandKind::HSet => {
570                let (key, field, value) = take_key_field_value(body, &mut cursor, "fast HSET")?;
571                FastCommand::HSet { key, field, value }
572            }
573            FastCommandKind::HGet => {
574                let (key, field) = take_key_field(body, &mut cursor, "fast HGET")?;
575                FastCommand::HGet { key, field }
576            }
577            FastCommandKind::HDel => {
578                let (key, field) = take_key_field(body, &mut cursor, "fast HDEL")?;
579                FastCommand::HDel { key, field }
580            }
581            FastCommandKind::HLen => FastCommand::HLen {
582                key: take_len_prefixed_slice(body, &mut cursor, "fast HLEN key")?,
583            },
584            FastCommandKind::HMGet => {
585                let (key, fields) = take_key_list(body, &mut cursor, "fast HMGET")?;
586                FastCommand::HMGet { key, fields }
587            }
588            FastCommandKind::LPush => {
589                let (key, values) = take_key_list(body, &mut cursor, "fast LPUSH")?;
590                FastCommand::LPush { key, values }
591            }
592            FastCommandKind::RPush => {
593                let (key, values) = take_key_list(body, &mut cursor, "fast RPUSH")?;
594                FastCommand::RPush { key, values }
595            }
596            FastCommandKind::LPop => FastCommand::LPop {
597                key: take_len_prefixed_slice(body, &mut cursor, "fast LPOP key")?,
598            },
599            FastCommandKind::RPop => FastCommand::RPop {
600                key: take_len_prefixed_slice(body, &mut cursor, "fast RPOP key")?,
601            },
602            FastCommandKind::LLen => FastCommand::LLen {
603                key: take_len_prefixed_slice(body, &mut cursor, "fast LLEN key")?,
604            },
605            FastCommandKind::LIndex => {
606                let index = take_i64(body, &mut cursor, "fast LINDEX index")?;
607                FastCommand::LIndex {
608                    key: take_len_prefixed_slice(body, &mut cursor, "fast LINDEX key")?,
609                    index,
610                }
611            }
612            FastCommandKind::LRange => {
613                let start = take_i64(body, &mut cursor, "fast LRANGE start")?;
614                let stop = take_i64(body, &mut cursor, "fast LRANGE stop")?;
615                FastCommand::LRange {
616                    key: take_len_prefixed_slice(body, &mut cursor, "fast LRANGE key")?,
617                    start,
618                    stop,
619                }
620            }
621            FastCommandKind::SAdd => {
622                let (key, members) = take_key_list(body, &mut cursor, "fast SADD")?;
623                FastCommand::SAdd { key, members }
624            }
625            FastCommandKind::SRem => {
626                let (key, members) = take_key_list(body, &mut cursor, "fast SREM")?;
627                FastCommand::SRem { key, members }
628            }
629            FastCommandKind::SIsMember => {
630                let (key, member) = take_key_field(body, &mut cursor, "fast SISMEMBER")?;
631                FastCommand::SIsMember { key, member }
632            }
633            FastCommandKind::SCard => FastCommand::SCard {
634                key: take_len_prefixed_slice(body, &mut cursor, "fast SCARD key")?,
635            },
636            FastCommandKind::SMembers => FastCommand::SMembers {
637                key: take_len_prefixed_slice(body, &mut cursor, "fast SMEMBERS key")?,
638            },
639            FastCommandKind::ZAdd => {
640                let score = take_f64(body, &mut cursor, "fast ZADD score")?;
641                let (key, member) = take_key_field(body, &mut cursor, "fast ZADD")?;
642                FastCommand::ZAdd { key, score, member }
643            }
644            FastCommandKind::ZRem => {
645                let (key, member) = take_key_field(body, &mut cursor, "fast ZREM")?;
646                FastCommand::ZRem { key, member }
647            }
648            FastCommandKind::ZScore => {
649                let (key, member) = take_key_field(body, &mut cursor, "fast ZSCORE")?;
650                FastCommand::ZScore { key, member }
651            }
652            FastCommandKind::ZCard => FastCommand::ZCard {
653                key: take_len_prefixed_slice(body, &mut cursor, "fast ZCARD key")?,
654            },
655            FastCommandKind::ZRange => {
656                let start = take_i64(body, &mut cursor, "fast ZRANGE start")?;
657                let stop = take_i64(body, &mut cursor, "fast ZRANGE stop")?;
658                FastCommand::ZRange {
659                    key: take_len_prefixed_slice(body, &mut cursor, "fast ZRANGE key")?,
660                    start,
661                    stop,
662                }
663            }
664            FastCommandKind::RespCommand => FastCommand::RespCommand {
665                parts: take_len_prefixed_list(body, &mut cursor, "fast RESP command parts")?,
666            },
667        };
668        ensure_finished(body, cursor, "fast request")?;
669
670        Ok(Some((
671            FastRequest {
672                key_hash,
673                route_shard,
674                key_tag,
675                command,
676            },
677            REQUEST_HEADER_LEN + body_len,
678        )))
679    }
680
681    pub fn encode_request(request: &FastRequest<'_>, out: &mut Vec<u8>) {
682        let mut flags = 0u8;
683        if request.key_hash.is_some() {
684            flags |= FAST_FLAG_KEY_HASH;
685        }
686        if request.route_shard.is_some() {
687            flags |= FAST_FLAG_ROUTE_SHARD;
688        }
689        if request.key_tag.is_some() {
690            flags |= FAST_FLAG_KEY_TAG;
691        }
692
693        out.push(FAST_REQUEST_MAGIC);
694        out.push(FAST_PROTOCOL_VERSION);
695        out.push(request.command.kind() as u8);
696        out.push(flags);
697
698        let body_len_index = out.len();
699        out.extend_from_slice(&0_u32.to_le_bytes());
700        let body_start = out.len();
701
702        if let Some(key_hash) = request.key_hash {
703            out.extend_from_slice(&key_hash.to_le_bytes());
704        }
705        if let Some(route_shard) = request.route_shard {
706            out.extend_from_slice(&route_shard.to_le_bytes());
707        }
708        if let Some(key_tag) = request.key_tag {
709            out.extend_from_slice(&key_tag.to_le_bytes());
710        }
711
712        match &request.command {
713            FastCommand::Ping(payload) => {
714                if let Some(payload) = payload {
715                    out.extend_from_slice(payload);
716                }
717            }
718            FastCommand::Auth
719            | FastCommand::Quit
720            | FastCommand::Info
721            | FastCommand::Command
722            | FastCommand::CommandDocs
723            | FastCommand::DbSize
724            | FastCommand::Time
725            | FastCommand::ClientGetName
726            | FastCommand::ClientId
727            | FastCommand::ClientList
728            | FastCommand::ClientKill => {}
729            FastCommand::Hello { proto } => {
730                if let Some(proto) = proto {
731                    out.extend_from_slice(&proto.to_le_bytes());
732                }
733            }
734            FastCommand::Select { db } => {
735                out.extend_from_slice(&db.to_le_bytes());
736            }
737            FastCommand::Echo { payload } => encode_len_prefixed(payload, out),
738            FastCommand::ConfigGet { patterns } => encode_len_prefixed_list(patterns, out),
739            FastCommand::ClientSetName { name } => encode_len_prefixed(name, out),
740            FastCommand::Get { key }
741            | FastCommand::Delete { key }
742            | FastCommand::Exists { key }
743            | FastCommand::Ttl { key } => encode_len_prefixed(key, out),
744            FastCommand::Set { key, value } => encode_key_value(key, value, out),
745            FastCommand::SetEx { key, value, ttl_ms } => {
746                out.extend_from_slice(&ttl_ms.to_le_bytes());
747                encode_key_value(key, value, out);
748            }
749            FastCommand::GetEx { key, ttl_ms } | FastCommand::Expire { key, ttl_ms } => {
750                out.extend_from_slice(&ttl_ms.to_le_bytes());
751                encode_len_prefixed(key, out);
752            }
753            FastCommand::MGet { keys } => encode_len_prefixed_list(keys, out),
754            FastCommand::MSet { items } => encode_key_value_list(items, out),
755            FastCommand::HSet { key, field, value } => {
756                encode_key_field_value(key, field, value, out);
757            }
758            FastCommand::HGet { key, field }
759            | FastCommand::HDel { key, field }
760            | FastCommand::SIsMember { key, member: field }
761            | FastCommand::ZRem { key, member: field }
762            | FastCommand::ZScore { key, member: field } => {
763                encode_key_field(key, field, out);
764            }
765            FastCommand::HLen { key }
766            | FastCommand::LPop { key }
767            | FastCommand::RPop { key }
768            | FastCommand::LLen { key }
769            | FastCommand::SCard { key }
770            | FastCommand::SMembers { key }
771            | FastCommand::ZCard { key } => encode_len_prefixed(key, out),
772            FastCommand::HMGet { key, fields } => encode_key_list(key, fields, out),
773            FastCommand::LPush { key, values } | FastCommand::RPush { key, values } => {
774                encode_key_list(key, values, out);
775            }
776            FastCommand::LIndex { key, index } => {
777                out.extend_from_slice(&index.to_le_bytes());
778                encode_len_prefixed(key, out);
779            }
780            FastCommand::LRange { key, start, stop } | FastCommand::ZRange { key, start, stop } => {
781                out.extend_from_slice(&start.to_le_bytes());
782                out.extend_from_slice(&stop.to_le_bytes());
783                encode_len_prefixed(key, out);
784            }
785            FastCommand::SAdd { key, members } | FastCommand::SRem { key, members } => {
786                encode_key_list(key, members, out);
787            }
788            FastCommand::ZAdd { key, score, member } => {
789                out.extend_from_slice(&score.to_le_bytes());
790                encode_key_field(key, member, out);
791            }
792            FastCommand::RespCommand { parts } => encode_len_prefixed_list(parts, out),
793        }
794
795        let body_len = (out.len() - body_start) as u32;
796        out[body_len_index..body_len_index + 4].copy_from_slice(&body_len.to_le_bytes());
797    }
798
799    pub fn encode_response(response: &FastResponse, out: &mut Vec<u8>) {
800        out.push(FAST_RESPONSE_MAGIC);
801        out.push(FAST_PROTOCOL_VERSION);
802        match response {
803            FastResponse::Ok => {
804                out.push(0);
805                out.push(0);
806                out.extend_from_slice(&0_u32.to_le_bytes());
807            }
808            FastResponse::Null => {
809                out.push(1);
810                out.push(0);
811                out.extend_from_slice(&0_u32.to_le_bytes());
812            }
813            FastResponse::Error(message) => {
814                out.push(2);
815                out.push(0);
816                out.extend_from_slice(&(message.len() as u32).to_le_bytes());
817                out.extend_from_slice(message);
818            }
819            FastResponse::Integer(value) => {
820                out.push(3);
821                out.push(0);
822                out.extend_from_slice(&8_u32.to_le_bytes());
823                out.extend_from_slice(&value.to_le_bytes());
824            }
825            FastResponse::Value(bytes) => {
826                out.push(4);
827                out.push(0);
828                out.extend_from_slice(&(bytes.len() as u32).to_le_bytes());
829                out.extend_from_slice(bytes);
830            }
831            FastResponse::Boolean(value) => {
832                out.push(5);
833                out.push(0);
834                out.extend_from_slice(&1_u32.to_le_bytes());
835                out.push(*value as u8);
836            }
837            FastResponse::Array(values) => {
838                out.push(6);
839                out.push(0);
840                let body_len_index = out.len();
841                out.extend_from_slice(&0_u32.to_le_bytes());
842                let body_start = out.len();
843                encode_array_body(values.iter().map(|value| value.as_deref()), out);
844                let body_len = (out.len() - body_start) as u32;
845                out[body_len_index..body_len_index + 4].copy_from_slice(&body_len.to_le_bytes());
846            }
847            FastResponse::Float(value) => {
848                out.push(7);
849                out.push(0);
850                out.extend_from_slice(&8_u32.to_le_bytes());
851                out.extend_from_slice(&value.to_le_bytes());
852            }
853        }
854    }
855
856    pub fn decode_response(buffer: &[u8]) -> Result<FastResponseDecodeResult> {
857        if buffer.is_empty() {
858            return Ok(None);
859        }
860        if buffer[0] != FAST_RESPONSE_MAGIC {
861            return Err(FastCacheError::Protocol(
862                "invalid fast response magic byte".into(),
863            ));
864        }
865        if buffer.len() < RESPONSE_HEADER_LEN {
866            return Ok(None);
867        }
868
869        let version = buffer[1];
870        if version != FAST_PROTOCOL_VERSION {
871            return Err(FastCacheError::Protocol(format!(
872                "unsupported fast response protocol version: {version}"
873            )));
874        }
875
876        let status = buffer[2];
877        let _flags = buffer[3];
878        let body_len = u32::from_le_bytes(buffer[4..8].try_into().unwrap()) as usize;
879        if buffer.len() < RESPONSE_HEADER_LEN + body_len {
880            return Ok(None);
881        }
882
883        let body = &buffer[RESPONSE_HEADER_LEN..RESPONSE_HEADER_LEN + body_len];
884        let response = match status {
885            0 => FastResponse::Ok,
886            1 => FastResponse::Null,
887            2 => FastResponse::Error(body.to_vec()),
888            3 => {
889                if body.len() != 8 {
890                    return Err(FastCacheError::Protocol(
891                        "fast integer response is truncated".into(),
892                    ));
893                }
894                FastResponse::Integer(i64::from_le_bytes(body.try_into().unwrap()))
895            }
896            4 => FastResponse::Value(body.to_vec()),
897            5 => {
898                if body.len() != 1 {
899                    return Err(FastCacheError::Protocol(
900                        "fast boolean response is truncated".into(),
901                    ));
902                }
903                FastResponse::Boolean(body[0] != 0)
904            }
905            6 => FastResponse::Array(decode_array_body(body)?),
906            7 => {
907                if body.len() != 8 {
908                    return Err(FastCacheError::Protocol(
909                        "fast float response is truncated".into(),
910                    ));
911                }
912                FastResponse::Float(f64::from_le_bytes(body.try_into().unwrap()))
913            }
914            other => {
915                return Err(FastCacheError::Protocol(format!(
916                    "unsupported fast response status: {other}"
917                )));
918            }
919        };
920
921        Ok(Some((response, RESPONSE_HEADER_LEN + body_len)))
922    }
923}
924
925fn take_u32(body: &[u8], cursor: &mut usize, field: &str) -> Result<u32> {
926    if body.len().saturating_sub(*cursor) < 4 {
927        return Err(FastCacheError::Protocol(format!("{field} is truncated")));
928    }
929    let value = u32::from_le_bytes(body[*cursor..*cursor + 4].try_into().unwrap());
930    *cursor += 4;
931    Ok(value)
932}
933
934fn take_u64(body: &[u8], cursor: &mut usize, field: &str) -> Result<u64> {
935    if body.len().saturating_sub(*cursor) < 8 {
936        return Err(FastCacheError::Protocol(format!("{field} is truncated")));
937    }
938    let value = u64::from_le_bytes(body[*cursor..*cursor + 8].try_into().unwrap());
939    *cursor += 8;
940    Ok(value)
941}
942
943fn take_i64(body: &[u8], cursor: &mut usize, field: &str) -> Result<i64> {
944    if body.len().saturating_sub(*cursor) < 8 {
945        return Err(FastCacheError::Protocol(format!("{field} is truncated")));
946    }
947    let value = i64::from_le_bytes(body[*cursor..*cursor + 8].try_into().unwrap());
948    *cursor += 8;
949    Ok(value)
950}
951
952fn take_f64(body: &[u8], cursor: &mut usize, field: &str) -> Result<f64> {
953    if body.len().saturating_sub(*cursor) < 8 {
954        return Err(FastCacheError::Protocol(format!("{field} is truncated")));
955    }
956    let value = f64::from_le_bytes(body[*cursor..*cursor + 8].try_into().unwrap());
957    *cursor += 8;
958    Ok(value)
959}
960
961fn take_exact_slice<'a>(
962    body: &'a [u8],
963    cursor: &mut usize,
964    len: usize,
965    field: &str,
966) -> Result<&'a [u8]> {
967    if body.len().saturating_sub(*cursor) < len {
968        return Err(FastCacheError::Protocol(format!(
969            "{field} length does not match body"
970        )));
971    }
972    let value = &body[*cursor..*cursor + len];
973    *cursor += len;
974    Ok(value)
975}
976
977fn take_len_prefixed_slice<'a>(
978    body: &'a [u8],
979    cursor: &mut usize,
980    field: &str,
981) -> Result<&'a [u8]> {
982    let len = take_u32(body, cursor, field)? as usize;
983    take_exact_slice(body, cursor, len, field)
984}
985
986fn take_key_value<'a>(
987    body: &'a [u8],
988    cursor: &mut usize,
989    field: &str,
990) -> Result<(&'a [u8], &'a [u8])> {
991    let key_len = take_u32(body, cursor, field)? as usize;
992    let value_len = take_u32(body, cursor, field)? as usize;
993    let key = take_exact_slice(body, cursor, key_len, field)?;
994    let value = take_exact_slice(body, cursor, value_len, field)?;
995    Ok((key, value))
996}
997
998fn take_key_field<'a>(
999    body: &'a [u8],
1000    cursor: &mut usize,
1001    field: &str,
1002) -> Result<(&'a [u8], &'a [u8])> {
1003    let key_len = take_u32(body, cursor, field)? as usize;
1004    let field_len = take_u32(body, cursor, field)? as usize;
1005    let key = take_exact_slice(body, cursor, key_len, field)?;
1006    let member = take_exact_slice(body, cursor, field_len, field)?;
1007    Ok((key, member))
1008}
1009
1010fn take_key_field_value<'a>(
1011    body: &'a [u8],
1012    cursor: &mut usize,
1013    field: &str,
1014) -> Result<(&'a [u8], &'a [u8], &'a [u8])> {
1015    let key_len = take_u32(body, cursor, field)? as usize;
1016    let field_len = take_u32(body, cursor, field)? as usize;
1017    let value_len = take_u32(body, cursor, field)? as usize;
1018    let key = take_exact_slice(body, cursor, key_len, field)?;
1019    let member = take_exact_slice(body, cursor, field_len, field)?;
1020    let value = take_exact_slice(body, cursor, value_len, field)?;
1021    Ok((key, member, value))
1022}
1023
1024fn take_len_prefixed_list<'a>(
1025    body: &'a [u8],
1026    cursor: &mut usize,
1027    field: &str,
1028) -> Result<Vec<&'a [u8]>> {
1029    let count = take_u32(body, cursor, field)? as usize;
1030    let mut values = Vec::with_capacity(count);
1031    for _ in 0..count {
1032        values.push(take_len_prefixed_slice(body, cursor, field)?);
1033    }
1034    Ok(values)
1035}
1036
1037fn take_key_list<'a>(
1038    body: &'a [u8],
1039    cursor: &mut usize,
1040    field: &str,
1041) -> Result<(&'a [u8], Vec<&'a [u8]>)> {
1042    let key = take_len_prefixed_slice(body, cursor, field)?;
1043    let values = take_len_prefixed_list(body, cursor, field)?;
1044    Ok((key, values))
1045}
1046
1047fn take_key_value_list<'a>(
1048    body: &'a [u8],
1049    cursor: &mut usize,
1050    field: &str,
1051) -> Result<Vec<(&'a [u8], &'a [u8])>> {
1052    let count = take_u32(body, cursor, field)? as usize;
1053    let mut items = Vec::with_capacity(count);
1054    for _ in 0..count {
1055        items.push(take_key_value(body, cursor, field)?);
1056    }
1057    Ok(items)
1058}
1059
1060fn ensure_finished(body: &[u8], cursor: usize, field: &str) -> Result<()> {
1061    if cursor != body.len() {
1062        return Err(FastCacheError::Protocol(format!(
1063            "{field} has trailing bytes"
1064        )));
1065    }
1066    Ok(())
1067}
1068
1069fn encode_len_prefixed(value: &[u8], out: &mut Vec<u8>) {
1070    out.extend_from_slice(&(value.len() as u32).to_le_bytes());
1071    out.extend_from_slice(value);
1072}
1073
1074fn encode_key_value(key: &[u8], value: &[u8], out: &mut Vec<u8>) {
1075    out.extend_from_slice(&(key.len() as u32).to_le_bytes());
1076    out.extend_from_slice(&(value.len() as u32).to_le_bytes());
1077    out.extend_from_slice(key);
1078    out.extend_from_slice(value);
1079}
1080
1081fn encode_key_field(key: &[u8], field: &[u8], out: &mut Vec<u8>) {
1082    out.extend_from_slice(&(key.len() as u32).to_le_bytes());
1083    out.extend_from_slice(&(field.len() as u32).to_le_bytes());
1084    out.extend_from_slice(key);
1085    out.extend_from_slice(field);
1086}
1087
1088fn encode_key_field_value(key: &[u8], field: &[u8], value: &[u8], out: &mut Vec<u8>) {
1089    out.extend_from_slice(&(key.len() as u32).to_le_bytes());
1090    out.extend_from_slice(&(field.len() as u32).to_le_bytes());
1091    out.extend_from_slice(&(value.len() as u32).to_le_bytes());
1092    out.extend_from_slice(key);
1093    out.extend_from_slice(field);
1094    out.extend_from_slice(value);
1095}
1096
1097fn encode_len_prefixed_list(values: &[&[u8]], out: &mut Vec<u8>) {
1098    out.extend_from_slice(&(values.len() as u32).to_le_bytes());
1099    for value in values {
1100        encode_len_prefixed(value, out);
1101    }
1102}
1103
1104fn encode_key_list(key: &[u8], values: &[&[u8]], out: &mut Vec<u8>) {
1105    encode_len_prefixed(key, out);
1106    encode_len_prefixed_list(values, out);
1107}
1108
1109fn encode_key_value_list(items: &[(&[u8], &[u8])], out: &mut Vec<u8>) {
1110    out.extend_from_slice(&(items.len() as u32).to_le_bytes());
1111    for (key, value) in items {
1112        encode_key_value(key, value, out);
1113    }
1114}
1115
1116fn encode_array_body<'a>(values: impl IntoIterator<Item = Option<&'a [u8]>>, out: &mut Vec<u8>) {
1117    let values = values.into_iter().collect::<Vec<_>>();
1118    out.extend_from_slice(&(values.len() as u32).to_le_bytes());
1119    for value in values {
1120        match value {
1121            Some(value) => {
1122                out.extend_from_slice(&(value.len() as u32).to_le_bytes());
1123                out.extend_from_slice(value);
1124            }
1125            None => out.extend_from_slice(&u32::MAX.to_le_bytes()),
1126        }
1127    }
1128}
1129
1130fn decode_array_body(body: &[u8]) -> Result<Vec<Option<Vec<u8>>>> {
1131    let mut cursor = 0usize;
1132    let count = take_u32(body, &mut cursor, "fast array count")? as usize;
1133    let mut values = Vec::with_capacity(count);
1134    for _ in 0..count {
1135        let len = take_u32(body, &mut cursor, "fast array item")?;
1136        if len == u32::MAX {
1137            values.push(None);
1138        } else {
1139            values.push(Some(
1140                take_exact_slice(body, &mut cursor, len as usize, "fast array item")?.to_vec(),
1141            ));
1142        }
1143    }
1144    ensure_finished(body, cursor, "fast array response")?;
1145    Ok(values)
1146}
1147
1148#[cfg(test)]
1149mod tests {
1150    use super::{FAST_PROTOCOL_VERSION, FastCodec, FastCommand, FastRequest, FastResponse};
1151
1152    #[test]
1153    fn round_trips_get_request_with_hash_and_shard() {
1154        let request = FastRequest {
1155            key_hash: Some(42),
1156            route_shard: Some(3),
1157            key_tag: Some(99),
1158            command: FastCommand::Get { key: b"alpha" },
1159        };
1160        let mut encoded = Vec::new();
1161        FastCodec::encode_request(&request, &mut encoded);
1162        assert_eq!(encoded[1], FAST_PROTOCOL_VERSION);
1163        let decoded = FastCodec::decode_request(&encoded).unwrap().unwrap().0;
1164        assert_eq!(decoded, request);
1165    }
1166
1167    #[test]
1168    fn round_trips_set_request() {
1169        let request = FastRequest {
1170            key_hash: Some(7),
1171            route_shard: None,
1172            key_tag: Some(13),
1173            command: FastCommand::Set {
1174                key: b"alpha",
1175                value: b"beta",
1176            },
1177        };
1178        let mut encoded = Vec::new();
1179        FastCodec::encode_request(&request, &mut encoded);
1180        let decoded = FastCodec::decode_request(&encoded).unwrap().unwrap().0;
1181        assert_eq!(decoded, request);
1182    }
1183
1184    #[test]
1185    fn round_trips_setex_request() {
1186        let request = FastRequest {
1187            key_hash: Some(7),
1188            route_shard: None,
1189            key_tag: Some(13),
1190            command: FastCommand::SetEx {
1191                key: b"alpha",
1192                value: b"beta",
1193                ttl_ms: 60_000,
1194            },
1195        };
1196        let mut encoded = Vec::new();
1197        FastCodec::encode_request(&request, &mut encoded);
1198        let decoded = FastCodec::decode_request(&encoded).unwrap().unwrap().0;
1199        assert_eq!(decoded, request);
1200    }
1201
1202    #[test]
1203    fn round_trips_getex_request() {
1204        let request = FastRequest {
1205            key_hash: Some(11),
1206            route_shard: None,
1207            key_tag: None,
1208            command: FastCommand::GetEx {
1209                key: b"alpha",
1210                ttl_ms: 5_000,
1211            },
1212        };
1213        let mut encoded = Vec::new();
1214        FastCodec::encode_request(&request, &mut encoded);
1215        let decoded = FastCodec::decode_request(&encoded).unwrap().unwrap().0;
1216        assert_eq!(decoded, request);
1217    }
1218
1219    #[test]
1220    fn round_trips_probe_requests() {
1221        let requests = [
1222            FastRequest {
1223                key_hash: None,
1224                route_shard: None,
1225                key_tag: None,
1226                command: FastCommand::Hello { proto: Some(2) },
1227            },
1228            FastRequest {
1229                key_hash: None,
1230                route_shard: None,
1231                key_tag: None,
1232                command: FastCommand::Select { db: 0 },
1233            },
1234            FastRequest {
1235                key_hash: None,
1236                route_shard: None,
1237                key_tag: None,
1238                command: FastCommand::Echo { payload: b"hello" },
1239            },
1240            FastRequest {
1241                key_hash: None,
1242                route_shard: None,
1243                key_tag: None,
1244                command: FastCommand::ConfigGet {
1245                    patterns: vec![b"*".as_slice()],
1246                },
1247            },
1248            FastRequest {
1249                key_hash: None,
1250                route_shard: None,
1251                key_tag: None,
1252                command: FastCommand::ClientSetName { name: b"bench" },
1253            },
1254            FastRequest {
1255                key_hash: None,
1256                route_shard: None,
1257                key_tag: None,
1258                command: FastCommand::DbSize,
1259            },
1260        ];
1261
1262        for request in requests {
1263            let mut encoded = Vec::new();
1264            FastCodec::encode_request(&request, &mut encoded);
1265            let decoded = FastCodec::decode_request(&encoded).unwrap().unwrap().0;
1266            assert_eq!(decoded, request);
1267        }
1268    }
1269
1270    #[test]
1271    fn round_trips_hash_object_request() {
1272        let request = FastRequest {
1273            key_hash: Some(12),
1274            route_shard: Some(1),
1275            key_tag: None,
1276            command: FastCommand::HSet {
1277                key: b"user:1",
1278                field: b"name",
1279                value: b"ada",
1280            },
1281        };
1282        let mut encoded = Vec::new();
1283        FastCodec::encode_request(&request, &mut encoded);
1284        let decoded = FastCodec::decode_request(&encoded).unwrap().unwrap().0;
1285        assert_eq!(decoded, request);
1286    }
1287
1288    #[test]
1289    fn round_trips_multivalue_object_request() {
1290        let request = FastRequest {
1291            key_hash: Some(13),
1292            route_shard: None,
1293            key_tag: None,
1294            command: FastCommand::HMGet {
1295                key: b"user:1",
1296                fields: vec![b"name".as_slice(), b"email".as_slice()],
1297            },
1298        };
1299        let mut encoded = Vec::new();
1300        FastCodec::encode_request(&request, &mut encoded);
1301        let decoded = FastCodec::decode_request(&encoded).unwrap().unwrap().0;
1302        assert_eq!(decoded, request);
1303    }
1304
1305    #[test]
1306    fn round_trips_range_and_zadd_requests() {
1307        let range = FastRequest {
1308            key_hash: Some(14),
1309            route_shard: None,
1310            key_tag: None,
1311            command: FastCommand::LRange {
1312                key: b"items",
1313                start: -2,
1314                stop: -1,
1315            },
1316        };
1317        let zadd = FastRequest {
1318            key_hash: Some(15),
1319            route_shard: None,
1320            key_tag: None,
1321            command: FastCommand::ZAdd {
1322                key: b"scores",
1323                score: 42.5,
1324                member: b"ada",
1325            },
1326        };
1327        for request in [range, zadd] {
1328            let mut encoded = Vec::new();
1329            FastCodec::encode_request(&request, &mut encoded);
1330            let decoded = FastCodec::decode_request(&encoded).unwrap().unwrap().0;
1331            assert_eq!(decoded, request);
1332        }
1333    }
1334
1335    #[test]
1336    fn round_trips_generic_resp_command_request() {
1337        let request = FastRequest {
1338            key_hash: None,
1339            route_shard: None,
1340            key_tag: None,
1341            command: FastCommand::RespCommand {
1342                parts: vec![
1343                    b"HSET".as_slice(),
1344                    b"h".as_slice(),
1345                    b"f".as_slice(),
1346                    b"v".as_slice(),
1347                ],
1348            },
1349        };
1350        let mut encoded = Vec::new();
1351        FastCodec::encode_request(&request, &mut encoded);
1352        let decoded = FastCodec::decode_request(&encoded).unwrap().unwrap().0;
1353        assert_eq!(decoded, request);
1354        assert_eq!(decoded.command.route_key(), Some(b"h".as_slice()));
1355    }
1356
1357    #[test]
1358    fn encodes_value_response() {
1359        let mut encoded = Vec::new();
1360        FastCodec::encode_response(&FastResponse::Value(b"payload".to_vec()), &mut encoded);
1361        assert_eq!(encoded[0], super::FAST_RESPONSE_MAGIC);
1362        assert_eq!(encoded[1], FAST_PROTOCOL_VERSION);
1363    }
1364
1365    #[test]
1366    fn round_trips_integer_response() {
1367        let mut encoded = Vec::new();
1368        FastCodec::encode_response(&FastResponse::Integer(42), &mut encoded);
1369        let decoded = FastCodec::decode_response(&encoded).unwrap().unwrap().0;
1370        assert_eq!(decoded, FastResponse::Integer(42));
1371    }
1372
1373    #[test]
1374    fn round_trips_array_and_float_responses() {
1375        let array = FastResponse::Array(vec![
1376            Some(b"ada".to_vec()),
1377            None,
1378            Some(b"lovelace".to_vec()),
1379        ]);
1380        let float = FastResponse::Float(12.25);
1381        for response in [array, float] {
1382            let mut encoded = Vec::new();
1383            FastCodec::encode_response(&response, &mut encoded);
1384            let decoded = FastCodec::decode_response(&encoded).unwrap().unwrap().0;
1385            assert_eq!(decoded, response);
1386        }
1387    }
1388}