Skip to main content

redis_oxide/protocol/
resp2_optimized.rs

1//! Optimized RESP2 protocol implementation
2//!
3//! This module provides optimized versions of RESP2 encoding and decoding
4//! with focus on memory allocation reduction and performance improvements.
5
6#![allow(unused_variables)]
7#![allow(dead_code)]
8#![allow(missing_docs)]
9
10use crate::core::{
11    error::{RedisError, RedisResult},
12    value::RespValue,
13};
14use bytes::{Buf, BufMut, Bytes, BytesMut};
15use std::io::Cursor;
16
17const CRLF: &[u8] = b"\r\n";
18
19// Static strings for common responses to avoid allocations
20const OK_RESPONSE: &str = "OK";
21const PONG_RESPONSE: &str = "PONG";
22const QUEUED_RESPONSE: &str = "QUEUED";
23
24/// Optimized RESP2 encoder with buffer pre-sizing and zero-copy optimizations
25pub struct OptimizedRespEncoder {
26    // Reusable buffer to avoid allocations
27    buffer: BytesMut,
28}
29
30impl OptimizedRespEncoder {
31    /// Create a new optimized encoder
32    pub fn new() -> Self {
33        Self {
34            buffer: BytesMut::with_capacity(1024), // Start with reasonable capacity
35        }
36    }
37
38    /// Create a new encoder with specific initial capacity
39    pub fn with_capacity(capacity: usize) -> Self {
40        Self {
41            buffer: BytesMut::with_capacity(capacity),
42        }
43    }
44
45    /// Estimate buffer size needed for a RESP value
46    fn estimate_size(value: &RespValue) -> usize {
47        match value {
48            RespValue::SimpleString(s) => 1 + s.len() + 2, // +str\r\n
49            RespValue::Error(e) => 1 + e.len() + 2,        // -err\r\n
50            RespValue::Integer(i) => 1 + i.to_string().len() + 2, // :num\r\n
51            RespValue::BulkString(b) => {
52                let len_str = b.len().to_string();
53                1 + len_str.len() + 2 + b.len() + 2 // $len\r\ndata\r\n
54            }
55            RespValue::Null => 5, // $-1\r\n
56            RespValue::Array(arr) => {
57                let len_str = arr.len().to_string();
58                let mut size = 1 + len_str.len() + 2; // *len\r\n
59                for item in arr {
60                    size += Self::estimate_size(item);
61                }
62                size
63            }
64        }
65    }
66
67    fn estimate_bulk_argument_size(data_len: usize) -> usize {
68        1 + data_len.to_string().len() + 2 + data_len + 2
69    }
70
71    fn estimate_command_arg_size(value: &RespValue) -> usize {
72        match value {
73            RespValue::SimpleString(s) | RespValue::Error(s) => {
74                Self::estimate_bulk_argument_size(s.len())
75            }
76            RespValue::Integer(i) => Self::estimate_bulk_argument_size(i.to_string().len()),
77            RespValue::BulkString(b) => Self::estimate_bulk_argument_size(b.len()),
78            RespValue::Null => 5,
79            RespValue::Array(_) => Self::estimate_size(value),
80        }
81    }
82
83    /// Estimate buffer size needed for a command with arguments
84    fn estimate_command_size(command: &str, args: &[RespValue]) -> usize {
85        let total_items = 1 + args.len();
86        let array_header = 1 + total_items.to_string().len() + 2; // *count\r\n
87
88        // Command size
89        let cmd_size = 1 + command.len().to_string().len() + 2 + command.len() + 2; // $len\r\ncmd\r\n
90
91        // Arguments size
92        let args_size: usize = args.iter().map(Self::estimate_command_arg_size).sum();
93
94        array_header + cmd_size + args_size
95    }
96
97    /// Encode a RESP value into the internal buffer with pre-sizing
98    pub fn encode(&mut self, value: &RespValue) -> RedisResult<Bytes> {
99        let estimated_size = Self::estimate_size(value);
100
101        // Reserve capacity if needed
102        if self.buffer.capacity() < estimated_size {
103            self.buffer.reserve(estimated_size);
104        }
105
106        self.buffer.clear();
107        self.encode_value(value)?;
108        Ok(self.buffer.split().freeze())
109    }
110
111    /// Encode a command with arguments using pre-sizing
112    pub fn encode_command(&mut self, command: &str, args: &[RespValue]) -> RedisResult<Bytes> {
113        let estimated_size = Self::estimate_command_size(command, args);
114
115        // Reserve capacity if needed
116        if self.buffer.capacity() < estimated_size {
117            self.buffer.reserve(estimated_size);
118        }
119
120        self.buffer.clear();
121
122        // Create array with command + args
123        let total_len = 1 + args.len();
124        self.buffer.put_u8(b'*');
125        self.put_integer_bytes(total_len);
126        self.buffer.put_slice(CRLF);
127
128        // Encode command as bulk string
129        self.buffer.put_u8(b'$');
130        self.put_integer_bytes(command.len());
131        self.buffer.put_slice(CRLF);
132        self.buffer.put_slice(command.as_bytes());
133        self.buffer.put_slice(CRLF);
134
135        // Encode arguments
136        for arg in args {
137            self.encode_command_arg(arg)?;
138        }
139
140        Ok(self.buffer.split().freeze())
141    }
142
143    fn encode_bulk_argument(&mut self, data: &[u8]) {
144        self.buffer.put_u8(b'$');
145        self.put_integer_bytes(data.len());
146        self.buffer.put_slice(CRLF);
147        self.buffer.put_slice(data);
148        self.buffer.put_slice(CRLF);
149    }
150
151    fn encode_command_arg(&mut self, value: &RespValue) -> RedisResult<()> {
152        match value {
153            RespValue::SimpleString(s) | RespValue::Error(s) => {
154                self.encode_bulk_argument(s.as_bytes());
155            }
156            RespValue::Integer(i) => {
157                self.encode_bulk_argument(i.to_string().as_bytes());
158            }
159            RespValue::BulkString(data) => {
160                self.encode_bulk_argument(data);
161            }
162            RespValue::Null => {
163                self.buffer.put_slice(b"$-1\r\n");
164            }
165            RespValue::Array(_) => {
166                self.encode_value(value)?;
167            }
168        }
169        Ok(())
170    }
171
172    /// Internal method to encode a value into the buffer
173    fn encode_value(&mut self, value: &RespValue) -> RedisResult<()> {
174        match value {
175            RespValue::SimpleString(s) => {
176                self.buffer.put_u8(b'+');
177                // Use static strings for common responses
178                self.buffer.put_slice(s.as_bytes());
179                self.buffer.put_slice(CRLF);
180            }
181            RespValue::Error(e) => {
182                self.buffer.put_u8(b'-');
183                self.buffer.put_slice(e.as_bytes());
184                self.buffer.put_slice(CRLF);
185            }
186            RespValue::Integer(i) => {
187                self.buffer.put_u8(b':');
188                self.put_integer_bytes(*i);
189                self.buffer.put_slice(CRLF);
190            }
191            RespValue::BulkString(data) => {
192                self.buffer.put_u8(b'$');
193                self.put_integer_bytes(data.len());
194                self.buffer.put_slice(CRLF);
195                self.buffer.put_slice(data);
196                self.buffer.put_slice(CRLF);
197            }
198            RespValue::Null => {
199                self.buffer.put_slice(b"$-1\r\n");
200            }
201            RespValue::Array(arr) => {
202                self.buffer.put_u8(b'*');
203                self.put_integer_bytes(arr.len());
204                self.buffer.put_slice(CRLF);
205                for item in arr {
206                    self.encode_value(item)?;
207                }
208            }
209        }
210        Ok(())
211    }
212
213    /// Optimized integer to bytes conversion
214    fn put_integer_bytes<T: itoa::Integer>(&mut self, value: T) {
215        let mut buffer = itoa::Buffer::new();
216        let s = buffer.format(value);
217        self.buffer.put_slice(s.as_bytes());
218    }
219
220    /// Get the current buffer capacity
221    pub fn capacity(&self) -> usize {
222        self.buffer.capacity()
223    }
224
225    /// Clear the internal buffer
226    pub fn clear(&mut self) {
227        self.buffer.clear();
228    }
229
230    /// Reserve additional capacity
231    pub fn reserve(&mut self, additional: usize) {
232        self.buffer.reserve(additional);
233    }
234}
235
236impl Default for OptimizedRespEncoder {
237    fn default() -> Self {
238        Self::new()
239    }
240}
241
242/// Optimized RESP2 decoder with streaming support and reduced allocations
243pub struct OptimizedRespDecoder {
244    buffer: BytesMut,
245    // String cache for frequently used strings
246    string_cache: std::collections::HashMap<Vec<u8>, String>,
247    max_cache_size: usize,
248}
249
250impl OptimizedRespDecoder {
251    /// Create a new optimized decoder
252    pub fn new() -> Self {
253        Self {
254            buffer: BytesMut::with_capacity(4096),
255            string_cache: std::collections::HashMap::new(),
256            max_cache_size: 1000, // Limit cache size to prevent memory leaks
257        }
258    }
259
260    /// Create a new decoder with specific buffer capacity and cache size
261    pub fn with_config(buffer_capacity: usize, max_cache_size: usize) -> Self {
262        Self {
263            buffer: BytesMut::with_capacity(buffer_capacity),
264            string_cache: std::collections::HashMap::new(),
265            max_cache_size,
266        }
267    }
268
269    /// Decode data with streaming support
270    pub fn decode_streaming(&mut self, data: &[u8]) -> RedisResult<Vec<RespValue>> {
271        self.buffer.extend_from_slice(data);
272        let mut results = Vec::new();
273
274        loop {
275            let buffer_len = self.buffer.len();
276            if buffer_len == 0 {
277                break;
278            }
279
280            let buffer_slice = self.buffer.clone().freeze();
281            let mut cursor = Cursor::new(&buffer_slice[..]);
282
283            match self.try_decode_value(&mut cursor)? {
284                Some(value) => {
285                    let consumed = cursor.position() as usize;
286                    self.buffer.advance(consumed);
287                    results.push(value);
288                }
289                None => break, // Need more data
290            }
291        }
292
293        Ok(results)
294    }
295
296    /// Try to decode a single value, returning None if more data is needed
297    fn try_decode_value(&mut self, cursor: &mut Cursor<&[u8]>) -> RedisResult<Option<RespValue>> {
298        if !cursor.has_remaining() {
299            return Ok(None);
300        }
301
302        let type_byte = cursor.chunk()[0];
303        cursor.advance(1);
304
305        match type_byte {
306            b'+' => self.try_decode_simple_string(cursor),
307            b'-' => self.try_decode_error(cursor),
308            b':' => self.try_decode_integer(cursor),
309            b'$' => self.try_decode_bulk_string(cursor),
310            b'*' => self.try_decode_array(cursor),
311            _ => Err(RedisError::Protocol(format!(
312                "Invalid RESP type byte: {}",
313                type_byte as char
314            ))),
315        }
316    }
317
318    /// Try to decode a simple string with caching
319    fn try_decode_simple_string(
320        &mut self,
321        cursor: &mut Cursor<&[u8]>,
322    ) -> RedisResult<Option<RespValue>> {
323        if let Some(line) = self.try_read_line(cursor)? {
324            let string = self.bytes_to_string_cached(&line)?;
325            Ok(Some(RespValue::SimpleString(string)))
326        } else {
327            Ok(None)
328        }
329    }
330
331    /// Try to decode an error string
332    fn try_decode_error(&mut self, cursor: &mut Cursor<&[u8]>) -> RedisResult<Option<RespValue>> {
333        if let Some(line) = self.try_read_line(cursor)? {
334            let string = String::from_utf8(line)
335                .map_err(|e| RedisError::Protocol(format!("Invalid UTF-8 in error: {e}")))?;
336            Ok(Some(RespValue::Error(string)))
337        } else {
338            Ok(None)
339        }
340    }
341
342    /// Try to decode an integer
343    fn try_decode_integer(&mut self, cursor: &mut Cursor<&[u8]>) -> RedisResult<Option<RespValue>> {
344        if let Some(line) = self.try_read_line(cursor)? {
345            let s = String::from_utf8(line)
346                .map_err(|e| RedisError::Protocol(format!("Invalid UTF-8 in integer: {e}")))?;
347            let num = s
348                .parse::<i64>()
349                .map_err(|e| RedisError::Protocol(format!("Invalid integer format: {e}")))?;
350            Ok(Some(RespValue::Integer(num)))
351        } else {
352            Ok(None)
353        }
354    }
355
356    /// Try to decode a bulk string
357    fn try_decode_bulk_string(
358        &mut self,
359        cursor: &mut Cursor<&[u8]>,
360    ) -> RedisResult<Option<RespValue>> {
361        let len_line = match self.try_read_line(cursor)? {
362            Some(line) => line,
363            None => return Ok(None),
364        };
365
366        let len_str = String::from_utf8(len_line).map_err(|e| {
367            RedisError::Protocol(format!("Invalid UTF-8 in bulk string length: {e}"))
368        })?;
369        let len = len_str
370            .parse::<isize>()
371            .map_err(|e| RedisError::Protocol(format!("Invalid bulk string length: {e}")))?;
372
373        if len == -1 {
374            return Ok(Some(RespValue::Null));
375        }
376
377        if len < 0 {
378            return Err(RedisError::Protocol(
379                "Invalid bulk string length".to_string(),
380            ));
381        }
382
383        let len = len as usize;
384        if cursor.remaining() < len + 2 {
385            return Ok(None); // Need more data
386        }
387
388        let data = cursor.chunk()[..len].to_vec();
389        cursor.advance(len);
390
391        // Check for CRLF
392        if cursor.remaining() < 2 || &cursor.chunk()[..2] != CRLF {
393            return Err(RedisError::Protocol(
394                "Missing CRLF after bulk string".to_string(),
395            ));
396        }
397        cursor.advance(2);
398
399        Ok(Some(RespValue::BulkString(Bytes::from(data))))
400    }
401
402    /// Try to decode an array
403    fn try_decode_array(&mut self, cursor: &mut Cursor<&[u8]>) -> RedisResult<Option<RespValue>> {
404        let len_line = match self.try_read_line(cursor)? {
405            Some(line) => line,
406            None => return Ok(None),
407        };
408
409        let len_str = String::from_utf8(len_line)
410            .map_err(|e| RedisError::Protocol(format!("Invalid UTF-8 in array length: {e}")))?;
411        let len = len_str
412            .parse::<isize>()
413            .map_err(|e| RedisError::Protocol(format!("Invalid array length: {e}")))?;
414
415        if len == -1 {
416            return Ok(Some(RespValue::Null));
417        }
418
419        if len < 0 {
420            return Err(RedisError::Protocol("Invalid array length".to_string()));
421        }
422
423        let len = len as usize;
424        let mut elements = Vec::with_capacity(len);
425
426        for _ in 0..len {
427            match self.try_decode_value(cursor)? {
428                Some(element) => elements.push(element),
429                None => return Ok(None), // Need more data
430            }
431        }
432
433        Ok(Some(RespValue::Array(elements)))
434    }
435
436    /// Try to read a line, returning None if incomplete
437    fn try_read_line(&self, cursor: &mut Cursor<&[u8]>) -> RedisResult<Option<Vec<u8>>> {
438        let start_pos = cursor.position() as usize;
439        let remaining = cursor.get_ref();
440
441        // Look for CRLF
442        for (i, window) in remaining[start_pos..].windows(2).enumerate() {
443            if window == CRLF {
444                let line_end = start_pos + i;
445                let line = remaining[start_pos..line_end].to_vec();
446                cursor.advance(i + 2); // Skip line + CRLF
447                return Ok(Some(line));
448            }
449        }
450
451        Ok(None) // No complete line found
452    }
453
454    /// Convert bytes to string with caching for frequently used strings
455    fn bytes_to_string_cached(&mut self, bytes: &[u8]) -> RedisResult<String> {
456        // Check cache first
457        if let Some(cached) = self.string_cache.get(bytes) {
458            return Ok(cached.clone());
459        }
460
461        let string = String::from_utf8(bytes.to_vec())
462            .map_err(|e| RedisError::Protocol(format!("Invalid UTF-8: {e}")))?;
463
464        // Cache the string if cache isn't full
465        if self.string_cache.len() < self.max_cache_size {
466            self.string_cache.insert(bytes.to_vec(), string.clone());
467        }
468
469        Ok(string)
470    }
471
472    /// Clear the string cache
473    pub fn clear_cache(&mut self) {
474        self.string_cache.clear();
475    }
476
477    /// Get cache statistics
478    pub fn cache_stats(&self) -> (usize, usize) {
479        (self.string_cache.len(), self.max_cache_size)
480    }
481}
482
483impl Default for OptimizedRespDecoder {
484    fn default() -> Self {
485        Self::new()
486    }
487}
488
489#[cfg(test)]
490mod tests {
491    use super::*;
492
493    #[test]
494    fn test_optimized_encoder_simple_string() {
495        let mut encoder = OptimizedRespEncoder::new();
496        let value = RespValue::SimpleString("OK".to_string());
497        let encoded_result = encoder.encode(&value).unwrap();
498        assert_eq!(encoded_result, Bytes::from("+OK\r\n"));
499    }
500
501    #[test]
502    fn test_optimized_encoder_command() {
503        let mut encoder = OptimizedRespEncoder::new();
504        let args = vec![RespValue::from("mykey")];
505        let encoded_result = encoder.encode_command("GET", &args).unwrap();
506
507        let expected = "*2\r\n$3\r\nGET\r\n$5\r\nmykey\r\n";
508        assert_eq!(encoded_result, Bytes::from(expected));
509    }
510
511    #[test]
512    fn test_optimized_encoder_command_arguments_are_bulk_strings() {
513        let mut encoder = OptimizedRespEncoder::new();
514        let args = vec![
515            RespValue::from("items"),
516            RespValue::from(0),
517            RespValue::from(-1),
518        ];
519        let encoded_result = encoder.encode_command("LRANGE", &args).unwrap();
520
521        let expected = "*4\r\n$6\r\nLRANGE\r\n$5\r\nitems\r\n$1\r\n0\r\n$2\r\n-1\r\n";
522        assert_eq!(encoded_result, Bytes::from(expected));
523    }
524
525    #[test]
526    fn test_optimized_decoder_streaming() {
527        let mut decoder = OptimizedRespDecoder::new();
528
529        // Test partial data
530        let partial1 = b"+OK\r\n:42\r\n$5\r\nhel";
531        let results1 = decoder.decode_streaming(partial1).unwrap();
532        assert_eq!(results1.len(), 2); // Should decode +OK and :42
533
534        // Complete the bulk string
535        let partial2 = b"lo\r\n";
536        let results2 = decoder.decode_streaming(partial2).unwrap();
537        assert_eq!(results2.len(), 1); // Should decode the bulk string
538
539        match &results2[0] {
540            RespValue::BulkString(b) => assert_eq!(b, &Bytes::from("hello")),
541            _ => panic!("Expected bulk string"),
542        }
543    }
544
545    #[test]
546    fn test_size_estimation() {
547        let value = RespValue::SimpleString("OK".to_string());
548        let estimated = OptimizedRespEncoder::estimate_size(&value);
549        assert_eq!(estimated, 5); // +OK\r\n
550
551        let value = RespValue::BulkString(Bytes::from("hello"));
552        let estimated = OptimizedRespEncoder::estimate_size(&value);
553        assert_eq!(estimated, 11); // $5\r\nhello\r\n
554    }
555
556    #[test]
557    fn test_string_caching() {
558        let mut decoder = OptimizedRespDecoder::new();
559
560        // Decode the same string multiple times
561        let data = b"+OK\r\n+OK\r\n";
562        let results = decoder.decode_streaming(data).unwrap();
563
564        assert_eq!(results.len(), 2);
565        let (cache_size, _) = decoder.cache_stats();
566        assert_eq!(cache_size, 1); // "OK" should be cached
567    }
568}