Skip to main content

embedded_redis/network/
handler.rs

1use crate::commands::auth::AuthCommand;
2use crate::commands::builder::{CommandBuilder, ToStringOption};
3use crate::commands::hello::HelloCommand;
4use crate::commands::ping::PingCommand;
5use crate::commands::Command;
6use crate::network::buffer::Network;
7use crate::network::client::{Client, CommandErrors};
8use crate::network::handler::ConnectionError::TcpConnectionFailed;
9use crate::network::protocol::{Protocol, Resp2, Resp3};
10use crate::network::response::MemoryParameters;
11use alloc::string::{String, ToString};
12use core::marker::PhantomData;
13use core::net::SocketAddr;
14use embedded_nal_async::TcpConnect;
15use embedded_time::duration::Extensions;
16use embedded_time::duration::Microseconds;
17use embedded_time::Clock;
18
19/// Error handling for connection management
20#[derive(Debug, Eq, PartialEq)]
21pub enum ConnectionError {
22    /// TCP connection failed
23    TcpConnectionFailed,
24
25    /// Authentication failed with the given sub error
26    AuthenticationError(CommandErrors),
27
28    /// Protocol switch (switch to RESP3) failed with the given sub error
29    ProtocolSwitchError(CommandErrors),
30}
31
32/// Authentication credentials
33#[derive(Clone)]
34pub struct Credentials {
35    pub(crate) username: Option<String>,
36    pub(crate) password: String,
37}
38
39impl Credentials {
40    /// Uses ACL based authentication
41    /// Required Redis version >= 6 + ACL enabled
42    pub fn acl(username: &str, password: &str) -> Self {
43        Credentials {
44            username: Some(username.to_string()),
45            password: password.to_string(),
46        }
47    }
48
49    /// Uses password-only authentication.
50    /// This form just authenticates against the password set with requirepass (Redis server conf)
51    pub fn password_only(password: &str) -> Self {
52        Self {
53            username: None,
54            password: password.to_string(),
55        }
56    }
57}
58
59/// Configuration and connection factory for Redis clients.
60///
61/// Each call to [`connect`](Self::connect) creates a new connection. The returned client owns the
62/// connection, which is closed by the `embedded-nal-async` implementation when it is dropped.
63pub struct ConnectionHandler<N: TcpConnect, P: Protocol>
64where
65    HelloCommand: Command<<P as Protocol>::FrameType>,
66{
67    /// Network details of the Redis server.
68    remote: SocketAddr,
69
70    /// Authentication credentials. `None` if authentication is disabled.
71    authentication: Option<Credentials>,
72
73    /// Maximum duration to wait for Redis responses.
74    timeout: Microseconds,
75
76    /// Parameters controlling response-buffer memory allocation.
77    memory: MemoryParameters,
78
79    /// Redis protocol implementation. RESP3 requires Redis 6.0 or newer.
80    protocol: P,
81
82    /// Whether newly opened connections are verified with a PING command.
83    use_ping: bool,
84
85    /// Associates the handler with its asynchronous network stack type without owning the stack.
86    network: PhantomData<N>,
87}
88
89impl<N: TcpConnect> ConnectionHandler<N, Resp2> {
90    /// Creates a new connection handler using RESP2 protocol
91    pub fn resp2(remote: SocketAddr) -> Self {
92        ConnectionHandler::new(remote, Resp2 {})
93    }
94}
95
96impl<N: TcpConnect> ConnectionHandler<N, Resp3> {
97    /// Creates a new connection handler using RESP3 protocol
98    pub fn resp3(remote: SocketAddr) -> Self {
99        ConnectionHandler::new(remote, Resp3 {})
100    }
101}
102
103impl<N: TcpConnect, P: Protocol> ConnectionHandler<N, P>
104where
105    AuthCommand: Command<<P as Protocol>::FrameType>,
106    HelloCommand: Command<<P as Protocol>::FrameType>,
107    PingCommand: Command<<P as Protocol>::FrameType>,
108    <P as Protocol>::FrameType: ToStringOption,
109    <P as Protocol>::FrameType: From<CommandBuilder>,
110{
111    fn new(remote: SocketAddr, protocol: P) -> Self {
112        ConnectionHandler {
113            remote,
114            authentication: None,
115            timeout: 0.microseconds(),
116            memory: MemoryParameters::default(),
117            protocol,
118            use_ping: false,
119            network: PhantomData,
120        }
121    }
122
123    /// Opens, authenticates and returns a Redis client.
124    pub async fn connect<'a, C: Clock>(
125        &self,
126        network: &'a N,
127        clock: Option<&'a C>,
128    ) -> Result<Client<'a, N::Connection<'a>, C, P>, ConnectionError> {
129        let connection = network.connect(self.remote).await.map_err(|_| TcpConnectionFailed)?;
130
131        let mut client = Client {
132            network: Network::new(connection, self.protocol.clone(), self.memory.clone()),
133            timeout_duration: self.timeout,
134            clock,
135            hello_response: None,
136        };
137
138        client.hello_response = client.init(self.authentication.clone()).await?;
139
140        if self.use_ping {
141            client.ping().await.map_err(|_| TcpConnectionFailed)?;
142        }
143
144        Ok(client)
145    }
146}
147
148impl<N: TcpConnect, P: Protocol> ConnectionHandler<N, P>
149where
150    HelloCommand: Command<<P as Protocol>::FrameType>,
151{
152    /// Sets the max. duration waiting for Redis responses
153    pub fn timeout(&mut self, timeout: Microseconds) -> &mut Self {
154        self.timeout = timeout;
155        self
156    }
157
158    /// Sets the authentication credentials
159    pub fn auth(&mut self, credentials: Credentials) -> &mut Self {
160        self.authentication = Some(credentials);
161        self
162    }
163
164    /// Uses a PING command to verify newly created connections
165    pub fn use_ping(&mut self) -> &mut Self {
166        self.use_ping = true;
167        self
168    }
169
170    /// Sets memory allocation parameters
171    pub fn memory(&mut self, parameters: MemoryParameters) -> &mut Self {
172        self.memory = parameters;
173        self
174    }
175}