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#[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
60const DEFAULT_CLIENT_SETINFO_LIB_NAME: &str = "redis-rs";
62const 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
81pub 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#[derive(Clone, Copy, PartialEq)]
101#[non_exhaustive]
102pub enum TlsMode {
103 Secure,
105 Insecure,
107}
108
109#[derive(Clone, Debug)]
115#[non_exhaustive]
116pub enum ConnectionAddr {
117 Tcp(String, u16),
119 TcpTls {
121 host: String,
123 port: u16,
125 insecure: bool,
134
135 tls_params: Option<TlsConnParams>,
137 },
138 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 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 #[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 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#[derive(Clone, Debug)]
243pub struct ConnectionInfo {
244 pub(crate) addr: ConnectionAddr,
246
247 pub(crate) tcp_settings: TcpSettings,
249 pub(crate) redis: RedisConnectionInfo,
251}
252
253impl ConnectionInfo {
254 pub fn addr(&self) -> &ConnectionAddr {
256 &self.addr
257 }
258
259 pub fn tcp_settings(&self) -> &TcpSettings {
261 &self.tcp_settings
262 }
263
264 pub fn redis_settings(&self) -> &RedisConnectionInfo {
266 &self.redis
267 }
268
269 pub fn set_addr(mut self, addr: ConnectionAddr) -> Self {
271 self.addr = addr;
272 self
273 }
274
275 pub fn set_tcp_settings(mut self, tcp_settings: TcpSettings) -> Self {
277 self.tcp_settings = tcp_settings;
278 self
279 }
280
281 pub fn set_redis_settings(mut self, redis: RedisConnectionInfo) -> Self {
283 self.redis = redis;
284 self
285 }
286}
287
288#[derive(Clone, Default)]
290pub struct RedisConnectionInfo {
291 pub(crate) db: i64,
293 pub(crate) username: Option<ArcStr>,
295 pub(crate) password: Option<ArcStr>,
297 pub(crate) protocol: ProtocolVersion,
299 pub(crate) skip_set_lib_name: bool,
301 pub(crate) lib_name: Option<ArcStr>,
303 pub(crate) lib_ver: Option<ArcStr>,
305}
306
307impl RedisConnectionInfo {
308 pub fn username(&self) -> Option<&str> {
310 self.username.as_deref()
311 }
312
313 pub fn password(&self) -> Option<&str> {
315 self.password.as_deref()
316 }
317
318 pub fn protocol(&self) -> ProtocolVersion {
320 self.protocol
321 }
322
323 pub fn skip_set_lib_name(&self) -> bool {
325 self.skip_set_lib_name
326 }
327
328 pub fn lib_name(&self) -> Option<&str> {
330 self.lib_name.as_deref()
331 }
332
333 pub fn lib_ver(&self) -> Option<&str> {
335 self.lib_ver.as_deref()
336 }
337
338 pub fn db(&self) -> i64 {
340 self.db
341 }
342
343 pub fn set_username(mut self, username: impl AsRef<str>) -> Self {
345 self.username = Some(username.as_ref().into());
346 self
347 }
348
349 pub fn set_password(mut self, password: impl AsRef<str>) -> Self {
351 self.password = Some(password.as_ref().into());
352 self
353 }
354
355 pub fn set_protocol(mut self, protocol: ProtocolVersion) -> Self {
357 self.protocol = protocol;
358 self
359 }
360
361 pub fn set_skip_set_lib_name(mut self) -> Self {
366 self.skip_set_lib_name = true;
367 self
368 }
369
370 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 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
420pub trait IntoConnectionInfo {
424 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
444impl 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
475impl 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 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#[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
825pub struct Connection {
827 con: ActualConnection,
828 parser: Parser,
829 db: i64,
830
831 pubsub: bool,
836
837 protocol: ProtocolVersion,
839
840 push_sender: Option<SyncPushSender>,
842
843 messages_to_skip: usize,
846}
847
848pub struct PubSub<'a> {
852 con: &'a mut Connection,
853 waiting_messages: VecDeque<Msg>,
854}
855
856#[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 #[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 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 let remaining_timeout = timeout.and_then(|timeout| timeout.checked_sub(start.elapsed()));
1307 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 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 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 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
1587pub trait ConnectionLike {
1599 fn req_packed_command(&mut self, cmd: &[u8]) -> RedisResult<Value>;
1602
1603 #[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 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 fn get_db(&self) -> i64;
1629
1630 #[doc(hidden)]
1632 fn supports_pipelining(&self) -> bool {
1633 true
1634 }
1635
1636 fn check_connection(&mut self) -> bool;
1638
1639 fn is_open(&self) -> bool;
1647}
1648
1649impl Connection {
1657 pub fn send_packed_command(&mut self, cmd: &[u8]) -> RedisResult<()> {
1662 self.send_bytes(cmd)?;
1663 Ok(())
1664 }
1665
1666 pub fn recv_response(&mut self) -> RedisResult<Value> {
1669 self.read(true)
1670 }
1671
1672 pub fn set_write_timeout(&self, dur: Option<Duration>) -> RedisResult<()> {
1678 self.con.set_write_timeout(dur)
1679 }
1680
1681 pub fn set_read_timeout(&self, dur: Option<Duration>) -> RedisResult<()> {
1687 self.con.set_read_timeout(dur)
1688 }
1689
1690 pub fn as_pubsub(&mut self) -> PubSub<'_> {
1692 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 self.pubsub = true;
1705 }
1706
1707 res
1708 }
1709
1710 fn clear_active_subscriptions(&mut self) -> RedisResult<()> {
1715 {
1721 let unsubscribe = cmd("UNSUBSCRIBE").get_packed_command();
1723 let punsubscribe = cmd("PUNSUBSCRIBE").get_packed_command();
1724
1725 self.send_bytes(&unsubscribe)?;
1727 self.send_bytes(&punsubscribe)?;
1728 }
1729
1730 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 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 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 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 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 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 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 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 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 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 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 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 let response = self.read(true);
2018 match response {
2019 Ok(Value::ServerError(err)) => {
2020 if idx < offset {
2021 server_errors.push((idx - 1, err)); } else {
2023 rv.push(Value::ServerError(err));
2024 }
2025 }
2026 Ok(item) => {
2027 if let Value::Push {
2029 kind: _kind,
2030 data: _data,
2031 } = item
2032 {
2033 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
2107impl<'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 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 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 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 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 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 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 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 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
2228impl Msg {
2231 pub fn from_value(value: &Value) -> Option<Self> {
2233 Self::from_owned_value(value.clone())
2234 }
2235
2236 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 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 pub fn get_channel<T: FromRedisValue>(&self) -> RedisResult<T> {
2293 Ok(from_redis_value_ref(&self.channel)?)
2294 }
2295
2296 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 pub fn get_payload<T: FromRedisValue>(&self) -> RedisResult<T> {
2309 Ok(from_redis_value_ref(&self.payload)?)
2310 }
2311
2312 pub fn get_payload_bytes(&self) -> &[u8] {
2316 match self.payload {
2317 Value::BulkString(ref bytes) => bytes,
2318 _ => b"",
2319 }
2320 }
2321
2322 #[allow(clippy::wrong_self_convention)]
2325 pub fn from_pattern(&self) -> bool {
2326 self.pattern.is_some()
2327 }
2328
2329 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
2341pub 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 cmd("UNWATCH").exec(con)?;
2396 return Ok(response);
2397 }
2398 }
2399 }
2400}
2401pub 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
2418pub 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
2450pub 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 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 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 assert_eq!(redis_connection_info.lib_name(), None);
2773 assert_eq!(redis_connection_info.lib_ver(), None);
2774
2775 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 redis_connection_info = redis_connection_info.set_skip_set_lib_name();
2789 assert!(redis_connection_info.skip_set_lib_name());
2790
2791 redis_connection_info = redis_connection_info.set_lib_name("foo", "42.4711");
2793
2794 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 assert_lib_name_in_connection_setup_pipeline(&redis_connection_info, "foo", "42.4711");
2801 }
2802}