Skip to main content

redis_oxide/protocol/
mod.rs

1//! Redis protocol implementations
2//!
3//! This module contains implementations for both RESP2 and RESP3 protocols,
4//! providing encoding and decoding functionality for Redis communication.
5
6pub mod resp2;
7pub mod resp2_optimized;
8pub mod resp3;
9
10// Re-export the existing protocol functionality
11pub use resp2::{RespDecoder, RespEncoder};
12pub use resp2_optimized::{OptimizedRespDecoder, OptimizedRespEncoder};
13pub use resp3::{Resp3Decoder, Resp3Encoder, Resp3Value};
14
15/// Protocol version enumeration
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
17pub enum ProtocolVersion {
18    /// RESP2 (Redis Serialization Protocol version 2)
19    #[default]
20    Resp2,
21    /// RESP3 (Redis Serialization Protocol version 3)
22    Resp3,
23}
24
25impl std::fmt::Display for ProtocolVersion {
26    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27        match self {
28            Self::Resp2 => write!(f, "RESP2"),
29            Self::Resp3 => write!(f, "RESP3"),
30        }
31    }
32}
33
34/// Protocol negotiation result
35#[derive(Debug, Clone)]
36pub struct ProtocolNegotiation {
37    /// The negotiated protocol version
38    pub version: ProtocolVersion,
39    /// Server capabilities (for RESP3)
40    pub capabilities: Vec<String>,
41}
42
43impl ProtocolNegotiation {
44    /// Create a new protocol negotiation result
45    #[must_use]
46    pub const fn new(version: ProtocolVersion) -> Self {
47        Self {
48            version,
49            capabilities: Vec::new(),
50        }
51    }
52
53    /// Create a RESP3 negotiation with capabilities
54    #[must_use]
55    pub fn resp3_with_capabilities(capabilities: Vec<String>) -> Self {
56        Self {
57            version: ProtocolVersion::Resp3,
58            capabilities,
59        }
60    }
61
62    /// Check if a capability is supported
63    #[must_use]
64    pub fn has_capability(&self, capability: &str) -> bool {
65        self.capabilities.iter().any(|c| c == capability)
66    }
67}
68
69/// Protocol negotiator for handling RESP2/RESP3 protocol selection
70pub struct ProtocolNegotiator {
71    preferred_version: ProtocolVersion,
72}
73
74impl ProtocolNegotiator {
75    /// Create a new protocol negotiator
76    #[must_use]
77    pub const fn new(preferred_version: ProtocolVersion) -> Self {
78        Self { preferred_version }
79    }
80
81    /// Negotiate protocol version with the server
82    ///
83    /// This method attempts to negotiate the preferred protocol version.
84    /// If RESP3 is preferred, it sends a HELLO command to negotiate.
85    /// Falls back to RESP2 if negotiation fails.
86    ///
87    /// # Errors
88    ///
89    /// Returns an error if protocol negotiation fails completely.
90    pub async fn negotiate<T>(
91        &self,
92        connection: &mut T,
93    ) -> crate::core::error::RedisResult<ProtocolNegotiation>
94    where
95        T: ProtocolConnection,
96    {
97        match self.preferred_version {
98            ProtocolVersion::Resp2 => Ok(ProtocolNegotiation::new(ProtocolVersion::Resp2)),
99            ProtocolVersion::Resp3 => {
100                // Try to negotiate RESP3
101                match self.try_negotiate_resp3(connection).await {
102                    Ok(negotiation) => Ok(negotiation),
103                    Err(_) => {
104                        // Fall back to RESP2
105                        Ok(ProtocolNegotiation::new(ProtocolVersion::Resp2))
106                    }
107                }
108            }
109        }
110    }
111
112    async fn try_negotiate_resp3<T>(
113        &self,
114        connection: &mut T,
115    ) -> crate::core::error::RedisResult<ProtocolNegotiation>
116    where
117        T: ProtocolConnection,
118    {
119        // Send HELLO 3 command to negotiate RESP3
120        let hello_cmd = crate::core::value::RespValue::Array(vec![
121            crate::core::value::RespValue::BulkString(bytes::Bytes::from("HELLO")),
122            crate::core::value::RespValue::BulkString(bytes::Bytes::from("3")),
123        ]);
124
125        connection.send_command(&hello_cmd).await?;
126        let response = connection.read_response().await?;
127
128        // Parse HELLO response to extract capabilities
129        match response {
130            crate::core::value::RespValue::Array(items) => {
131                let mut capabilities = Vec::new();
132
133                // HELLO response format: [server, version, proto, capabilities...]
134                if items.len() >= 4 {
135                    // Extract capabilities from the response
136                    for item in items.iter().skip(3) {
137                        if let crate::core::value::RespValue::BulkString(cap) = item {
138                            if let Ok(cap_str) = String::from_utf8(cap.to_vec()) {
139                                capabilities.push(cap_str);
140                            }
141                        }
142                    }
143                }
144
145                Ok(ProtocolNegotiation::resp3_with_capabilities(capabilities))
146            }
147            _ => Err(crate::core::error::RedisError::Protocol(
148                "Invalid HELLO response".to_string(),
149            )),
150        }
151    }
152}
153
154impl Default for ProtocolNegotiator {
155    fn default() -> Self {
156        Self::new(ProtocolVersion::Resp2)
157    }
158}
159
160/// Trait for connections that support protocol negotiation
161#[async_trait::async_trait]
162pub trait ProtocolConnection {
163    /// Send a command to the server
164    async fn send_command(
165        &mut self,
166        command: &crate::core::value::RespValue,
167    ) -> crate::core::error::RedisResult<()>;
168
169    /// Read a response from the server
170    async fn read_response(
171        &mut self,
172    ) -> crate::core::error::RedisResult<crate::core::value::RespValue>;
173}
174
175#[cfg(test)]
176mod tests {
177    use super::*;
178
179    #[test]
180    fn test_protocol_version_display() {
181        assert_eq!(ProtocolVersion::Resp2.to_string(), "RESP2");
182        assert_eq!(ProtocolVersion::Resp3.to_string(), "RESP3");
183    }
184
185    #[test]
186    fn test_protocol_negotiation() {
187        let negotiation = ProtocolNegotiation::new(ProtocolVersion::Resp2);
188        assert_eq!(negotiation.version, ProtocolVersion::Resp2);
189        assert!(negotiation.capabilities.is_empty());
190
191        let negotiation = ProtocolNegotiation::resp3_with_capabilities(vec![
192            "push".to_string(),
193            "streams".to_string(),
194        ]);
195        assert_eq!(negotiation.version, ProtocolVersion::Resp3);
196        assert!(negotiation.has_capability("push"));
197        assert!(negotiation.has_capability("streams"));
198        assert!(!negotiation.has_capability("unknown"));
199    }
200
201    #[test]
202    fn test_protocol_negotiator() {
203        let negotiator = ProtocolNegotiator::new(ProtocolVersion::Resp3);
204        assert_eq!(negotiator.preferred_version, ProtocolVersion::Resp3);
205
206        let negotiator = ProtocolNegotiator::default();
207        assert_eq!(negotiator.preferred_version, ProtocolVersion::Resp2);
208    }
209}