Skip to main content

redis/
connection.rs

1use std::borrow::Cow;
2use std::collections::VecDeque;
3use std::fmt;
4use std::io::{self, Write};
5use std::net::{self, SocketAddr, TcpStream, ToSocketAddrs};
6use std::ops::DerefMut;
7use std::path::PathBuf;
8use std::str::{FromStr, from_utf8};
9use std::time::{Duration, Instant};
10
11use crate::cmd::{Cmd, cmd, pipe};
12use crate::errors::{ErrorKind, RedisError, ServerError, ServerErrorKind};
13use crate::io::tcp::{TcpSettings, stream_with_settings};
14use crate::parser::Parser;
15use crate::pipeline::Pipeline;
16use crate::types::{
17    FromRedisValue, HashMap, PushKind, RedisResult, SyncPushSender, ToRedisArgs, Value,
18    from_redis_value_ref,
19};
20use crate::{ProtocolVersion, check_resp3, from_redis_value};
21
22#[cfg(unix)]
23use std::os::unix::net::UnixStream;
24
25use crate::commands::resp3_hello;
26use arcstr::ArcStr;
27#[cfg(all(feature = "tls-native-tls", not(feature = "tls-rustls")))]
28use native_tls::{TlsConnector, TlsStream};
29
30#[cfg(feature = "tls-rustls")]
31use rustls::{RootCertStore, StreamOwned};
32#[cfg(feature = "tls-rustls")]
33use std::sync::Arc;
34
35use crate::PushInfo;
36
37#[cfg(all(
38    feature = "tls-rustls",
39    not(feature = "tls-native-tls"),
40    not(feature = "tls-rustls-webpki-roots")
41))]
42use rustls_native_certs::load_native_certs;
43
44#[cfg(feature = "tls-rustls")]
45use crate::tls::ClientTlsParams;
46
47// Non-exhaustive to prevent construction outside this crate
48#[derive(Clone, Debug)]
49pub struct TlsConnParams {
50    #[cfg(feature = "tls-rustls")]
51    pub(crate) client_tls_params: Option<ClientTlsParams>,
52    #[cfg(feature = "tls-rustls")]
53    pub(crate) root_cert_store: Option<RootCertStore>,
54    #[cfg(any(feature = "tls-rustls-insecure", feature = "tls-native-tls"))]
55    pub(crate) danger_accept_invalid_hostnames: bool,
56}
57
58static DEFAULT_PORT: u16 = 6379;
59
60/// Default library name to connect with
61const DEFAULT_CLIENT_SETINFO_LIB_NAME: &str = "redis-rs";
62/// Default library version to connect with
63const DEFAULT_CLIENT_SETINFO_LIB_VER: &str = env!("CARGO_PKG_VERSION");
64
65#[inline(always)]
66fn connect_tcp(addr: (&str, u16), tcp_settings: &TcpSettings) -> io::Result<TcpStream> {
67    let socket = TcpStream::connect(addr)?;
68    stream_with_settings(socket, tcp_settings)
69}
70
71#[inline(always)]
72fn connect_tcp_timeout(
73    addr: &SocketAddr,
74    timeout: Duration,
75    tcp_settings: &TcpSettings,
76) -> io::Result<TcpStream> {
77    let socket = TcpStream::connect_timeout(addr, timeout)?;
78    stream_with_settings(socket, tcp_settings)
79}
80
81/// This function takes a redis URL string and parses it into a URL
82/// as used by rust-url.
83///
84/// This is necessary as the default parser does not understand how redis URLs function.
85pub fn parse_redis_url(input: &str) -> Option<url::Url> {
86    match url::Url::parse(input) {
87        Ok(result) => match result.scheme() {
88            "redis" | "rediss" | "valkey" | "valkeys" | "redis+unix" | "valkey+unix" | "unix" => {
89                Some(result)
90            }
91            _ => None,
92        },
93        Err(_) => None,
94    }
95}
96
97/// TlsMode indicates use or do not use verification of certification.
98///
99/// Check [ConnectionAddr](ConnectionAddr::TcpTls::insecure) for more.
100#[derive(Clone, Copy, PartialEq)]
101#[non_exhaustive]
102pub enum TlsMode {
103    /// Secure verify certification.
104    Secure,
105    /// Insecure do not verify certification.
106    Insecure,
107}
108
109/// Defines the connection address.
110///
111/// Not all connection addresses are supported on all platforms.  For instance
112/// to connect to a unix socket you need to run this on an operating system
113/// that supports them.
114#[derive(Clone, Debug)]
115#[non_exhaustive]
116pub enum ConnectionAddr {
117    /// Format for this is `(host, port)`.
118    Tcp(String, u16),
119    /// Format for this is `(host, port)`.
120    TcpTls {
121        /// Hostname
122        host: String,
123        /// Port
124        port: u16,
125        /// Disable hostname verification when connecting.
126        ///
127        /// # Warning
128        ///
129        /// You should think very carefully before you use this method. If hostname
130        /// verification is not used, any valid certificate for any site will be
131        /// trusted for use from any other. This introduces a significant
132        /// vulnerability to man-in-the-middle attacks.
133        insecure: bool,
134
135        /// TLS certificates and client key.
136        tls_params: Option<TlsConnParams>,
137    },
138    /// Format for this is the path to the unix socket.
139    Unix(PathBuf),
140}
141
142impl PartialEq for ConnectionAddr {
143    fn eq(&self, other: &Self) -> bool {
144        match (self, other) {
145            (Self::Tcp(host1, port1), Self::Tcp(host2, port2)) => host1 == host2 && port1 == port2,
146            (
147                Self::TcpTls {
148                    host: host1,
149                    port: port1,
150                    insecure: insecure1,
151                    tls_params: _,
152                },
153                Self::TcpTls {
154                    host: host2,
155                    port: port2,
156                    insecure: insecure2,
157                    tls_params: _,
158                },
159            ) => port1 == port2 && host1 == host2 && insecure1 == insecure2,
160            (Self::Unix(path1), Self::Unix(path2)) => path1 == path2,
161            _ => false,
162        }
163    }
164}
165
166impl Eq for ConnectionAddr {}
167
168impl ConnectionAddr {
169    /// Checks if this address is supported.
170    ///
171    /// Because not all platforms support all connection addresses this is a
172    /// quick way to figure out if a connection method is supported. Currently
173    /// this affects:
174    ///
175    /// - Unix socket addresses, which are supported only on Unix
176    ///
177    /// - TLS addresses, which are supported only if a TLS feature is enabled
178    ///   (either `tls-native-tls` or `tls-rustls`).
179    pub fn is_supported(&self) -> bool {
180        match *self {
181            Self::Tcp(_, _) => true,
182            Self::TcpTls { .. } => {
183                cfg!(any(feature = "tls-native-tls", feature = "tls-rustls"))
184            }
185            Self::Unix(_) => cfg!(unix),
186        }
187    }
188
189    /// Configure this address to connect without checking certificate hostnames.
190    ///
191    /// # Warning
192    ///
193    /// You should think very carefully before you use this method. If hostname
194    /// verification is not used, any valid certificate for any site will be
195    /// trusted for use from any other. This introduces a significant
196    /// vulnerability to man-in-the-middle attacks.
197    #[cfg(any(feature = "tls-rustls-insecure", feature = "tls-native-tls"))]
198    pub fn set_danger_accept_invalid_hostnames(&mut self, insecure: bool) {
199        if let Self::TcpTls { tls_params, .. } = self {
200            if let Some(params) = tls_params {
201                params.danger_accept_invalid_hostnames = insecure;
202            } else if insecure {
203                *tls_params = Some(TlsConnParams {
204                    #[cfg(feature = "tls-rustls")]
205                    client_tls_params: None,
206                    #[cfg(feature = "tls-rustls")]
207                    root_cert_store: None,
208                    danger_accept_invalid_hostnames: insecure,
209                });
210            }
211        }
212    }
213
214    #[cfg(feature = "cluster")]
215    pub(crate) fn tls_mode(&self) -> Option<TlsMode> {
216        match self {
217            Self::TcpTls { insecure, .. } => {
218                if *insecure {
219                    Some(TlsMode::Insecure)
220                } else {
221                    Some(TlsMode::Secure)
222                }
223            }
224            _ => None,
225        }
226    }
227}
228
229impl fmt::Display for ConnectionAddr {
230    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
231        // Cluster::get_connection_info depends on the return value from this function
232        match *self {
233            Self::Tcp(ref host, port) | Self::TcpTls { ref host, port, .. } => {
234                write!(f, "{host}:{port}")
235            }
236            Self::Unix(ref path) => write!(f, "{}", path.display()),
237        }
238    }
239}
240
241/// Holds the connection information that redis should use for connecting.
242#[derive(Clone, Debug)]
243pub struct ConnectionInfo {
244    /// A connection address for where to connect to.
245    pub(crate) addr: ConnectionAddr,
246
247    /// The settings for the TCP connection
248    pub(crate) tcp_settings: TcpSettings,
249    /// A redis connection info for how to handshake with redis.
250    pub(crate) redis: RedisConnectionInfo,
251}
252
253impl ConnectionInfo {
254    /// Returns the connection address.
255    pub fn addr(&self) -> &ConnectionAddr {
256        &self.addr
257    }
258
259    /// Returns the settings for the TCP connection.
260    pub fn tcp_settings(&self) -> &TcpSettings {
261        &self.tcp_settings
262    }
263
264    /// Returns the redis connection info for how to handshake with redis.
265    pub fn redis_settings(&self) -> &RedisConnectionInfo {
266        &self.redis
267    }
268
269    /// Sets the connection address for where to connect to.
270    pub fn set_addr(mut self, addr: ConnectionAddr) -> Self {
271        self.addr = addr;
272        self
273    }
274
275    /// Sets the TCP settings for the connection.
276    pub fn set_tcp_settings(mut self, tcp_settings: TcpSettings) -> Self {
277        self.tcp_settings = tcp_settings;
278        self
279    }
280
281    /// Set all redis connection info fields at once.
282    pub fn set_redis_settings(mut self, redis: RedisConnectionInfo) -> Self {
283        self.redis = redis;
284        self
285    }
286}
287
288/// Redis specific/connection independent information used to establish a connection to redis.
289#[derive(Clone, Default)]
290pub struct RedisConnectionInfo {
291    /// The database number to use.  This is usually `0`.
292    pub(crate) db: i64,
293    /// Optionally a username that should be used for connection.
294    pub(crate) username: Option<ArcStr>,
295    /// Optionally a password that should be used for connection.
296    pub(crate) password: Option<ArcStr>,
297    /// Version of the protocol to use.
298    pub(crate) protocol: ProtocolVersion,
299    /// If set, the connection shouldn't send the library name and version to the server.
300    pub(crate) skip_set_lib_name: bool,
301    /// Library name to send to the server after connecting (if [`Self::skip_set_lib_name`] is `false`)
302    pub(crate) lib_name: Option<ArcStr>,
303    /// Library name to send to the server after connecting (if [`Self::skip_set_lib_name`] is `false`)
304    pub(crate) lib_ver: Option<ArcStr>,
305}
306
307impl RedisConnectionInfo {
308    /// Returns the username that should be used for connection.
309    pub fn username(&self) -> Option<&str> {
310        self.username.as_deref()
311    }
312
313    /// Returns the password that should be used for connection.
314    pub fn password(&self) -> Option<&str> {
315        self.password.as_deref()
316    }
317
318    /// Returns version of the protocol to use.
319    pub fn protocol(&self) -> ProtocolVersion {
320        self.protocol
321    }
322
323    /// Returns `true` if the `CLIENT SETINFO` command should be skipped.
324    pub fn skip_set_lib_name(&self) -> bool {
325        self.skip_set_lib_name
326    }
327
328    /// Returns the set library name
329    pub fn lib_name(&self) -> Option<&str> {
330        self.lib_name.as_deref()
331    }
332
333    /// Returns the set library version
334    pub fn lib_ver(&self) -> Option<&str> {
335        self.lib_ver.as_deref()
336    }
337
338    /// Returns the database number to use.
339    pub fn db(&self) -> i64 {
340        self.db
341    }
342
343    /// Sets the username for the connection's ACL.
344    pub fn set_username(mut self, username: impl AsRef<str>) -> Self {
345        self.username = Some(username.as_ref().into());
346        self
347    }
348
349    /// Sets the password for the connection's ACL.
350    pub fn set_password(mut self, password: impl AsRef<str>) -> Self {
351        self.password = Some(password.as_ref().into());
352        self
353    }
354
355    /// Sets the version of the RESP to use.
356    pub fn set_protocol(mut self, protocol: ProtocolVersion) -> Self {
357        self.protocol = protocol;
358        self
359    }
360
361    /// Removes the pipelined `CLIENT SETINFO` call from the connection creation.
362    ///
363    /// This function makes previously set [`lib_name`](Self::lib_name) and
364    /// [`lib_ver`](Self::lib_ver) ineffective.
365    pub fn set_skip_set_lib_name(mut self) -> Self {
366        self.skip_set_lib_name = true;
367        self
368    }
369
370    /// Sets the library information and enables sending it when connecting.
371    ///
372    /// This function clears [`skip_set_lib_name`](Self::skip_set_lib_name).
373    pub fn set_lib_name(mut self, lib_name: impl AsRef<str>, lib_ver: impl AsRef<str>) -> Self {
374        self.lib_name = Some(lib_name.as_ref().into());
375        self.lib_ver = Some(lib_ver.as_ref().into());
376        self.skip_set_lib_name = false;
377        self
378    }
379
380    /// Sets the database number to use.
381    pub fn set_db(mut self, db: i64) -> Self {
382        self.db = db;
383        self
384    }
385}
386
387impl std::fmt::Debug for RedisConnectionInfo {
388    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
389        let Self {
390            db,
391            username,
392            password,
393            protocol,
394            skip_set_lib_name,
395            lib_name,
396            lib_ver,
397        } = self;
398        let mut debug_info = f.debug_struct("RedisConnectionInfo");
399
400        debug_info.field("db", &db);
401        debug_info.field("username", &username);
402        debug_info.field("password", &password.as_ref().map(|_| "<redacted>"));
403        debug_info.field("protocol", &protocol);
404        debug_info.field("skip_set_lib_name", &skip_set_lib_name);
405        debug_info.field("lib_name", &lib_name);
406        debug_info.field("lib_ver", &lib_ver);
407
408        debug_info.finish()
409    }
410}
411
412impl FromStr for ConnectionInfo {
413    type Err = RedisError;
414
415    fn from_str(s: &str) -> Result<Self, Self::Err> {
416        s.into_connection_info()
417    }
418}
419
420/// Converts an object into a connection info struct.  This allows the
421/// constructor of the client to accept connection information in a
422/// range of different formats.
423pub trait IntoConnectionInfo {
424    /// Converts the object into a connection info object.
425    fn into_connection_info(self) -> RedisResult<ConnectionInfo>;
426}
427
428impl IntoConnectionInfo for ConnectionInfo {
429    fn into_connection_info(self) -> RedisResult<ConnectionInfo> {
430        Ok(self)
431    }
432}
433
434impl IntoConnectionInfo for ConnectionAddr {
435    fn into_connection_info(self) -> RedisResult<ConnectionInfo> {
436        Ok(ConnectionInfo {
437            addr: self,
438            redis: Default::default(),
439            tcp_settings: Default::default(),
440        })
441    }
442}
443
444/// URL format: `{redis|rediss|valkey|valkeys}://[<username>][:<password>@]<hostname>[:port][/<db>]`
445///
446/// - Basic: `redis://127.0.0.1:6379`
447/// - Username & Password: `redis://user:password@127.0.0.1:6379`
448/// - Password only: `redis://:password@127.0.0.1:6379`
449/// - Specifying DB: `redis://127.0.0.1:6379/0`
450/// - Enabling TLS: `rediss://127.0.0.1:6379`
451/// - Enabling Insecure TLS: `rediss://127.0.0.1:6379/#insecure`
452/// - Enabling RESP3: `redis://127.0.0.1:6379/?protocol=resp3`
453impl IntoConnectionInfo for &str {
454    fn into_connection_info(self) -> RedisResult<ConnectionInfo> {
455        match parse_redis_url(self) {
456            Some(u) => u.into_connection_info(),
457            None => fail!((ErrorKind::InvalidClientConfig, "Redis URL did not parse")),
458        }
459    }
460}
461
462impl<T> IntoConnectionInfo for (T, u16)
463where
464    T: Into<String>,
465{
466    fn into_connection_info(self) -> RedisResult<ConnectionInfo> {
467        Ok(ConnectionInfo {
468            addr: ConnectionAddr::Tcp(self.0.into(), self.1),
469            redis: RedisConnectionInfo::default(),
470            tcp_settings: TcpSettings::default(),
471        })
472    }
473}
474
475/// URL format: `{redis|rediss|valkey|valkeys}://[<username>][:<password>@]<hostname>[:port][/<db>]`
476///
477/// - Basic: `redis://127.0.0.1:6379`
478/// - Username & Password: `redis://user:password@127.0.0.1:6379`
479/// - Password only: `redis://:password@127.0.0.1:6379`
480/// - Specifying DB: `redis://127.0.0.1:6379/0`
481/// - Enabling TLS: `rediss://127.0.0.1:6379`
482/// - Enabling Insecure TLS: `rediss://127.0.0.1:6379/#insecure`
483/// - Enabling RESP3: `redis://127.0.0.1:6379/?protocol=resp3`
484impl IntoConnectionInfo for String {
485    fn into_connection_info(self) -> RedisResult<ConnectionInfo> {
486        match parse_redis_url(&self) {
487            Some(u) => u.into_connection_info(),
488            None => fail!((ErrorKind::InvalidClientConfig, "Redis URL did not parse")),
489        }
490    }
491}
492
493fn parse_protocol(query: &HashMap<Cow<str>, Cow<str>>) -> RedisResult<ProtocolVersion> {
494    Ok(match query.get("protocol") {
495        Some(protocol) => {
496            if protocol == "2" || protocol == "resp2" {
497                ProtocolVersion::RESP2
498            } else if protocol == "3" || protocol == "resp3" {
499                ProtocolVersion::RESP3
500            } else {
501                fail!((
502                    ErrorKind::InvalidClientConfig,
503                    "Invalid protocol version",
504                    protocol.to_string()
505                ))
506            }
507        }
508        None => ProtocolVersion::RESP2,
509    })
510}
511
512#[inline]
513pub(crate) fn is_wildcard_address(address: &str) -> bool {
514    address == "0.0.0.0" || address == "::"
515}
516
517fn url_to_tcp_connection_info(url: url::Url) -> RedisResult<ConnectionInfo> {
518    let host = match url.host() {
519        Some(host) => {
520            // Here we manually match host's enum arms and call their to_string().
521            // Because url.host().to_string() will add `[` and `]` for ipv6:
522            // https://docs.rs/url/latest/src/url/host.rs.html#170
523            // And these brackets will break host.parse::<Ipv6Addr>() when
524            // `client.open()` - `ActualConnection::new()` - `addr.to_socket_addrs()`:
525            // https://doc.rust-lang.org/src/std/net/addr.rs.html#963
526            // https://doc.rust-lang.org/src/std/net/parser.rs.html#158
527            // IpAddr string with brackets can ONLY parse to SocketAddrV6:
528            // https://doc.rust-lang.org/src/std/net/parser.rs.html#255
529            // But if we call Ipv6Addr.to_string directly, it follows rfc5952 without brackets:
530            // https://doc.rust-lang.org/src/std/net/ip.rs.html#1755
531            let host_str = match host {
532                url::Host::Domain(path) => path.to_string(),
533                url::Host::Ipv4(v4) => v4.to_string(),
534                url::Host::Ipv6(v6) => v6.to_string(),
535            };
536
537            if is_wildcard_address(&host_str) {
538                return Err(RedisError::from((
539                    ErrorKind::InvalidClientConfig,
540                    "Cannot connect to a wildcard address (0.0.0.0 or ::)",
541                )));
542            }
543            host_str
544        }
545        None => fail!((ErrorKind::InvalidClientConfig, "Missing hostname")),
546    };
547    let port = url.port().unwrap_or(DEFAULT_PORT);
548    let addr = if url.scheme() == "rediss" || url.scheme() == "valkeys" {
549        #[cfg(any(feature = "tls-native-tls", feature = "tls-rustls"))]
550        {
551            match url.fragment() {
552                Some("insecure") => ConnectionAddr::TcpTls {
553                    host,
554                    port,
555                    insecure: true,
556                    tls_params: None,
557                },
558                Some(_) => fail!((
559                    ErrorKind::InvalidClientConfig,
560                    "only #insecure is supported as URL fragment"
561                )),
562                _ => ConnectionAddr::TcpTls {
563                    host,
564                    port,
565                    insecure: false,
566                    tls_params: None,
567                },
568            }
569        }
570
571        #[cfg(not(any(feature = "tls-native-tls", feature = "tls-rustls")))]
572        fail!((
573            ErrorKind::InvalidClientConfig,
574            "can't connect with TLS, the feature is not enabled"
575        ));
576    } else {
577        ConnectionAddr::Tcp(host, port)
578    };
579    let query: HashMap<_, _> = url.query_pairs().collect();
580    Ok(ConnectionInfo {
581        addr,
582        redis: RedisConnectionInfo {
583            db: match url.path().trim_matches('/') {
584                "" => 0,
585                path => path.parse::<i64>().map_err(|_| -> RedisError {
586                    (ErrorKind::InvalidClientConfig, "Invalid database number").into()
587                })?,
588            },
589            username: if url.username().is_empty() {
590                None
591            } else {
592                match percent_encoding::percent_decode(url.username().as_bytes()).decode_utf8() {
593                    Ok(decoded) => Some(decoded.into()),
594                    Err(_) => fail!((
595                        ErrorKind::InvalidClientConfig,
596                        "Username is not valid UTF-8 string"
597                    )),
598                }
599            },
600            password: match url.password() {
601                Some(pw) => match percent_encoding::percent_decode(pw.as_bytes()).decode_utf8() {
602                    Ok(decoded) => Some(decoded.into()),
603                    Err(_) => fail!((
604                        ErrorKind::InvalidClientConfig,
605                        "Password is not valid UTF-8 string"
606                    )),
607                },
608                None => None,
609            },
610            protocol: parse_protocol(&query)?,
611            skip_set_lib_name: false,
612            lib_name: None,
613            lib_ver: None,
614        },
615        tcp_settings: TcpSettings::default(),
616    })
617}
618
619#[cfg(unix)]
620fn url_to_unix_connection_info(url: url::Url) -> RedisResult<ConnectionInfo> {
621    let query: HashMap<_, _> = url.query_pairs().collect();
622    Ok(ConnectionInfo {
623        addr: ConnectionAddr::Unix(url.to_file_path().map_err(|_| -> RedisError {
624            (ErrorKind::InvalidClientConfig, "Missing path").into()
625        })?),
626        redis: RedisConnectionInfo {
627            db: match query.get("db") {
628                Some(db) => db.parse::<i64>().map_err(|_| -> RedisError {
629                    (ErrorKind::InvalidClientConfig, "Invalid database number").into()
630                })?,
631
632                None => 0,
633            },
634            username: query.get("user").map(|username| username.as_ref().into()),
635            password: query.get("pass").map(|password| password.as_ref().into()),
636            protocol: parse_protocol(&query)?,
637            skip_set_lib_name: false,
638            lib_name: None,
639            lib_ver: None,
640        },
641        tcp_settings: TcpSettings::default(),
642    })
643}
644
645#[cfg(not(unix))]
646fn url_to_unix_connection_info(_: url::Url) -> RedisResult<ConnectionInfo> {
647    fail!((
648        ErrorKind::InvalidClientConfig,
649        "Unix sockets are not available on this platform."
650    ));
651}
652
653impl IntoConnectionInfo for url::Url {
654    fn into_connection_info(self) -> RedisResult<ConnectionInfo> {
655        match self.scheme() {
656            "redis" | "rediss" | "valkey" | "valkeys" => url_to_tcp_connection_info(self),
657            "unix" | "redis+unix" | "valkey+unix" => url_to_unix_connection_info(self),
658            _ => fail!((
659                ErrorKind::InvalidClientConfig,
660                "URL provided is not a redis URL"
661            )),
662        }
663    }
664}
665
666struct TcpConnection {
667    reader: TcpStream,
668    open: bool,
669}
670
671#[cfg(all(feature = "tls-native-tls", not(feature = "tls-rustls")))]
672struct TcpNativeTlsConnection {
673    reader: TlsStream<TcpStream>,
674    open: bool,
675}
676
677#[cfg(feature = "tls-rustls")]
678struct TcpRustlsConnection {
679    reader: StreamOwned<rustls::ClientConnection, TcpStream>,
680    open: bool,
681}
682
683#[cfg(unix)]
684struct UnixConnection {
685    sock: UnixStream,
686    open: bool,
687}
688
689enum ActualConnection {
690    Tcp(TcpConnection),
691    #[cfg(all(feature = "tls-native-tls", not(feature = "tls-rustls")))]
692    TcpNativeTls(Box<TcpNativeTlsConnection>),
693    #[cfg(feature = "tls-rustls")]
694    TcpRustls(Box<TcpRustlsConnection>),
695    #[cfg(unix)]
696    Unix(UnixConnection),
697}
698
699#[cfg(feature = "tls-rustls-insecure")]
700struct NoCertificateVerification {
701    supported: rustls::crypto::WebPkiSupportedAlgorithms,
702}
703
704#[cfg(feature = "tls-rustls-insecure")]
705impl rustls::client::danger::ServerCertVerifier for NoCertificateVerification {
706    fn verify_server_cert(
707        &self,
708        _end_entity: &rustls::pki_types::CertificateDer<'_>,
709        _intermediates: &[rustls::pki_types::CertificateDer<'_>],
710        _server_name: &rustls::pki_types::ServerName<'_>,
711        _ocsp_response: &[u8],
712        _now: rustls::pki_types::UnixTime,
713    ) -> Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
714        Ok(rustls::client::danger::ServerCertVerified::assertion())
715    }
716
717    fn verify_tls12_signature(
718        &self,
719        _message: &[u8],
720        _cert: &rustls::pki_types::CertificateDer<'_>,
721        _dss: &rustls::DigitallySignedStruct,
722    ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
723        Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
724    }
725
726    fn verify_tls13_signature(
727        &self,
728        _message: &[u8],
729        _cert: &rustls::pki_types::CertificateDer<'_>,
730        _dss: &rustls::DigitallySignedStruct,
731    ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
732        Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
733    }
734
735    fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
736        self.supported.supported_schemes()
737    }
738}
739
740#[cfg(feature = "tls-rustls-insecure")]
741impl fmt::Debug for NoCertificateVerification {
742    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
743        f.debug_struct("NoCertificateVerification").finish()
744    }
745}
746
747/// Insecure `ServerCertVerifier` for rustls that implements `danger_accept_invalid_hostnames`.
748#[cfg(feature = "tls-rustls-insecure")]
749#[derive(Debug)]
750struct AcceptInvalidHostnamesCertVerifier {
751    inner: Arc<rustls::client::WebPkiServerVerifier>,
752}
753
754#[cfg(feature = "tls-rustls-insecure")]
755fn is_hostname_error(err: &rustls::Error) -> bool {
756    matches!(
757        err,
758        rustls::Error::InvalidCertificate(
759            rustls::CertificateError::NotValidForName
760                | rustls::CertificateError::NotValidForNameContext { .. }
761        )
762    )
763}
764
765#[cfg(feature = "tls-rustls-insecure")]
766impl rustls::client::danger::ServerCertVerifier for AcceptInvalidHostnamesCertVerifier {
767    fn verify_server_cert(
768        &self,
769        end_entity: &rustls::pki_types::CertificateDer<'_>,
770        intermediates: &[rustls::pki_types::CertificateDer<'_>],
771        server_name: &rustls::pki_types::ServerName<'_>,
772        ocsp_response: &[u8],
773        now: rustls::pki_types::UnixTime,
774    ) -> Result<rustls::client::danger::ServerCertVerified, rustls::Error> {
775        self.inner
776            .verify_server_cert(end_entity, intermediates, server_name, ocsp_response, now)
777            .or_else(|err| {
778                if is_hostname_error(&err) {
779                    Ok(rustls::client::danger::ServerCertVerified::assertion())
780                } else {
781                    Err(err)
782                }
783            })
784    }
785
786    fn verify_tls12_signature(
787        &self,
788        message: &[u8],
789        cert: &rustls::pki_types::CertificateDer<'_>,
790        dss: &rustls::DigitallySignedStruct,
791    ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
792        self.inner
793            .verify_tls12_signature(message, cert, dss)
794            .or_else(|err| {
795                if is_hostname_error(&err) {
796                    Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
797                } else {
798                    Err(err)
799                }
800            })
801    }
802
803    fn verify_tls13_signature(
804        &self,
805        message: &[u8],
806        cert: &rustls::pki_types::CertificateDer<'_>,
807        dss: &rustls::DigitallySignedStruct,
808    ) -> Result<rustls::client::danger::HandshakeSignatureValid, rustls::Error> {
809        self.inner
810            .verify_tls13_signature(message, cert, dss)
811            .or_else(|err| {
812                if is_hostname_error(&err) {
813                    Ok(rustls::client::danger::HandshakeSignatureValid::assertion())
814                } else {
815                    Err(err)
816                }
817            })
818    }
819
820    fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
821        self.inner.supported_verify_schemes()
822    }
823}
824
825/// Represents a stateful redis TCP connection.
826pub struct Connection {
827    con: ActualConnection,
828    parser: Parser,
829    db: i64,
830
831    /// Flag indicating whether the connection was left in the PubSub state after dropping `PubSub`.
832    ///
833    /// This flag is checked when attempting to send a command, and if it's raised, we attempt to
834    /// exit the pubsub state before executing the new request.
835    pubsub: bool,
836
837    // Field indicating which protocol to use for server communications.
838    protocol: ProtocolVersion,
839
840    /// This is used to manage Push messages in RESP3 mode.
841    push_sender: Option<SyncPushSender>,
842
843    /// The number of messages that are expected to be returned from the server,
844    /// but the user no longer waits for - answers for requests that already returned a transient error.
845    messages_to_skip: usize,
846}
847
848/// Represents a RESP2 pubsub connection.
849///
850/// If you're using a DB that supports RESP3, consider using a regular connection and setting a push sender it using [Connection::set_push_sender].
851pub struct PubSub<'a> {
852    con: &'a mut Connection,
853    waiting_messages: VecDeque<Msg>,
854}
855
856/// Represents a pubsub message.
857#[derive(Debug, Clone)]
858pub struct Msg {
859    payload: Value,
860    channel: Value,
861    pattern: Option<Value>,
862}
863
864impl ActualConnection {
865    pub fn new(
866        addr: &ConnectionAddr,
867        timeout: Option<Duration>,
868        tcp_settings: &TcpSettings,
869    ) -> RedisResult<Self> {
870        Ok(match *addr {
871            ConnectionAddr::Tcp(ref host, ref port) => {
872                if is_wildcard_address(host) {
873                    fail!((
874                        ErrorKind::InvalidClientConfig,
875                        "Cannot connect to a wildcard address (0.0.0.0 or ::)"
876                    ));
877                }
878                let addr = (host.as_str(), *port);
879                let tcp = match timeout {
880                    None => connect_tcp(addr, tcp_settings)?,
881                    Some(timeout) => {
882                        let mut tcp = None;
883                        let mut last_error = None;
884                        for addr in addr.to_socket_addrs()? {
885                            match connect_tcp_timeout(&addr, timeout, tcp_settings) {
886                                Ok(l) => {
887                                    tcp = Some(l);
888                                    break;
889                                }
890                                Err(e) => {
891                                    last_error = Some(e);
892                                }
893                            }
894                        }
895                        match (tcp, last_error) {
896                            (Some(tcp), _) => tcp,
897                            (None, Some(e)) => {
898                                fail!(e);
899                            }
900                            (None, None) => {
901                                fail!((
902                                    ErrorKind::InvalidClientConfig,
903                                    "could not resolve to any addresses"
904                                ));
905                            }
906                        }
907                    }
908                };
909                Self::Tcp(TcpConnection {
910                    reader: tcp,
911                    open: true,
912                })
913            }
914            #[cfg(all(feature = "tls-native-tls", not(feature = "tls-rustls")))]
915            ConnectionAddr::TcpTls {
916                ref host,
917                port,
918                insecure,
919                ref tls_params,
920            } => {
921                let tls_connector = if insecure {
922                    TlsConnector::builder()
923                        .danger_accept_invalid_certs(true)
924                        .danger_accept_invalid_hostnames(true)
925                        .use_sni(false)
926                        .build()?
927                } else if let Some(params) = tls_params {
928                    TlsConnector::builder()
929                        .danger_accept_invalid_hostnames(params.danger_accept_invalid_hostnames)
930                        .build()?
931                } else {
932                    TlsConnector::new()?
933                };
934                let addr = (host.as_str(), port);
935                let tls = match timeout {
936                    None => {
937                        let tcp = connect_tcp(addr, tcp_settings)?;
938                        match tls_connector.connect(host, tcp) {
939                            Ok(res) => res,
940                            Err(e) => {
941                                fail!((ErrorKind::Io, "SSL Handshake error", e.to_string()));
942                            }
943                        }
944                    }
945                    Some(timeout) => {
946                        let mut tcp = None;
947                        let mut last_error = None;
948                        for addr in (host.as_str(), port).to_socket_addrs()? {
949                            match connect_tcp_timeout(&addr, timeout, tcp_settings) {
950                                Ok(l) => {
951                                    tcp = Some(l);
952                                    break;
953                                }
954                                Err(e) => {
955                                    last_error = Some(e);
956                                }
957                            };
958                        }
959                        match (tcp, last_error) {
960                            (Some(tcp), _) => tls_connector.connect(host, tcp).unwrap(),
961                            (None, Some(e)) => {
962                                fail!(e);
963                            }
964                            (None, None) => {
965                                fail!((
966                                    ErrorKind::InvalidClientConfig,
967                                    "could not resolve to any addresses"
968                                ));
969                            }
970                        }
971                    }
972                };
973                ActualConnection::TcpNativeTls(Box::new(TcpNativeTlsConnection {
974                    reader: tls,
975                    open: true,
976                }))
977            }
978            #[cfg(feature = "tls-rustls")]
979            ConnectionAddr::TcpTls {
980                ref host,
981                port,
982                insecure,
983                ref tls_params,
984            } => {
985                let host: &str = host;
986                let config = create_rustls_config(insecure, tls_params.clone())?;
987                let conn = rustls::ClientConnection::new(
988                    Arc::new(config),
989                    rustls::pki_types::ServerName::try_from(host)?.to_owned(),
990                )?;
991                let reader = match timeout {
992                    None => {
993                        let tcp = connect_tcp((host, port), tcp_settings)?;
994                        StreamOwned::new(conn, tcp)
995                    }
996                    Some(timeout) => {
997                        let mut tcp = None;
998                        let mut last_error = None;
999                        for addr in (host, port).to_socket_addrs()? {
1000                            match connect_tcp_timeout(&addr, timeout, tcp_settings) {
1001                                Ok(l) => {
1002                                    tcp = Some(l);
1003                                    break;
1004                                }
1005                                Err(e) => {
1006                                    last_error = Some(e);
1007                                }
1008                            }
1009                        }
1010                        match (tcp, last_error) {
1011                            (Some(tcp), _) => StreamOwned::new(conn, tcp),
1012                            (None, Some(e)) => {
1013                                fail!(e);
1014                            }
1015                            (None, None) => {
1016                                fail!((
1017                                    ErrorKind::InvalidClientConfig,
1018                                    "could not resolve to any addresses"
1019                                ));
1020                            }
1021                        }
1022                    }
1023                };
1024
1025                Self::TcpRustls(Box::new(TcpRustlsConnection { reader, open: true }))
1026            }
1027            #[cfg(not(any(feature = "tls-native-tls", feature = "tls-rustls")))]
1028            ConnectionAddr::TcpTls { .. } => {
1029                fail!((
1030                    ErrorKind::InvalidClientConfig,
1031                    "Cannot connect to TCP with TLS without the tls feature"
1032                ));
1033            }
1034            #[cfg(unix)]
1035            ConnectionAddr::Unix(ref path) => Self::Unix(UnixConnection {
1036                sock: UnixStream::connect(path)?,
1037                open: true,
1038            }),
1039            #[cfg(not(unix))]
1040            ConnectionAddr::Unix(ref _path) => {
1041                fail!((
1042                    ErrorKind::InvalidClientConfig,
1043                    "Cannot connect to unix sockets \
1044                     on this platform"
1045                ));
1046            }
1047        })
1048    }
1049
1050    pub fn send_bytes(&mut self, bytes: &[u8]) -> RedisResult<Value> {
1051        match *self {
1052            Self::Tcp(ref mut connection) => {
1053                let res = connection.reader.write_all(bytes).map_err(RedisError::from);
1054                match res {
1055                    Err(e) => {
1056                        if e.is_unrecoverable_error() {
1057                            connection.open = false;
1058                        }
1059                        Err(e)
1060                    }
1061                    Ok(_) => Ok(Value::Okay),
1062                }
1063            }
1064            #[cfg(all(feature = "tls-native-tls", not(feature = "tls-rustls")))]
1065            ActualConnection::TcpNativeTls(ref mut connection) => {
1066                let res = connection.reader.write_all(bytes).map_err(RedisError::from);
1067                match res {
1068                    Err(e) => {
1069                        if e.is_unrecoverable_error() {
1070                            connection.open = false;
1071                        }
1072                        Err(e)
1073                    }
1074                    Ok(_) => Ok(Value::Okay),
1075                }
1076            }
1077            #[cfg(feature = "tls-rustls")]
1078            Self::TcpRustls(ref mut connection) => {
1079                let res = connection.reader.write_all(bytes).map_err(RedisError::from);
1080                match res {
1081                    Err(e) => {
1082                        if e.is_unrecoverable_error() {
1083                            connection.open = false;
1084                        }
1085                        Err(e)
1086                    }
1087                    Ok(_) => Ok(Value::Okay),
1088                }
1089            }
1090            #[cfg(unix)]
1091            Self::Unix(ref mut connection) => {
1092                let result = connection.sock.write_all(bytes).map_err(RedisError::from);
1093                match result {
1094                    Err(e) => {
1095                        if e.is_unrecoverable_error() {
1096                            connection.open = false;
1097                        }
1098                        Err(e)
1099                    }
1100                    Ok(_) => Ok(Value::Okay),
1101                }
1102            }
1103        }
1104    }
1105
1106    pub fn set_write_timeout(&self, dur: Option<Duration>) -> RedisResult<()> {
1107        match *self {
1108            Self::Tcp(TcpConnection { ref reader, .. }) => {
1109                reader.set_write_timeout(dur)?;
1110            }
1111            #[cfg(all(feature = "tls-native-tls", not(feature = "tls-rustls")))]
1112            ActualConnection::TcpNativeTls(ref boxed_tls_connection) => {
1113                let reader = &(boxed_tls_connection.reader);
1114                reader.get_ref().set_write_timeout(dur)?;
1115            }
1116            #[cfg(feature = "tls-rustls")]
1117            Self::TcpRustls(ref boxed_tls_connection) => {
1118                let reader = &(boxed_tls_connection.reader);
1119                reader.get_ref().set_write_timeout(dur)?;
1120            }
1121            #[cfg(unix)]
1122            Self::Unix(UnixConnection { ref sock, .. }) => {
1123                sock.set_write_timeout(dur)?;
1124            }
1125        }
1126        Ok(())
1127    }
1128
1129    pub fn set_read_timeout(&self, dur: Option<Duration>) -> RedisResult<()> {
1130        match *self {
1131            Self::Tcp(TcpConnection { ref reader, .. }) => {
1132                reader.set_read_timeout(dur)?;
1133            }
1134            #[cfg(all(feature = "tls-native-tls", not(feature = "tls-rustls")))]
1135            ActualConnection::TcpNativeTls(ref boxed_tls_connection) => {
1136                let reader = &(boxed_tls_connection.reader);
1137                reader.get_ref().set_read_timeout(dur)?;
1138            }
1139            #[cfg(feature = "tls-rustls")]
1140            Self::TcpRustls(ref boxed_tls_connection) => {
1141                let reader = &(boxed_tls_connection.reader);
1142                reader.get_ref().set_read_timeout(dur)?;
1143            }
1144            #[cfg(unix)]
1145            Self::Unix(UnixConnection { ref sock, .. }) => {
1146                sock.set_read_timeout(dur)?;
1147            }
1148        }
1149        Ok(())
1150    }
1151
1152    pub fn is_open(&self) -> bool {
1153        match *self {
1154            Self::Tcp(TcpConnection { open, .. }) => open,
1155            #[cfg(all(feature = "tls-native-tls", not(feature = "tls-rustls")))]
1156            ActualConnection::TcpNativeTls(ref boxed_tls_connection) => boxed_tls_connection.open,
1157            #[cfg(feature = "tls-rustls")]
1158            Self::TcpRustls(ref boxed_tls_connection) => boxed_tls_connection.open,
1159            #[cfg(unix)]
1160            Self::Unix(UnixConnection { open, .. }) => open,
1161        }
1162    }
1163}
1164
1165#[cfg(feature = "tls-rustls")]
1166pub(crate) fn create_rustls_config(
1167    insecure: bool,
1168    tls_params: Option<TlsConnParams>,
1169) -> RedisResult<rustls::ClientConfig> {
1170    #[allow(unused_mut)]
1171    let mut root_store = RootCertStore::empty();
1172    #[cfg(feature = "tls-rustls-webpki-roots")]
1173    root_store.extend(webpki_roots::TLS_SERVER_ROOTS.iter().cloned());
1174    #[cfg(all(
1175        feature = "tls-rustls",
1176        not(feature = "tls-native-tls"),
1177        not(feature = "tls-rustls-webpki-roots")
1178    ))]
1179    {
1180        let mut certificate_result = load_native_certs();
1181        if let Some(error) = certificate_result.errors.pop() {
1182            return Err(error.into());
1183        }
1184        for cert in certificate_result.certs {
1185            root_store.add(cert)?;
1186        }
1187    }
1188
1189    let config = rustls::ClientConfig::builder();
1190    let config = if let Some(tls_params) = tls_params {
1191        let root_cert_store = tls_params.root_cert_store.unwrap_or(root_store);
1192        let config_builder = config.with_root_certificates(root_cert_store.clone());
1193
1194        let config_builder = if let Some(ClientTlsParams {
1195            client_cert_chain: client_cert,
1196            client_key,
1197        }) = tls_params.client_tls_params
1198        {
1199            config_builder
1200                .with_client_auth_cert(client_cert, client_key)
1201                .map_err(|err| {
1202                    RedisError::from((
1203                        ErrorKind::InvalidClientConfig,
1204                        "Unable to build client with TLS parameters provided.",
1205                        err.to_string(),
1206                    ))
1207                })?
1208        } else {
1209            config_builder.with_no_client_auth()
1210        };
1211
1212        // Implement `danger_accept_invalid_hostnames`.
1213        //
1214        // The strange cfg here is to handle a specific unusual combination of features: if
1215        // `tls-native-tls` and `tls-rustls` are enabled, but `tls-rustls-insecure` is not, and the
1216        // application tries to use the danger flag.
1217        #[cfg(any(feature = "tls-rustls-insecure", feature = "tls-native-tls"))]
1218        let config_builder = if !insecure && tls_params.danger_accept_invalid_hostnames {
1219            #[cfg(not(feature = "tls-rustls-insecure"))]
1220            {
1221                // This code should not enable an insecure mode if the `insecure` feature is not
1222                // set, but it shouldn't silently ignore the flag either. So return an error.
1223                fail!((
1224                    ErrorKind::InvalidClientConfig,
1225                    "Cannot create insecure client via danger_accept_invalid_hostnames without tls-rustls-insecure feature"
1226                ));
1227            }
1228
1229            #[cfg(feature = "tls-rustls-insecure")]
1230            {
1231                let mut config = config_builder;
1232                config.dangerous().set_certificate_verifier(Arc::new(
1233                    AcceptInvalidHostnamesCertVerifier {
1234                        inner: rustls::client::WebPkiServerVerifier::builder(Arc::new(
1235                            root_cert_store,
1236                        ))
1237                        .build()
1238                        .map_err(|err| rustls::Error::from(rustls::OtherError(Arc::new(err))))?,
1239                    },
1240                ));
1241                config
1242            }
1243        } else {
1244            config_builder
1245        };
1246
1247        config_builder
1248    } else {
1249        config
1250            .with_root_certificates(root_store)
1251            .with_no_client_auth()
1252    };
1253
1254    match (insecure, cfg!(feature = "tls-rustls-insecure")) {
1255        #[cfg(feature = "tls-rustls-insecure")]
1256        (true, true) => {
1257            let mut config = config;
1258            config.enable_sni = false;
1259            let Some(crypto_provider) = rustls::crypto::CryptoProvider::get_default() else {
1260                return Err(RedisError::from((
1261                    ErrorKind::InvalidClientConfig,
1262                    "No crypto provider available for rustls",
1263                )));
1264            };
1265            config
1266                .dangerous()
1267                .set_certificate_verifier(Arc::new(NoCertificateVerification {
1268                    supported: crypto_provider.signature_verification_algorithms,
1269                }));
1270
1271            Ok(config)
1272        }
1273        (true, false) => {
1274            fail!((
1275                ErrorKind::InvalidClientConfig,
1276                "Cannot create insecure client without tls-rustls-insecure feature"
1277            ));
1278        }
1279        _ => Ok(config),
1280    }
1281}
1282
1283pub(crate) fn authenticate_cmd(username: Option<&str>, password: &str) -> Cmd {
1284    let mut command = cmd("AUTH");
1285
1286    if let Some(username) = &username {
1287        command.arg(username);
1288    }
1289
1290    command.arg(password);
1291    command
1292}
1293
1294pub fn connect(
1295    connection_info: &ConnectionInfo,
1296    timeout: Option<Duration>,
1297) -> RedisResult<Connection> {
1298    let start = Instant::now();
1299    let con: ActualConnection = ActualConnection::new(
1300        &connection_info.addr,
1301        timeout,
1302        &connection_info.tcp_settings,
1303    )?;
1304
1305    // we temporarily set the timeout, and will remove it after finishing setup.
1306    let remaining_timeout = timeout.and_then(|timeout| timeout.checked_sub(start.elapsed()));
1307    // TLS could run logic that doesn't contain a timeout, and should fail if it takes too long.
1308    if timeout.is_some() && remaining_timeout.is_none() {
1309        return Err(RedisError::from(std::io::Error::new(
1310            std::io::ErrorKind::TimedOut,
1311            "Connection timed out",
1312        )));
1313    }
1314    con.set_read_timeout(remaining_timeout)?;
1315    con.set_write_timeout(remaining_timeout)?;
1316
1317    let con = setup_connection(
1318        con,
1319        &connection_info.redis,
1320        #[cfg(feature = "cache-aio")]
1321        None,
1322    )?;
1323
1324    // remove the temporary timeout.
1325    con.set_read_timeout(None)?;
1326    con.set_write_timeout(None)?;
1327
1328    Ok(con)
1329}
1330
1331pub(crate) struct ConnectionSetupComponents {
1332    resp3_auth_cmd_idx: Option<usize>,
1333    resp2_auth_cmd_idx: Option<usize>,
1334    select_cmd_idx: Option<usize>,
1335    #[cfg(feature = "cache-aio")]
1336    cache_cmd_idx: Option<usize>,
1337}
1338
1339pub(crate) fn connection_setup_pipeline(
1340    connection_info: &RedisConnectionInfo,
1341    check_username: bool,
1342    #[cfg(feature = "cache-aio")] cache_config: Option<crate::caching::CacheConfig>,
1343) -> (crate::Pipeline, ConnectionSetupComponents) {
1344    let mut pipeline = pipe();
1345    let (authenticate_with_resp3_cmd_index, authenticate_with_resp2_cmd_index) =
1346        if connection_info.protocol.supports_resp3() {
1347            pipeline.add_command(resp3_hello(connection_info));
1348            (Some(0), None)
1349        } else if let Some(password) = connection_info.password.as_ref() {
1350            pipeline.add_command(authenticate_cmd(
1351                check_username.then(|| connection_info.username()).flatten(),
1352                password,
1353            ));
1354            (None, Some(0))
1355        } else {
1356            (None, None)
1357        };
1358
1359    let select_db_cmd_index = (connection_info.db != 0)
1360        .then(|| pipeline.len())
1361        .inspect(|_| {
1362            pipeline.cmd("SELECT").arg(connection_info.db);
1363        });
1364
1365    #[cfg(feature = "cache-aio")]
1366    let cache_cmd_index = cache_config.map(|cache_config| {
1367        pipeline.cmd("CLIENT").arg("TRACKING").arg("ON");
1368        match cache_config.mode {
1369            crate::caching::CacheMode::All => {}
1370            crate::caching::CacheMode::OptIn => {
1371                pipeline.arg("OPTIN");
1372            }
1373        }
1374        pipeline.len() - 1
1375    });
1376
1377    // result is ignored, as per the command's instructions.
1378    // https://redis.io/commands/client-setinfo/
1379    if !connection_info.skip_set_lib_name {
1380        pipeline
1381            .cmd("CLIENT")
1382            .arg("SETINFO")
1383            .arg("LIB-NAME")
1384            .arg(
1385                connection_info
1386                    .lib_name
1387                    .as_ref()
1388                    .map_or(DEFAULT_CLIENT_SETINFO_LIB_NAME, ArcStr::as_str),
1389            )
1390            .ignore();
1391        pipeline
1392            .cmd("CLIENT")
1393            .arg("SETINFO")
1394            .arg("LIB-VER")
1395            .arg(
1396                connection_info
1397                    .lib_ver
1398                    .as_ref()
1399                    .map_or(DEFAULT_CLIENT_SETINFO_LIB_VER, ArcStr::as_str),
1400            )
1401            .ignore();
1402    }
1403
1404    (
1405        pipeline,
1406        ConnectionSetupComponents {
1407            resp3_auth_cmd_idx: authenticate_with_resp3_cmd_index,
1408            resp2_auth_cmd_idx: authenticate_with_resp2_cmd_index,
1409            select_cmd_idx: select_db_cmd_index,
1410            #[cfg(feature = "cache-aio")]
1411            cache_cmd_idx: cache_cmd_index,
1412        },
1413    )
1414}
1415
1416fn check_resp3_auth(result: &Value) -> RedisResult<()> {
1417    if let Value::ServerError(err) = result {
1418        return Err(get_resp3_hello_command_error(err.clone().into()));
1419    }
1420    Ok(())
1421}
1422
1423#[derive(PartialEq)]
1424pub(crate) enum AuthResult {
1425    Succeeded,
1426    ShouldRetryWithoutUsername,
1427}
1428
1429fn check_resp2_auth(result: &Value) -> RedisResult<AuthResult> {
1430    let err = match result {
1431        Value::Okay => {
1432            return Ok(AuthResult::Succeeded);
1433        }
1434        Value::ServerError(err) => err,
1435        _ => {
1436            return Err((
1437                ServerErrorKind::ResponseError.into(),
1438                "Redis server refused to authenticate, returns Ok() != Value::Okay",
1439            )
1440                .into());
1441        }
1442    };
1443
1444    let err_msg = err.details().ok_or((
1445        ErrorKind::AuthenticationFailed,
1446        "Password authentication failed",
1447    ))?;
1448    if !err_msg.contains("wrong number of arguments for 'auth' command") {
1449        return Err((
1450            ErrorKind::AuthenticationFailed,
1451            "Password authentication failed",
1452        )
1453            .into());
1454    }
1455    Ok(AuthResult::ShouldRetryWithoutUsername)
1456}
1457
1458fn check_db_select(value: &Value) -> RedisResult<()> {
1459    let Value::ServerError(err) = value else {
1460        return Ok(());
1461    };
1462
1463    match err.details() {
1464        Some(err_msg) => Err((
1465            ServerErrorKind::ResponseError.into(),
1466            "Redis server refused to switch database",
1467            err_msg.to_string(),
1468        )
1469            .into()),
1470        None => Err((
1471            ServerErrorKind::ResponseError.into(),
1472            "Redis server refused to switch database",
1473        )
1474            .into()),
1475    }
1476}
1477
1478#[cfg(feature = "cache-aio")]
1479fn check_caching(result: &Value) -> RedisResult<()> {
1480    match result {
1481        Value::Okay => Ok(()),
1482        _ => Err((
1483            ServerErrorKind::ResponseError.into(),
1484            "Client-side caching returned unknown response",
1485            format!("{result:?}"),
1486        )
1487            .into()),
1488    }
1489}
1490
1491pub(crate) fn check_connection_setup(
1492    results: Vec<Value>,
1493    ConnectionSetupComponents {
1494        resp3_auth_cmd_idx,
1495        resp2_auth_cmd_idx,
1496        select_cmd_idx,
1497        #[cfg(feature = "cache-aio")]
1498        cache_cmd_idx,
1499    }: ConnectionSetupComponents,
1500) -> RedisResult<AuthResult> {
1501    // can't have both values set
1502    assert!(!(resp2_auth_cmd_idx.is_some() && resp3_auth_cmd_idx.is_some()));
1503
1504    if let Some(index) = resp3_auth_cmd_idx {
1505        let Some(value) = results.get(index) else {
1506            return Err((ErrorKind::Client, "Missing RESP3 auth response").into());
1507        };
1508        check_resp3_auth(value)?;
1509    } else if let Some(index) = resp2_auth_cmd_idx {
1510        let Some(value) = results.get(index) else {
1511            return Err((ErrorKind::Client, "Missing RESP2 auth response").into());
1512        };
1513        if check_resp2_auth(value)? == AuthResult::ShouldRetryWithoutUsername {
1514            return Ok(AuthResult::ShouldRetryWithoutUsername);
1515        }
1516    }
1517
1518    if let Some(index) = select_cmd_idx {
1519        let Some(value) = results.get(index) else {
1520            return Err((ErrorKind::Client, "Missing SELECT DB response").into());
1521        };
1522        check_db_select(value)?;
1523    }
1524
1525    #[cfg(feature = "cache-aio")]
1526    if let Some(index) = cache_cmd_idx {
1527        let Some(value) = results.get(index) else {
1528            return Err((ErrorKind::Client, "Missing Caching response").into());
1529        };
1530        check_caching(value)?;
1531    }
1532
1533    Ok(AuthResult::Succeeded)
1534}
1535
1536fn execute_connection_pipeline(
1537    rv: &mut Connection,
1538    (pipeline, instructions): (crate::Pipeline, ConnectionSetupComponents),
1539) -> RedisResult<AuthResult> {
1540    if pipeline.is_empty() {
1541        return Ok(AuthResult::Succeeded);
1542    }
1543    let results = rv.req_packed_commands(&pipeline.get_packed_pipeline(), 0, pipeline.len())?;
1544
1545    check_connection_setup(results, instructions)
1546}
1547
1548fn setup_connection(
1549    con: ActualConnection,
1550    connection_info: &RedisConnectionInfo,
1551    #[cfg(feature = "cache-aio")] cache_config: Option<crate::caching::CacheConfig>,
1552) -> RedisResult<Connection> {
1553    let mut rv = Connection {
1554        con,
1555        parser: Parser::new(),
1556        db: connection_info.db,
1557        pubsub: false,
1558        protocol: connection_info.protocol,
1559        push_sender: None,
1560        messages_to_skip: 0,
1561    };
1562
1563    if execute_connection_pipeline(
1564        &mut rv,
1565        connection_setup_pipeline(
1566            connection_info,
1567            true,
1568            #[cfg(feature = "cache-aio")]
1569            cache_config,
1570        ),
1571    )? == AuthResult::ShouldRetryWithoutUsername
1572    {
1573        execute_connection_pipeline(
1574            &mut rv,
1575            connection_setup_pipeline(
1576                connection_info,
1577                false,
1578                #[cfg(feature = "cache-aio")]
1579                cache_config,
1580            ),
1581        )?;
1582    }
1583
1584    Ok(rv)
1585}
1586
1587/// Implements the "stateless" part of the connection interface that is used by the
1588/// different objects in redis-rs.
1589///
1590/// Primarily it obviously applies to `Connection` object but also some other objects
1591///  implement the interface (for instance whole clients or certain redis results).
1592///
1593/// Generally clients and connections (as well as redis results of those) implement
1594/// this trait.  Actual connections provide more functionality which can be used
1595/// to implement things like `PubSub` but they also can modify the intrinsic
1596/// state of the TCP connection.  This is not possible with `ConnectionLike`
1597/// implementors because that functionality is not exposed.
1598pub trait ConnectionLike {
1599    /// Sends an already encoded (packed) command into the TCP socket and
1600    /// reads the single response from it.
1601    fn req_packed_command(&mut self, cmd: &[u8]) -> RedisResult<Value>;
1602
1603    /// Sends multiple already encoded (packed) command into the TCP socket
1604    /// and reads `count` responses from it.  This is used to implement
1605    /// pipelining.
1606    /// Important - this function is meant for internal usage, since it's
1607    /// easy to pass incorrect `offset` & `count` parameters, which might
1608    /// cause the connection to enter an erroneous state. Users shouldn't
1609    /// call it, instead using the Pipeline::query function.
1610    #[doc(hidden)]
1611    fn req_packed_commands(
1612        &mut self,
1613        cmd: &[u8],
1614        offset: usize,
1615        count: usize,
1616    ) -> RedisResult<Vec<Value>>;
1617
1618    /// Sends a [Cmd] into the TCP socket and reads a single response from it.
1619    fn req_command(&mut self, cmd: &Cmd) -> RedisResult<Value> {
1620        let pcmd = cmd.get_packed_command();
1621        self.req_packed_command(&pcmd)
1622    }
1623
1624    /// Returns the database this connection is bound to.  Note that this
1625    /// information might be unreliable because it's initially cached and
1626    /// also might be incorrect if the connection like object is not
1627    /// actually connected.
1628    fn get_db(&self) -> i64;
1629
1630    /// Does this connection support pipelining?
1631    #[doc(hidden)]
1632    fn supports_pipelining(&self) -> bool {
1633        true
1634    }
1635
1636    /// Check that all connections it has are available (`PING` internally).
1637    fn check_connection(&mut self) -> bool;
1638
1639    /// Returns the connection status.
1640    ///
1641    /// The connection is open until any `read` call received an
1642    /// invalid response from the server (most likely a closed or dropped
1643    /// connection, otherwise a Redis protocol error). When using unix
1644    /// sockets the connection is open until writing a command failed with a
1645    /// `BrokenPipe` error.
1646    fn is_open(&self) -> bool;
1647}
1648
1649/// A connection is an object that represents a single redis connection.  It
1650/// provides basic support for sending encoded commands into a redis connection
1651/// and to read a response from it.  It's bound to a single database and can
1652/// only be created from the client.
1653///
1654/// You generally do not much with this object other than passing it to
1655/// `Cmd` objects.
1656impl Connection {
1657    /// Sends an already encoded (packed) command into the TCP socket and
1658    /// does not read a response.  This is useful for commands like
1659    /// `MONITOR` which yield multiple items.  This needs to be used with
1660    /// care because it changes the state of the connection.
1661    pub fn send_packed_command(&mut self, cmd: &[u8]) -> RedisResult<()> {
1662        self.send_bytes(cmd)?;
1663        Ok(())
1664    }
1665
1666    /// Fetches a single response from the connection.  This is useful
1667    /// if used in combination with `send_packed_command`.
1668    pub fn recv_response(&mut self) -> RedisResult<Value> {
1669        self.read(true)
1670    }
1671
1672    /// Sets the write timeout for the connection.
1673    ///
1674    /// If the provided value is `None`, then `send_packed_command` call will
1675    /// block indefinitely. It is an error to pass the zero `Duration` to this
1676    /// method.
1677    pub fn set_write_timeout(&self, dur: Option<Duration>) -> RedisResult<()> {
1678        self.con.set_write_timeout(dur)
1679    }
1680
1681    /// Sets the read timeout for the connection.
1682    ///
1683    /// If the provided value is `None`, then `recv_response` call will
1684    /// block indefinitely. It is an error to pass the zero `Duration` to this
1685    /// method.
1686    pub fn set_read_timeout(&self, dur: Option<Duration>) -> RedisResult<()> {
1687        self.con.set_read_timeout(dur)
1688    }
1689
1690    /// Creates a [`PubSub`] instance for this connection.
1691    pub fn as_pubsub(&mut self) -> PubSub<'_> {
1692        // NOTE: The pubsub flag is intentionally not raised at this time since
1693        // running commands within the pubsub state should not try and exit from
1694        // the pubsub state.
1695        PubSub::new(self)
1696    }
1697
1698    fn exit_pubsub(&mut self) -> RedisResult<()> {
1699        let res = self.clear_active_subscriptions();
1700        if res.is_ok() {
1701            self.pubsub = false;
1702        } else {
1703            // Raise the pubsub flag to indicate the connection is "stuck" in that state.
1704            self.pubsub = true;
1705        }
1706
1707        res
1708    }
1709
1710    /// Get the inner connection out of a PubSub
1711    ///
1712    /// Any active subscriptions are unsubscribed. In the event of an error, the connection is
1713    /// dropped.
1714    fn clear_active_subscriptions(&mut self) -> RedisResult<()> {
1715        // Responses to unsubscribe commands return in a 3-tuple with values
1716        // ("unsubscribe" or "punsubscribe", name of subscription removed, count of remaining subs).
1717        // The "count of remaining subs" includes both pattern subscriptions and non pattern
1718        // subscriptions. Thus, to accurately drain all unsubscribe messages received from the
1719        // server, both commands need to be executed at once.
1720        {
1721            // Prepare both unsubscribe commands
1722            let unsubscribe = cmd("UNSUBSCRIBE").get_packed_command();
1723            let punsubscribe = cmd("PUNSUBSCRIBE").get_packed_command();
1724
1725            // Execute commands
1726            self.send_bytes(&unsubscribe)?;
1727            self.send_bytes(&punsubscribe)?;
1728        }
1729
1730        // Receive responses
1731        //
1732        // There will be at minimum two responses - 1 for each of punsubscribe and unsubscribe
1733        // commands. There may be more responses if there are active subscriptions. In this case,
1734        // messages are received until the _subscription count_ in the responses reach zero.
1735        let mut received_unsub = false;
1736        let mut received_punsub = false;
1737
1738        loop {
1739            let resp = self.recv_response()?;
1740
1741            match resp {
1742                Value::Push { kind, data } => {
1743                    if data.len() >= 2
1744                        && let Value::Int(num) = data[1]
1745                        && resp3_is_pub_sub_state_cleared(
1746                            &mut received_unsub,
1747                            &mut received_punsub,
1748                            &kind,
1749                            num as isize,
1750                        )
1751                    {
1752                        break;
1753                    }
1754                }
1755                Value::ServerError(err) => {
1756                    // a new error behavior, introduced in valkey 8.
1757                    // https://github.com/valkey-io/valkey/pull/759
1758                    if err.kind() == Some(ServerErrorKind::NoSub) {
1759                        if no_sub_err_is_pub_sub_state_cleared(
1760                            &mut received_unsub,
1761                            &mut received_punsub,
1762                            &err,
1763                        ) {
1764                            break;
1765                        } else {
1766                            continue;
1767                        }
1768                    }
1769
1770                    return Err(err.into());
1771                }
1772                Value::Array(vec) => {
1773                    let res: (Vec<u8>, (), isize) = from_redis_value(Value::Array(vec))?;
1774                    if resp2_is_pub_sub_state_cleared(
1775                        &mut received_unsub,
1776                        &mut received_punsub,
1777                        &res.0,
1778                        res.2,
1779                    ) {
1780                        break;
1781                    }
1782                }
1783                _ => {
1784                    return Err((
1785                        ErrorKind::Client,
1786                        "Unexpected unsubscribe response",
1787                        format!("{resp:?}"),
1788                    )
1789                        .into());
1790                }
1791            }
1792        }
1793
1794        // Finally, the connection is back in its normal state since all subscriptions were
1795        // cancelled *and* all unsubscribe messages were received.
1796        Ok(())
1797    }
1798
1799    fn send_push(&self, push: PushInfo) {
1800        if let Some(sender) = &self.push_sender {
1801            let _ = sender.send(push);
1802        }
1803    }
1804
1805    fn try_send(&self, value: &RedisResult<Value>) {
1806        if let Ok(Value::Push { kind, data }) = value {
1807            self.send_push(PushInfo {
1808                kind: kind.clone(),
1809                data: data.clone(),
1810            });
1811        }
1812    }
1813
1814    fn send_disconnect(&self) {
1815        self.send_push(PushInfo::disconnect());
1816    }
1817
1818    fn close_connection(&mut self) {
1819        // Notify the PushManager that the connection was lost
1820        self.send_disconnect();
1821        match self.con {
1822            ActualConnection::Tcp(ref mut connection) => {
1823                let _ = connection.reader.shutdown(net::Shutdown::Both);
1824                connection.open = false;
1825            }
1826            #[cfg(all(feature = "tls-native-tls", not(feature = "tls-rustls")))]
1827            ActualConnection::TcpNativeTls(ref mut connection) => {
1828                let _ = connection.reader.shutdown();
1829                connection.open = false;
1830            }
1831            #[cfg(feature = "tls-rustls")]
1832            ActualConnection::TcpRustls(ref mut connection) => {
1833                let _ = connection.reader.get_mut().shutdown(net::Shutdown::Both);
1834                connection.open = false;
1835            }
1836            #[cfg(unix)]
1837            ActualConnection::Unix(ref mut connection) => {
1838                let _ = connection.sock.shutdown(net::Shutdown::Both);
1839                connection.open = false;
1840            }
1841        }
1842    }
1843
1844    /// Fetches a single message from the connection. If the message is a response,
1845    /// increment `messages_to_skip` if it wasn't received before a timeout.
1846    fn read(&mut self, is_response: bool) -> RedisResult<Value> {
1847        loop {
1848            let result = match self.con {
1849                ActualConnection::Tcp(TcpConnection { ref mut reader, .. }) => {
1850                    self.parser.parse_value(reader)
1851                }
1852                #[cfg(all(feature = "tls-native-tls", not(feature = "tls-rustls")))]
1853                ActualConnection::TcpNativeTls(ref mut boxed_tls_connection) => {
1854                    let reader = &mut boxed_tls_connection.reader;
1855                    self.parser.parse_value(reader)
1856                }
1857                #[cfg(feature = "tls-rustls")]
1858                ActualConnection::TcpRustls(ref mut boxed_tls_connection) => {
1859                    let reader = &mut boxed_tls_connection.reader;
1860                    self.parser.parse_value(reader)
1861                }
1862                #[cfg(unix)]
1863                ActualConnection::Unix(UnixConnection { ref mut sock, .. }) => {
1864                    self.parser.parse_value(sock)
1865                }
1866            };
1867            self.try_send(&result);
1868
1869            let Err(err) = &result else {
1870                if self.messages_to_skip > 0 {
1871                    self.messages_to_skip -= 1;
1872                    continue;
1873                }
1874                return result;
1875            };
1876            let Some(io_error) = err.as_io_error() else {
1877                if self.messages_to_skip > 0 {
1878                    self.messages_to_skip -= 1;
1879                    continue;
1880                }
1881                return result;
1882            };
1883            // shutdown connection on protocol error
1884            if io_error.kind() == io::ErrorKind::UnexpectedEof {
1885                self.close_connection();
1886            } else if is_response {
1887                self.messages_to_skip += 1;
1888            }
1889
1890            return result;
1891        }
1892    }
1893
1894    /// Sets sender channel for push values.
1895    pub fn set_push_sender(&mut self, sender: SyncPushSender) {
1896        self.push_sender = Some(sender);
1897    }
1898
1899    fn send_bytes(&mut self, bytes: &[u8]) -> RedisResult<Value> {
1900        if bytes.is_empty() {
1901            return Err(RedisError::make_empty_command());
1902        }
1903        let result = self.con.send_bytes(bytes);
1904        if self.protocol.supports_resp3()
1905            && let Err(e) = &result
1906            && e.is_connection_dropped()
1907        {
1908            self.send_disconnect();
1909        }
1910        result
1911    }
1912
1913    /// Subscribes to a new channel(s).
1914    ///
1915    /// This only works if the connection was configured with [ProtocolVersion::RESP3] and [Self::set_push_sender].
1916    pub fn subscribe_resp3<T: ToRedisArgs>(&mut self, channel: T) -> RedisResult<()> {
1917        check_resp3!(self.protocol);
1918        cmd("SUBSCRIBE")
1919            .arg(channel)
1920            .set_no_response(true)
1921            .exec(self)
1922    }
1923
1924    /// Subscribes to new channel(s) with pattern(s).
1925    ///
1926    /// This only works if the connection was configured with [ProtocolVersion::RESP3] and [Self::set_push_sender].
1927    pub fn psubscribe_resp3<T: ToRedisArgs>(&mut self, pchannel: T) -> RedisResult<()> {
1928        check_resp3!(self.protocol);
1929        cmd("PSUBSCRIBE")
1930            .arg(pchannel)
1931            .set_no_response(true)
1932            .exec(self)
1933    }
1934
1935    /// Unsubscribes from a channel(s).
1936    ///
1937    /// This only works if the connection was configured with [ProtocolVersion::RESP3] and [Self::set_push_sender].
1938    pub fn unsubscribe_resp3<T: ToRedisArgs>(&mut self, channel: T) -> RedisResult<()> {
1939        check_resp3!(self.protocol);
1940        cmd("UNSUBSCRIBE")
1941            .arg(channel)
1942            .set_no_response(true)
1943            .exec(self)
1944    }
1945
1946    /// Unsubscribes from channel pattern(s).
1947    ///
1948    /// This only works if the connection was configured with [ProtocolVersion::RESP3] and [Self::set_push_sender].
1949    pub fn punsubscribe_resp3<T: ToRedisArgs>(&mut self, pchannel: T) -> RedisResult<()> {
1950        check_resp3!(self.protocol);
1951        cmd("PUNSUBSCRIBE")
1952            .arg(pchannel)
1953            .set_no_response(true)
1954            .exec(self)
1955    }
1956}
1957
1958impl ConnectionLike for Connection {
1959    /// Sends a [Cmd] into the TCP socket and reads a single response from it.
1960    fn req_command(&mut self, cmd: &Cmd) -> RedisResult<Value> {
1961        let pcmd = cmd.get_packed_command();
1962        if self.pubsub {
1963            self.exit_pubsub()?;
1964        }
1965
1966        self.send_bytes(&pcmd)?;
1967        if cmd.is_no_response() {
1968            return Ok(Value::Nil);
1969        }
1970        loop {
1971            match self.read(true)? {
1972                Value::Push {
1973                    kind: _kind,
1974                    data: _data,
1975                } => continue,
1976                val => return Ok(val),
1977            }
1978        }
1979    }
1980    fn req_packed_command(&mut self, cmd: &[u8]) -> RedisResult<Value> {
1981        if self.pubsub {
1982            self.exit_pubsub()?;
1983        }
1984
1985        self.send_bytes(cmd)?;
1986        loop {
1987            match self.read(true)? {
1988                Value::Push {
1989                    kind: _kind,
1990                    data: _data,
1991                } => continue,
1992                val => return Ok(val),
1993            }
1994        }
1995    }
1996
1997    fn req_packed_commands(
1998        &mut self,
1999        cmd: &[u8],
2000        offset: usize,
2001        count: usize,
2002    ) -> RedisResult<Vec<Value>> {
2003        if self.pubsub {
2004            self.exit_pubsub()?;
2005        }
2006        self.send_bytes(cmd)?;
2007        let mut rv = vec![];
2008        let mut first_err = None;
2009        let mut server_errors = vec![];
2010        let mut count = count;
2011        let mut idx = 0;
2012        while idx < (offset + count) {
2013            // When processing a transaction, some responses may be errors.
2014            // We need to keep processing the rest of the responses in that case,
2015            // so bailing early with `?` would not be correct.
2016            // See: https://github.com/redis-rs/redis-rs/issues/436
2017            let response = self.read(true);
2018            match response {
2019                Ok(Value::ServerError(err)) => {
2020                    if idx < offset {
2021                        server_errors.push((idx - 1, err)); // -1, to offset the added MULTI call.
2022                    } else {
2023                        rv.push(Value::ServerError(err));
2024                    }
2025                }
2026                Ok(item) => {
2027                    // RESP3 can insert push data between command replies
2028                    if let Value::Push {
2029                        kind: _kind,
2030                        data: _data,
2031                    } = item
2032                    {
2033                        // if that is the case we have to extend the loop and handle push data
2034                        count += 1;
2035                    } else if idx >= offset {
2036                        rv.push(item);
2037                    }
2038                }
2039                Err(err) => {
2040                    if first_err.is_none() {
2041                        first_err = Some(err);
2042                    }
2043                }
2044            }
2045            idx += 1;
2046        }
2047
2048        if !server_errors.is_empty() {
2049            return Err(RedisError::make_aborted_transaction(server_errors));
2050        }
2051
2052        first_err.map_or(Ok(rv), Err)
2053    }
2054
2055    fn get_db(&self) -> i64 {
2056        self.db
2057    }
2058
2059    fn check_connection(&mut self) -> bool {
2060        cmd("PING").query::<String>(self).is_ok()
2061    }
2062
2063    fn is_open(&self) -> bool {
2064        self.con.is_open()
2065    }
2066}
2067
2068impl<C, T> ConnectionLike for T
2069where
2070    C: ConnectionLike,
2071    T: DerefMut<Target = C>,
2072{
2073    fn req_packed_command(&mut self, cmd: &[u8]) -> RedisResult<Value> {
2074        self.deref_mut().req_packed_command(cmd)
2075    }
2076
2077    fn req_packed_commands(
2078        &mut self,
2079        cmd: &[u8],
2080        offset: usize,
2081        count: usize,
2082    ) -> RedisResult<Vec<Value>> {
2083        self.deref_mut().req_packed_commands(cmd, offset, count)
2084    }
2085
2086    fn req_command(&mut self, cmd: &Cmd) -> RedisResult<Value> {
2087        self.deref_mut().req_command(cmd)
2088    }
2089
2090    fn get_db(&self) -> i64 {
2091        self.deref().get_db()
2092    }
2093
2094    fn supports_pipelining(&self) -> bool {
2095        self.deref().supports_pipelining()
2096    }
2097
2098    fn check_connection(&mut self) -> bool {
2099        self.deref_mut().check_connection()
2100    }
2101
2102    fn is_open(&self) -> bool {
2103        self.deref().is_open()
2104    }
2105}
2106
2107/// The pubsub object provides convenient access to the redis pubsub
2108/// system.  Once created you can subscribe and unsubscribe from channels
2109/// and listen in on messages.
2110///
2111/// Example:
2112///
2113/// ```rust,no_run
2114/// # fn do_something() -> redis::RedisResult<()> {
2115/// let client = redis::Client::open("redis://127.0.0.1/")?;
2116/// let mut con = client.get_connection()?;
2117/// let mut pubsub = con.as_pubsub();
2118/// pubsub.subscribe("channel_1")?;
2119/// pubsub.subscribe("channel_2")?;
2120///
2121/// loop {
2122///     let msg = pubsub.get_message()?;
2123///     let payload : String = msg.get_payload()?;
2124///     println!("channel '{}': {}", msg.get_channel_name(), payload);
2125/// }
2126/// # }
2127/// ```
2128impl<'a> PubSub<'a> {
2129    fn new(con: &'a mut Connection) -> Self {
2130        Self {
2131            con,
2132            waiting_messages: VecDeque::new(),
2133        }
2134    }
2135
2136    fn cache_messages_until_received_response(
2137        &mut self,
2138        cmd: &mut Cmd,
2139        is_sub_unsub: bool,
2140    ) -> RedisResult<Value> {
2141        let ignore_response = self.con.protocol.supports_resp3() && is_sub_unsub;
2142        cmd.set_no_response(ignore_response);
2143
2144        self.con.send_packed_command(&cmd.get_packed_command())?;
2145
2146        loop {
2147            let response = self.con.recv_response()?;
2148            if let Some(msg) = Msg::from_value(&response) {
2149                self.waiting_messages.push_back(msg);
2150            } else {
2151                return Ok(response);
2152            }
2153        }
2154    }
2155
2156    /// Subscribes to a new channel(s).
2157    pub fn subscribe<T: ToRedisArgs>(&mut self, channel: T) -> RedisResult<()> {
2158        self.cache_messages_until_received_response(cmd("SUBSCRIBE").arg(channel), true)?;
2159        Ok(())
2160    }
2161
2162    /// Subscribes to new channel(s) with pattern(s).
2163    pub fn psubscribe<T: ToRedisArgs>(&mut self, pchannel: T) -> RedisResult<()> {
2164        self.cache_messages_until_received_response(cmd("PSUBSCRIBE").arg(pchannel), true)?;
2165        Ok(())
2166    }
2167
2168    /// Unsubscribes from a channel(s).
2169    pub fn unsubscribe<T: ToRedisArgs>(&mut self, channel: T) -> RedisResult<()> {
2170        self.cache_messages_until_received_response(cmd("UNSUBSCRIBE").arg(channel), true)?;
2171        Ok(())
2172    }
2173
2174    /// Unsubscribes from channel pattern(s).
2175    pub fn punsubscribe<T: ToRedisArgs>(&mut self, pchannel: T) -> RedisResult<()> {
2176        self.cache_messages_until_received_response(cmd("PUNSUBSCRIBE").arg(pchannel), true)?;
2177        Ok(())
2178    }
2179
2180    /// Sends a ping with a message to the server
2181    pub fn ping_message<T: FromRedisValue>(&mut self, message: impl ToRedisArgs) -> RedisResult<T> {
2182        Ok(from_redis_value(
2183            self.cache_messages_until_received_response(cmd("PING").arg(message), false)?,
2184        )?)
2185    }
2186    /// Sends a ping to the server
2187    pub fn ping<T: FromRedisValue>(&mut self) -> RedisResult<T> {
2188        Ok(from_redis_value(
2189            self.cache_messages_until_received_response(&mut cmd("PING"), false)?,
2190        )?)
2191    }
2192
2193    /// Fetches the next message from the pubsub connection.  Blocks until
2194    /// a message becomes available.  This currently does not provide a
2195    /// wait not to block :(
2196    ///
2197    /// The message itself is still generic and can be converted into an
2198    /// appropriate type through the helper methods on it.
2199    pub fn get_message(&mut self) -> RedisResult<Msg> {
2200        if let Some(msg) = self.waiting_messages.pop_front() {
2201            return Ok(msg);
2202        }
2203        loop {
2204            if let Some(msg) = Msg::from_owned_value(self.con.read(false)?) {
2205                return Ok(msg);
2206            } else {
2207                continue;
2208            }
2209        }
2210    }
2211
2212    /// Sets the read timeout for the connection.
2213    ///
2214    /// If the provided value is `None`, then `get_message` call will
2215    /// block indefinitely. It is an error to pass the zero `Duration` to this
2216    /// method.
2217    pub fn set_read_timeout(&self, dur: Option<Duration>) -> RedisResult<()> {
2218        self.con.set_read_timeout(dur)
2219    }
2220}
2221
2222impl Drop for PubSub<'_> {
2223    fn drop(&mut self) {
2224        let _ = self.con.exit_pubsub();
2225    }
2226}
2227
2228/// This holds the data that comes from listening to a pubsub
2229/// connection.  It only contains actual message data.
2230impl Msg {
2231    /// Tries to convert provided [`Value`] into [`Msg`].
2232    pub fn from_value(value: &Value) -> Option<Self> {
2233        Self::from_owned_value(value.clone())
2234    }
2235
2236    /// Tries to convert provided [`Value`] into [`Msg`].
2237    pub fn from_owned_value(value: Value) -> Option<Self> {
2238        let mut pattern = None;
2239        let payload;
2240        let channel;
2241
2242        if let Value::Push { kind, data } = value {
2243            return Self::from_push_info(PushInfo { kind, data });
2244        } else {
2245            let raw_msg: Vec<Value> = from_redis_value(value).ok()?;
2246            let mut iter = raw_msg.into_iter();
2247            let msg_type: String = from_redis_value(iter.next()?).ok()?;
2248            if msg_type == "message" {
2249                channel = iter.next()?;
2250                payload = iter.next()?;
2251            } else if msg_type == "pmessage" {
2252                pattern = Some(iter.next()?);
2253                channel = iter.next()?;
2254                payload = iter.next()?;
2255            } else {
2256                return None;
2257            }
2258        }
2259        Some(Self {
2260            payload,
2261            channel,
2262            pattern,
2263        })
2264    }
2265
2266    /// Tries to convert provided [`PushInfo`] into [`Msg`].
2267    pub fn from_push_info(push_info: PushInfo) -> Option<Self> {
2268        let mut pattern = None;
2269        let payload;
2270        let channel;
2271
2272        let mut iter = push_info.data.into_iter();
2273        if push_info.kind == PushKind::Message || push_info.kind == PushKind::SMessage {
2274            channel = iter.next()?;
2275            payload = iter.next()?;
2276        } else if push_info.kind == PushKind::PMessage {
2277            pattern = Some(iter.next()?);
2278            channel = iter.next()?;
2279            payload = iter.next()?;
2280        } else {
2281            return None;
2282        }
2283
2284        Some(Self {
2285            payload,
2286            channel,
2287            pattern,
2288        })
2289    }
2290
2291    /// Returns the channel this message came on.
2292    pub fn get_channel<T: FromRedisValue>(&self) -> RedisResult<T> {
2293        Ok(from_redis_value_ref(&self.channel)?)
2294    }
2295
2296    /// Convenience method to get a string version of the channel.  Unless
2297    /// your channel contains non utf-8 bytes you can always use this
2298    /// method.  If the channel is not a valid string (which really should
2299    /// not happen) then the return value is `"?"`.
2300    pub fn get_channel_name(&self) -> &str {
2301        match self.channel {
2302            Value::BulkString(ref bytes) => from_utf8(bytes).unwrap_or("?"),
2303            _ => "?",
2304        }
2305    }
2306
2307    /// Returns the message's payload in a specific format.
2308    pub fn get_payload<T: FromRedisValue>(&self) -> RedisResult<T> {
2309        Ok(from_redis_value_ref(&self.payload)?)
2310    }
2311
2312    /// Returns the bytes that are the message's payload.  This can be used
2313    /// as an alternative to the `get_payload` function if you are interested
2314    /// in the raw bytes in it.
2315    pub fn get_payload_bytes(&self) -> &[u8] {
2316        match self.payload {
2317            Value::BulkString(ref bytes) => bytes,
2318            _ => b"",
2319        }
2320    }
2321
2322    /// Returns true if the message was constructed from a pattern
2323    /// subscription.
2324    #[allow(clippy::wrong_self_convention)]
2325    pub fn from_pattern(&self) -> bool {
2326        self.pattern.is_some()
2327    }
2328
2329    /// If the message was constructed from a message pattern this can be
2330    /// used to find out which one.  It's recommended to match against
2331    /// an `Option<String>` so that you do not need to use `from_pattern`
2332    /// to figure out if a pattern was set.
2333    pub fn get_pattern<T: FromRedisValue>(&self) -> RedisResult<T> {
2334        Ok(match self.pattern {
2335            None => from_redis_value_ref(&Value::Nil),
2336            Some(ref x) => from_redis_value_ref(x),
2337        }?)
2338    }
2339}
2340
2341/// This function simplifies transaction management slightly.  What it
2342/// does is automatically watching keys and then going into a transaction
2343/// loop util it succeeds.  Once it goes through the results are
2344/// returned.
2345///
2346/// To use the transaction two pieces of information are needed: a list
2347/// of all the keys that need to be watched for modifications and a
2348/// closure with the code that should be execute in the context of the
2349/// transaction.  The closure is invoked with a fresh pipeline in atomic
2350/// mode.  To use the transaction the function needs to return the result
2351/// from querying the pipeline with the connection.
2352///
2353/// The end result of the transaction is then available as the return
2354/// value from the function call.
2355///
2356/// Example:
2357///
2358/// ```rust,no_run
2359/// use redis::Commands;
2360/// # fn do_something() -> redis::RedisResult<()> {
2361/// # let client = redis::Client::open("redis://127.0.0.1/").unwrap();
2362/// # let mut con = client.get_connection().unwrap();
2363/// let key = "the_key";
2364/// let (new_val,) : (isize,) = redis::transaction(&mut con, &[key], |con, pipe| {
2365///     let old_val : isize = con.get(key)?;
2366///     pipe
2367///         .set(key, old_val + 1).ignore()
2368///         .get(key).query(con)
2369/// })?;
2370/// println!("The incremented number is: {}", new_val);
2371/// # Ok(()) }
2372/// ```
2373pub fn transaction<
2374    C: ConnectionLike,
2375    K: ToRedisArgs,
2376    T,
2377    F: FnMut(&mut C, &mut Pipeline) -> RedisResult<Option<T>>,
2378>(
2379    con: &mut C,
2380    keys: &[K],
2381    func: F,
2382) -> RedisResult<T> {
2383    let mut func = func;
2384    loop {
2385        cmd("WATCH").arg(keys).exec(con)?;
2386        let mut p = pipe();
2387        let response: Option<T> = func(con, p.atomic())?;
2388        match response {
2389            None => {
2390                continue;
2391            }
2392            Some(response) => {
2393                // make sure no watch is left in the connection, even if
2394                // someone forgot to use the pipeline.
2395                cmd("UNWATCH").exec(con)?;
2396                return Ok(response);
2397            }
2398        }
2399    }
2400}
2401//TODO: for both clearing logic support sharded channels.
2402
2403/// Common logic for clearing subscriptions in RESP2 async/sync
2404pub fn resp2_is_pub_sub_state_cleared(
2405    received_unsub: &mut bool,
2406    received_punsub: &mut bool,
2407    kind: &[u8],
2408    num: isize,
2409) -> bool {
2410    match kind.first() {
2411        Some(&b'u') => *received_unsub = true,
2412        Some(&b'p') => *received_punsub = true,
2413        _ => (),
2414    }
2415    *received_unsub && *received_punsub && num == 0
2416}
2417
2418/// Common logic for clearing subscriptions in RESP3 async/sync
2419pub fn resp3_is_pub_sub_state_cleared(
2420    received_unsub: &mut bool,
2421    received_punsub: &mut bool,
2422    kind: &PushKind,
2423    num: isize,
2424) -> bool {
2425    match kind {
2426        PushKind::Unsubscribe => *received_unsub = true,
2427        PushKind::PUnsubscribe => *received_punsub = true,
2428        _ => (),
2429    }
2430    *received_unsub && *received_punsub && num == 0
2431}
2432
2433pub fn no_sub_err_is_pub_sub_state_cleared(
2434    received_unsub: &mut bool,
2435    received_punsub: &mut bool,
2436    err: &ServerError,
2437) -> bool {
2438    let details = err.details();
2439    *received_unsub = *received_unsub
2440        || details
2441            .map(|details| details.starts_with("'unsub"))
2442            .unwrap_or_default();
2443    *received_punsub = *received_punsub
2444        || details
2445            .map(|details| details.starts_with("'punsub"))
2446            .unwrap_or_default();
2447    *received_unsub && *received_punsub
2448}
2449
2450/// Common logic for checking real cause of hello3 command error
2451pub fn get_resp3_hello_command_error(err: RedisError) -> RedisError {
2452    if let Some(detail) = err.detail()
2453        && detail.starts_with("unknown command `HELLO`")
2454    {
2455        return (
2456            ErrorKind::RESP3NotSupported,
2457            "Redis Server doesn't support HELLO command therefore resp3 cannot be used",
2458        )
2459            .into();
2460    }
2461    err
2462}
2463
2464#[cfg(test)]
2465mod tests {
2466    mod util {
2467        use crate::connection::connection_setup_pipeline;
2468        use crate::{RedisConnectionInfo, cmd};
2469
2470        /// Assures that the given [`RedisConnectionInfo`] sets the given expected library name and version
2471        pub fn assert_lib_name_in_connection_setup_pipeline(
2472            redis_connection_info: &RedisConnectionInfo,
2473            expected_lib_name: &str,
2474            expected_lib_ver: &str,
2475        ) {
2476            // Build the pipeline
2477            let pipeline = connection_setup_pipeline(
2478                redis_connection_info,
2479                false,
2480                #[cfg(feature = "cache-aio")]
2481                None,
2482            )
2483            .0;
2484
2485            let actual_packed_cmds = pipeline
2486                .commands
2487                .iter()
2488                .map(|c| c.get_packed_command())
2489                .collect::<Vec<_>>();
2490
2491            let expected_lib_name_packed_cmd = cmd("CLIENT")
2492                .arg("SETINFO")
2493                .arg("LIB-NAME")
2494                .arg(expected_lib_name)
2495                .get_packed_command();
2496            assert!(actual_packed_cmds.contains(&expected_lib_name_packed_cmd));
2497
2498            let expected_lib_ver_packed_cmd = cmd("CLIENT")
2499                .arg("SETINFO")
2500                .arg("LIB-VER")
2501                .arg(expected_lib_ver)
2502                .get_packed_command();
2503            assert!(actual_packed_cmds.contains(&expected_lib_ver_packed_cmd));
2504        }
2505    }
2506
2507    use super::*;
2508    use util::assert_lib_name_in_connection_setup_pipeline;
2509
2510    #[test]
2511    fn test_parse_redis_url() {
2512        let cases = vec![
2513            ("redis://127.0.0.1", true),
2514            ("redis://[::1]", true),
2515            ("rediss://127.0.0.1", true),
2516            ("rediss://[::1]", true),
2517            ("valkey://127.0.0.1", true),
2518            ("valkey://[::1]", true),
2519            ("valkeys://127.0.0.1", true),
2520            ("valkeys://[::1]", true),
2521            ("redis+unix:///run/redis.sock", true),
2522            ("valkey+unix:///run/valkey.sock", true),
2523            ("unix:///run/redis.sock", true),
2524            ("http://127.0.0.1", false),
2525            ("tcp://127.0.0.1", false),
2526        ];
2527        for (url, expected) in cases.into_iter() {
2528            let res = parse_redis_url(url);
2529            assert_eq!(
2530                res.is_some(),
2531                expected,
2532                "Parsed result of `{url}` is not expected",
2533            );
2534        }
2535    }
2536
2537    #[test]
2538    fn test_url_to_tcp_connection_info() {
2539        let cases = vec![
2540            (
2541                url::Url::parse("redis://127.0.0.1").unwrap(),
2542                ConnectionInfo {
2543                    addr: ConnectionAddr::Tcp("127.0.0.1".to_string(), 6379),
2544                    redis: Default::default(),
2545                    tcp_settings: TcpSettings::default(),
2546                },
2547            ),
2548            (
2549                url::Url::parse("redis://[::1]").unwrap(),
2550                ConnectionInfo {
2551                    addr: ConnectionAddr::Tcp("::1".to_string(), 6379),
2552                    redis: Default::default(),
2553                    tcp_settings: TcpSettings::default(),
2554                },
2555            ),
2556            (
2557                url::Url::parse("redis://%25johndoe%25:%23%40%3C%3E%24@example.com/2").unwrap(),
2558                ConnectionInfo {
2559                    addr: ConnectionAddr::Tcp("example.com".to_string(), 6379),
2560                    redis: RedisConnectionInfo {
2561                        db: 2,
2562                        username: Some("%johndoe%".into()),
2563                        password: Some("#@<>$".into()),
2564                        protocol: ProtocolVersion::RESP2,
2565                        skip_set_lib_name: false,
2566                        lib_name: None,
2567                        lib_ver: None,
2568                    },
2569                    tcp_settings: TcpSettings::default(),
2570                },
2571            ),
2572            (
2573                url::Url::parse("redis://127.0.0.1/?protocol=2").unwrap(),
2574                ConnectionInfo {
2575                    addr: ConnectionAddr::Tcp("127.0.0.1".to_string(), 6379),
2576                    redis: Default::default(),
2577                    tcp_settings: TcpSettings::default(),
2578                },
2579            ),
2580            (
2581                url::Url::parse("redis://127.0.0.1/?protocol=resp3").unwrap(),
2582                ConnectionInfo {
2583                    addr: ConnectionAddr::Tcp("127.0.0.1".to_string(), 6379),
2584                    redis: RedisConnectionInfo {
2585                        db: 0,
2586                        username: None,
2587                        password: None,
2588                        protocol: ProtocolVersion::RESP3,
2589                        skip_set_lib_name: false,
2590                        lib_name: None,
2591                        lib_ver: None,
2592                    },
2593                    tcp_settings: TcpSettings::default(),
2594                },
2595            ),
2596        ];
2597        for (url, expected) in cases.into_iter() {
2598            let res = url_to_tcp_connection_info(url.clone()).unwrap();
2599            assert_eq!(res.addr, expected.addr, "addr of {url} is not expected");
2600            assert_eq!(
2601                res.redis.db, expected.redis.db,
2602                "db of {url} is not expected",
2603            );
2604            assert_eq!(
2605                res.redis.username, expected.redis.username,
2606                "username of {url} is not expected",
2607            );
2608            assert_eq!(
2609                res.redis.password, expected.redis.password,
2610                "password of {url} is not expected",
2611            );
2612        }
2613    }
2614
2615    #[test]
2616    fn test_url_to_tcp_connection_info_failed() {
2617        let cases = vec![
2618            (
2619                url::Url::parse("redis://").unwrap(),
2620                "Missing hostname",
2621                None,
2622            ),
2623            (
2624                url::Url::parse("redis://127.0.0.1/db").unwrap(),
2625                "Invalid database number",
2626                None,
2627            ),
2628            (
2629                url::Url::parse("redis://C3%B0@127.0.0.1").unwrap(),
2630                "Username is not valid UTF-8 string",
2631                None,
2632            ),
2633            (
2634                url::Url::parse("redis://:C3%B0@127.0.0.1").unwrap(),
2635                "Password is not valid UTF-8 string",
2636                None,
2637            ),
2638            (
2639                url::Url::parse("redis://127.0.0.1/?protocol=4").unwrap(),
2640                "Invalid protocol version",
2641                Some("4"),
2642            ),
2643        ];
2644        for (url, expected, detail) in cases.into_iter() {
2645            let res = url_to_tcp_connection_info(url).unwrap_err();
2646            assert_eq!(res.kind(), crate::ErrorKind::InvalidClientConfig,);
2647            let desc = res.to_string();
2648            assert!(desc.contains(expected), "{desc}");
2649            assert_eq!(res.detail(), detail);
2650        }
2651    }
2652
2653    #[test]
2654    #[cfg(unix)]
2655    fn test_url_to_unix_connection_info() {
2656        let cases = vec![
2657            (
2658                url::Url::parse("unix:///var/run/redis.sock").unwrap(),
2659                ConnectionInfo {
2660                    addr: ConnectionAddr::Unix("/var/run/redis.sock".into()),
2661                    redis: RedisConnectionInfo {
2662                        db: 0,
2663                        username: None,
2664                        password: None,
2665                        protocol: ProtocolVersion::RESP2,
2666                        skip_set_lib_name: false,
2667                        lib_name: None,
2668                        lib_ver: None,
2669                    },
2670                    tcp_settings: Default::default(),
2671                },
2672            ),
2673            (
2674                url::Url::parse("redis+unix:///var/run/redis.sock?db=1").unwrap(),
2675                ConnectionInfo {
2676                    addr: ConnectionAddr::Unix("/var/run/redis.sock".into()),
2677                    redis: RedisConnectionInfo {
2678                        db: 1,
2679                        username: None,
2680                        password: None,
2681                        protocol: ProtocolVersion::RESP2,
2682                        skip_set_lib_name: false,
2683                        lib_name: None,
2684                        lib_ver: None,
2685                    },
2686                    tcp_settings: TcpSettings::default(),
2687                },
2688            ),
2689            (
2690                url::Url::parse(
2691                    "unix:///example.sock?user=%25johndoe%25&pass=%23%40%3C%3E%24&db=2",
2692                )
2693                .unwrap(),
2694                ConnectionInfo {
2695                    addr: ConnectionAddr::Unix("/example.sock".into()),
2696                    redis: RedisConnectionInfo {
2697                        db: 2,
2698                        username: Some("%johndoe%".into()),
2699                        password: Some("#@<>$".into()),
2700                        protocol: ProtocolVersion::RESP2,
2701                        skip_set_lib_name: false,
2702                        lib_name: None,
2703                        lib_ver: None,
2704                    },
2705                    tcp_settings: TcpSettings::default(),
2706                },
2707            ),
2708            (
2709                url::Url::parse(
2710                    "redis+unix:///example.sock?pass=%26%3F%3D+%2A%2B&db=2&user=%25johndoe%25",
2711                )
2712                .unwrap(),
2713                ConnectionInfo {
2714                    addr: ConnectionAddr::Unix("/example.sock".into()),
2715                    redis: RedisConnectionInfo {
2716                        db: 2,
2717                        username: Some("%johndoe%".into()),
2718                        password: Some("&?= *+".into()),
2719                        protocol: ProtocolVersion::RESP2,
2720                        skip_set_lib_name: false,
2721                        lib_name: None,
2722                        lib_ver: None,
2723                    },
2724                    tcp_settings: TcpSettings::default(),
2725                },
2726            ),
2727            (
2728                url::Url::parse("redis+unix:///var/run/redis.sock?protocol=3").unwrap(),
2729                ConnectionInfo {
2730                    addr: ConnectionAddr::Unix("/var/run/redis.sock".into()),
2731                    redis: RedisConnectionInfo {
2732                        db: 0,
2733                        username: None,
2734                        password: None,
2735                        protocol: ProtocolVersion::RESP3,
2736                        skip_set_lib_name: false,
2737                        lib_name: None,
2738                        lib_ver: None,
2739                    },
2740                    tcp_settings: TcpSettings::default(),
2741                },
2742            ),
2743        ];
2744        for (url, expected) in cases.into_iter() {
2745            assert_eq!(
2746                ConnectionAddr::Unix(url.to_file_path().unwrap()),
2747                expected.addr,
2748                "addr of {url} is not expected",
2749            );
2750            let res = url_to_unix_connection_info(url.clone()).unwrap();
2751            assert_eq!(res.addr, expected.addr, "addr of {url} is not expected");
2752            assert_eq!(
2753                res.redis.db, expected.redis.db,
2754                "db of {url} is not expected",
2755            );
2756            assert_eq!(
2757                res.redis.username, expected.redis.username,
2758                "username of {url} is not expected",
2759            );
2760            assert_eq!(
2761                res.redis.password, expected.redis.password,
2762                "password of {url} is not expected",
2763            );
2764        }
2765    }
2766
2767    #[test]
2768    fn redis_connection_info_lib_name_default() {
2769        let redis_connection_info = RedisConnectionInfo::default();
2770
2771        // Check the accessors
2772        assert_eq!(redis_connection_info.lib_name(), None);
2773        assert_eq!(redis_connection_info.lib_ver(), None);
2774
2775        // Check the connection setup pipeline
2776        assert_lib_name_in_connection_setup_pipeline(
2777            &redis_connection_info,
2778            DEFAULT_CLIENT_SETINFO_LIB_NAME,
2779            DEFAULT_CLIENT_SETINFO_LIB_VER,
2780        );
2781    }
2782
2783    #[test]
2784    fn redis_connection_info_lib_name_custom() {
2785        let mut redis_connection_info = RedisConnectionInfo::default();
2786
2787        // Mark to skip setting the lib_name. This allows to test that `set_lib_name` clears it again;
2788        redis_connection_info = redis_connection_info.set_skip_set_lib_name();
2789        assert!(redis_connection_info.skip_set_lib_name());
2790
2791        // Set the lib_name
2792        redis_connection_info = redis_connection_info.set_lib_name("foo", "42.4711");
2793
2794        // Check the accessors
2795        assert!(!redis_connection_info.skip_set_lib_name());
2796        assert_eq!(redis_connection_info.lib_name(), Some("foo"));
2797        assert_eq!(redis_connection_info.lib_ver(), Some("42.4711"));
2798
2799        // Check the connection setup pipeline
2800        assert_lib_name_in_connection_setup_pipeline(&redis_connection_info, "foo", "42.4711");
2801    }
2802}