1use std::fmt::{self, Debug, Display};
10use std::net::SocketAddr;
11use std::slice;
12use std::str::FromStr;
13
14use itertools::Either;
15use safelog::Redactable;
16use safelog::util::write_start_redacted;
17use serde::{Deserialize, Serialize};
18use thiserror::Error;
19
20use crate::HasAddrs;
21
22#[derive(Debug, Clone, Default, Eq, PartialEq, Hash)]
34pub struct TransportId(Inner);
35
36#[derive(Debug, Default, Clone, Eq, PartialEq, Hash)]
40enum Inner {
41 #[default]
43 BuiltIn,
44
45 #[cfg(feature = "pt-client")]
47 Pluggable(PtTransportName),
48}
49
50#[derive(
59 Debug,
60 Clone,
61 Default,
62 Eq,
63 PartialEq,
64 Hash,
65 serde_with::DeserializeFromStr,
66 serde_with::SerializeDisplay,
67)]
68pub struct PtTransportName(String);
69
70impl FromStr for PtTransportName {
71 type Err = TransportIdError;
72
73 fn from_str(s: &str) -> Result<Self, Self::Err> {
74 s.to_string().try_into()
75 }
76}
77
78impl TryFrom<String> for PtTransportName {
79 type Error = TransportIdError;
80
81 fn try_from(s: String) -> Result<PtTransportName, Self::Error> {
82 if is_well_formed_id(&s) {
83 Ok(PtTransportName(s))
84 } else {
85 Err(TransportIdError::BadId(s))
86 }
87 }
88}
89
90impl Display for PtTransportName {
91 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
92 Display::fmt(&self.0, f)
93 }
94}
95
96impl AsRef<str> for PtTransportName {
97 fn as_ref(&self) -> &str {
98 &self.0
99 }
100}
101
102const BUILT_IN_IDS: &[&str] = &["-", "", "bridge", "<none>"];
110
111impl FromStr for TransportId {
112 type Err = TransportIdError;
113
114 fn from_str(s: &str) -> Result<Self, Self::Err> {
115 if BUILT_IN_IDS.contains(&s) {
116 return Ok(TransportId(Inner::BuiltIn));
117 };
118
119 #[cfg(feature = "pt-client")]
120 {
121 let name: PtTransportName = s.parse()?;
122 Ok(TransportId(Inner::Pluggable(name)))
123 }
124
125 #[cfg(not(feature = "pt-client"))]
126 Err(TransportIdError::NoSupport)
127 }
128}
129
130impl Display for TransportId {
131 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
132 match &self.0 {
133 Inner::BuiltIn => write!(f, "{}", BUILT_IN_IDS[0]),
134 #[cfg(feature = "pt-client")]
135 Inner::Pluggable(name) => write!(f, "{}", name),
136 }
137 }
138}
139
140#[cfg(feature = "pt-client")]
141impl From<PtTransportName> for TransportId {
142 fn from(name: PtTransportName) -> Self {
143 TransportId(Inner::Pluggable(name))
144 }
145}
146
147fn is_well_formed_id(s: &str) -> bool {
153 let mut bytes = s.bytes();
156
157 if let Some(first) = bytes.next() {
158 (first.is_ascii_alphabetic() || first == b'_')
159 && bytes.all(|b| b.is_ascii_alphanumeric() || b == b'_')
160 && !s.eq_ignore_ascii_case("bridge")
161 } else {
162 false
163 }
164}
165
166#[derive(Clone, Debug, thiserror::Error)]
168#[non_exhaustive]
169pub enum TransportIdError {
170 #[error("Not compiled with pluggable transport support")]
173 NoSupport,
174
175 #[error("{0:?} is not a valid pluggable transport ID")]
177 BadId(String),
178}
179
180impl TransportId {
181 pub fn new_builtin() -> Self {
185 TransportId(Inner::BuiltIn)
186 }
187
188 #[cfg(feature = "pt-client")]
192 pub fn new_pluggable(pt: PtTransportName) -> Self {
193 pt.into()
194 }
195
196 pub fn is_builtin(&self) -> bool {
198 self.0 == Inner::BuiltIn
199 }
200
201 #[cfg(feature = "pt-client")]
206 pub fn as_pluggable(&self) -> Option<&PtTransportName> {
207 match &self.0 {
208 Inner::BuiltIn => None,
209 #[cfg(feature = "pt-client")]
210 Inner::Pluggable(pt) => Some(pt),
211 }
212 }
213
214 #[cfg(feature = "pt-client")]
219 pub fn into_pluggable(self) -> Option<PtTransportName> {
220 match self.0 {
221 Inner::BuiltIn => None,
222 #[cfg(feature = "pt-client")]
223 Inner::Pluggable(pt) => Some(pt),
224 }
225 }
226}
227
228const NONE_ADDR: &str = "-";
230
231#[derive(
242 Clone, Debug, PartialEq, Eq, Hash, serde_with::DeserializeFromStr, serde_with::SerializeDisplay,
243)]
244#[non_exhaustive]
245pub enum PtTargetAddr {
246 IpPort(SocketAddr),
250 HostPort(String, u16),
252 None,
254}
255
256#[derive(
267 Clone,
268 Debug,
269 PartialEq,
270 Eq,
271 Hash,
272 serde_with::DeserializeFromStr,
273 serde_with::SerializeDisplay,
274 derive_more::Display,
275)]
276pub struct BridgeAddr(BridgeAddrInner<SocketAddr, String>);
277
278#[derive(Clone, Debug, PartialEq, Eq, Hash)]
284enum BridgeAddrInner<SA, HN> {
285 IpPort(SA),
287 HostPort(HN, u16),
289}
290
291impl BridgeAddr {
299 pub fn new_addr_from_sockaddr(sa: SocketAddr) -> Self {
301 BridgeAddr(BridgeAddrInner::IpPort(sa))
302 }
303
304 pub fn as_socketaddr(&self) -> Option<&SocketAddr> {
306 match &self.0 {
307 BridgeAddrInner::IpPort(sa) => Some(sa),
308 BridgeAddrInner::HostPort(..) => None,
309 }
310 }
311
312 pub fn new_named_host_port(hostname: impl Into<String>, port: u16) -> Self {
314 BridgeAddr(BridgeAddrInner::HostPort(hostname.into(), port))
315 }
316
317 pub fn as_host_port(&self) -> Option<(&str, u16)> {
319 match &self.0 {
320 BridgeAddrInner::IpPort(..) => None,
321 BridgeAddrInner::HostPort(hn, port) => Some((hn, *port)),
322 }
323 }
324}
325
326impl From<PtTargetAddr> for Option<BridgeAddr> {
327 fn from(pt: PtTargetAddr) -> Option<BridgeAddr> {
328 match pt {
329 PtTargetAddr::IpPort(sa) => Some(BridgeAddrInner::IpPort(sa)),
330 PtTargetAddr::HostPort(hn, p) => Some(BridgeAddrInner::HostPort(hn, p)),
331 PtTargetAddr::None => None,
332 }
333 .map(BridgeAddr)
334 }
335}
336impl From<Option<BridgeAddr>> for PtTargetAddr {
337 fn from(pt: Option<BridgeAddr>) -> PtTargetAddr {
338 match pt.map(|ba| ba.0) {
339 Some(BridgeAddrInner::IpPort(sa)) => PtTargetAddr::IpPort(sa),
340 Some(BridgeAddrInner::HostPort(hn, p)) => PtTargetAddr::HostPort(hn, p),
341 None => PtTargetAddr::None,
342 }
343 }
344}
345
346#[derive(Clone, Debug, thiserror::Error)]
348#[non_exhaustive]
349pub enum BridgeAddrError {
350 #[error("Not compiled with pluggable transport support.")]
352 NoSupport,
353 #[error("Cannot parse {0:?} as an address.")]
355 BadAddress(String),
356}
357
358impl FromStr for BridgeAddr {
359 type Err = BridgeAddrError;
360
361 fn from_str(s: &str) -> Result<Self, Self::Err> {
362 Ok(BridgeAddr(if let Ok(addr) = s.parse() {
363 BridgeAddrInner::IpPort(addr)
364 } else if let Some((name, port)) = s.rsplit_once(':') {
365 let port = port
366 .parse()
367 .map_err(|_| BridgeAddrError::BadAddress(s.to_string()))?;
368
369 BridgeAddrInner::HostPort(name.to_string(), port)
370 } else {
371 return Err(BridgeAddrError::BadAddress(s.to_string()));
372 }))
373 }
374}
375
376impl FromStr for PtTargetAddr {
377 type Err = BridgeAddrError;
378
379 fn from_str(s: &str) -> Result<Self, Self::Err> {
380 Ok(if s == NONE_ADDR {
381 PtTargetAddr::None
382 } else {
383 Some(BridgeAddr::from_str(s)?).into()
384 })
385 }
386}
387
388impl PtTargetAddr {
389 fn as_bridge_ref(&self) -> Option<BridgeAddrInner<&SocketAddr, &str>> {
396 match self {
397 PtTargetAddr::IpPort(addr) => Some(BridgeAddrInner::IpPort(addr)),
398 PtTargetAddr::HostPort(host, port) => Some(BridgeAddrInner::HostPort(host, *port)),
399 PtTargetAddr::None => None,
400 }
401 }
402}
403
404impl<SA: Display, HN: Display> Display for BridgeAddrInner<SA, HN> {
405 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
406 match self {
407 BridgeAddrInner::IpPort(addr) => write!(f, "{}", addr),
408 BridgeAddrInner::HostPort(host, port) => write!(f, "{}:{}", host, port),
409 }
410 }
411}
412
413impl Display for PtTargetAddr {
416 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
417 match self.as_bridge_ref() {
418 Some(b) => write!(f, "{}", b),
419 None => write!(f, "{}", NONE_ADDR),
420 }
421 }
422}
423
424impl<SA: Debug + Redactable, HN: Debug + Display + AsRef<str>> Redactable
425 for BridgeAddrInner<SA, HN>
426{
427 fn display_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
428 match self {
429 BridgeAddrInner::IpPort(a) => a.display_redacted(f),
430 BridgeAddrInner::HostPort(host, port) => {
431 write_start_redacted(f, host.as_ref(), 2, "…")?;
432 write!(f, ":{port}")
433 }
434 }
435 }
436}
437
438impl Redactable for BridgeAddr {
439 fn display_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
440 self.0.display_redacted(f)
441 }
442}
443
444impl Redactable for PtTargetAddr {
445 fn display_redacted(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
446 match self.as_bridge_ref() {
447 Some(b) => b.display_redacted(f),
448 None => write!(f, "{}", NONE_ADDR),
449 }
450 }
451}
452
453#[derive(Clone, Debug, Default, Eq, PartialEq, Hash, Serialize, Deserialize)]
463#[serde(into = "Vec<(String, String)>", try_from = "Vec<(String, String)>")]
464pub struct PtTargetSettings {
465 settings: Vec<(String, String)>,
467}
468
469impl PtTargetSettings {
470 fn check_setting(k: &str, v: &str) -> Result<(), PtTargetInvalidSetting> {
472 if k.find(|c: char| c == '=' || c.is_whitespace()).is_some() {
477 return Err(PtTargetInvalidSetting::Key(k.to_string()));
478 }
479 if v.find(|c: char| c.is_whitespace()).is_some() {
480 return Err(PtTargetInvalidSetting::Value(v.to_string()));
481 }
482 Ok(())
483 }
484
485 fn push_setting(
487 &mut self,
488 k: impl Into<String>,
489 v: impl Into<String>,
490 ) -> Result<(), PtTargetInvalidSetting> {
491 let k = k.into();
492 let v = v.into();
493 Self::check_setting(&k, &v)?;
494 self.settings.push((k, v));
495 Ok(())
496 }
497
498 pub fn into_inner(self) -> Vec<(String, String)> {
500 self.settings
501 }
502}
503
504impl TryFrom<Vec<(String, String)>> for PtTargetSettings {
505 type Error = PtTargetInvalidSetting;
506
507 fn try_from(settings: Vec<(String, String)>) -> Result<Self, Self::Error> {
508 for (k, v) in settings.iter() {
509 Self::check_setting(k, v)?;
510 }
511 Ok(Self { settings })
512 }
513}
514
515impl From<PtTargetSettings> for Vec<(String, String)> {
516 fn from(settings: PtTargetSettings) -> Self {
517 settings.settings
518 }
519}
520
521#[derive(Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)]
524pub struct PtTarget {
525 transport: PtTransportName,
527 addr: PtTargetAddr,
529 #[serde(default)]
531 settings: PtTargetSettings,
532}
533
534#[derive(Error, Clone, Debug, Eq, PartialEq)]
536#[non_exhaustive]
537pub enum PtTargetInvalidSetting {
538 #[error("key {0:?} has invalid or unsupported syntax")]
543 Key(String),
544
545 #[error("value {0:?} has invalid or unsupported syntax")]
550 Value(String),
551}
552
553impl PtTarget {
554 pub fn new(transport: PtTransportName, addr: PtTargetAddr) -> Self {
556 PtTarget {
557 transport,
558 addr,
559 settings: Default::default(),
560 }
561 }
562
563 pub fn push_setting(
565 &mut self,
566 k: impl Into<String>,
567 v: impl Into<String>,
568 ) -> Result<(), PtTargetInvalidSetting> {
569 self.settings.push_setting(k, v)
570 }
571
572 pub fn transport(&self) -> &PtTransportName {
574 &self.transport
575 }
576
577 pub fn addr(&self) -> &PtTargetAddr {
579 &self.addr
580 }
581
582 pub fn settings(&self) -> impl Iterator<Item = (&str, &str)> {
584 self.settings.settings.iter().map(|(k, v)| (&**k, &**v))
585 }
586
587 pub fn socket_addrs(&self) -> Option<&[std::net::SocketAddr]> {
597 match self {
598 PtTarget {
599 addr: PtTargetAddr::IpPort(addr),
600 ..
601 } => Some(std::slice::from_ref(addr)),
602
603 _ => None,
604 }
605 }
606
607 pub fn into_parts(self) -> (PtTransportName, PtTargetAddr, PtTargetSettings) {
609 (self.transport, self.addr, self.settings)
610 }
611}
612
613impl Display for PtTarget {
614 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
615 write!(
616 f,
617 "{} @ {} with {:?}",
618 self.transport, self.addr, self.settings
619 )
620 }
621}
622
623#[derive(Clone, Debug, Eq, PartialEq, Hash)]
629#[non_exhaustive]
630pub enum ChannelMethod {
631 Direct(Vec<std::net::SocketAddr>),
633
634 #[cfg(feature = "pt-client")]
636 Pluggable(PtTarget),
637}
638
639impl ChannelMethod {
640 pub fn socket_addrs(&self) -> Option<&[std::net::SocketAddr]> {
650 match self {
651 ChannelMethod::Direct(addr) => Some(addr.as_ref()),
652
653 #[cfg(feature = "pt-client")]
654 ChannelMethod::Pluggable(t) => t.socket_addrs(),
655 }
656 }
657
658 pub fn target_addr(&self) -> Option<PtTargetAddr> {
662 match self {
663 ChannelMethod::Direct(addr) if !addr.is_empty() => Some(PtTargetAddr::IpPort(addr[0])),
664
665 #[cfg(feature = "pt-client")]
666 ChannelMethod::Pluggable(PtTarget { addr, .. }) => Some(addr.clone()),
667
668 _ => None,
669 }
670 }
671
672 pub fn unique_direct_addr(&self) -> Option<SocketAddr> {
682 match self {
683 Self::Direct(addrs) => match addrs.as_slice() {
684 [addr] => Some(*addr),
685 _ => None,
687 },
688 #[cfg(feature = "pt-client")]
689 Self::Pluggable(_) => None,
690 }
691 }
692
693 pub fn is_direct(&self) -> bool {
695 matches!(self, ChannelMethod::Direct(_))
696 }
697
698 pub fn transport_id(&self) -> TransportId {
700 match self {
701 ChannelMethod::Direct(_) => TransportId::default(),
702 #[cfg(feature = "pt-client")]
703 ChannelMethod::Pluggable(target) => target.transport().clone().into(),
704 }
705 }
706
707 pub fn retain_addrs<P>(&mut self, pred: P) -> Result<(), RetainAddrsError>
715 where
716 P: Fn(&std::net::SocketAddr) -> bool,
717 {
718 #[cfg(feature = "pt-client")]
719 use PtTargetAddr as Pt;
720
721 match self {
722 ChannelMethod::Direct(d) if d.is_empty() => {}
723 ChannelMethod::Direct(d) => {
724 d.retain(pred);
725 if d.is_empty() {
726 return Err(RetainAddrsError::NoAddrsLeft);
727 }
728 }
729 #[cfg(feature = "pt-client")]
730 ChannelMethod::Pluggable(PtTarget { addr, .. }) => match addr {
731 Pt::IpPort(a) => {
732 if !pred(a) {
733 *addr = Pt::None;
734 return Err(RetainAddrsError::NoAddrsLeft);
735 }
736 }
737 Pt::HostPort(_, _) => {}
738 Pt::None => {}
739 },
740 }
741 Ok(())
742 }
743
744 pub fn contained_by(&self, other: &ChannelMethod) -> bool {
747 use ChannelMethod as CM;
748 match (self, other) {
749 (CM::Direct(our_addrs), CM::Direct(their_addrs)) => {
750 our_addrs.iter().all(|a| their_addrs.contains(a))
751 }
752 #[cfg(feature = "pt-client")]
753 (CM::Pluggable(our_target), CM::Pluggable(their_target)) => our_target == their_target,
754 #[cfg(feature = "pt-client")]
755 (_, _) => false,
756 }
757 }
758}
759
760#[derive(Clone, Debug, thiserror::Error)]
762pub enum RetainAddrsError {
763 #[error("All addresses were removed.")]
765 NoAddrsLeft,
766}
767
768impl HasAddrs for PtTargetAddr {
769 fn addrs(&self) -> impl Iterator<Item = SocketAddr> {
770 match self {
771 PtTargetAddr::IpPort(sockaddr) => slice::from_ref(sockaddr),
772 PtTargetAddr::HostPort(..) | PtTargetAddr::None => &[],
773 }
774 .iter()
775 .copied()
776 }
777}
778
779impl HasAddrs for ChannelMethod {
780 fn addrs(&self) -> impl Iterator<Item = SocketAddr> {
781 let r = match self {
782 ChannelMethod::Direct(addrs) => Either::Left(addrs.iter().copied()),
783 #[cfg(feature = "pt-client")]
784 ChannelMethod::Pluggable(pt) => Either::Right(pt.addr.addrs()),
785 };
786
787 #[cfg(not(feature = "pt-client"))]
791 let _: &Either<_, std::iter::Empty<_>> = &r;
792
793 r
794 }
795}
796
797#[cfg(test)]
798mod test {
799 #![allow(clippy::bool_assert_comparison)]
801 #![allow(clippy::clone_on_copy)]
802 #![allow(clippy::dbg_macro)]
803 #![allow(clippy::mixed_attributes_style)]
804 #![allow(clippy::print_stderr)]
805 #![allow(clippy::print_stdout)]
806 #![allow(clippy::single_char_pattern)]
807 #![allow(clippy::unwrap_used)]
808 #![allow(clippy::unchecked_time_subtraction)]
809 #![allow(clippy::useless_vec)]
810 #![allow(clippy::needless_pass_by_value)]
811 #![allow(clippy::string_slice)] use super::*;
814 use itertools::Itertools;
815
816 #[test]
817 fn builtin() {
818 assert!(TransportId::default().is_builtin());
819 assert_eq!(
820 TransportId::default(),
821 "<none>".parse().expect("Couldn't parse default ID")
822 );
823 }
824
825 #[test]
826 #[cfg(not(feature = "pt-client"))]
827 fn nosupport() {
828 assert!(matches!(
830 TransportId::from_str("obfs4"),
831 Err(TransportIdError::NoSupport)
832 ));
833 }
834
835 #[test]
836 #[cfg(feature = "pt-client")]
837 fn wellformed() {
838 for id in &["snowflake", "obfs4", "_ohai", "Z", "future_WORK2"] {
839 assert!(is_well_formed_id(id));
840 }
841
842 for id in &[" ", "Mölm", "12345", ""] {
843 assert!(!is_well_formed_id(id));
844 }
845 }
846
847 #[test]
848 #[cfg(feature = "pt-client")]
849 fn parsing() {
850 let obfs = TransportId::from_str("obfs4").unwrap();
851 let dflt = TransportId::default();
852 let dflt2 = TransportId::from_str("<none>").unwrap();
853 let dflt3 = TransportId::from_str("-").unwrap();
854 let dflt4 = TransportId::from_str("").unwrap();
855 let dflt5 = TransportId::from_str("bridge").unwrap();
856 let snow = TransportId::from_str("snowflake").unwrap();
857 let obfs_again = TransportId::from_str("obfs4").unwrap();
858
859 assert_eq!(obfs, obfs_again);
860 assert_eq!(dflt, dflt2);
861 assert_eq!(dflt, dflt3);
862 assert_eq!(dflt, dflt4);
863 assert_eq!(dflt, dflt5);
864 assert_ne!(snow, obfs);
865 assert_ne!(snow, dflt);
866
867 assert_eq!(dflt.to_string(), "-");
868
869 assert!(matches!(
870 TransportId::from_str("12345"),
871 Err(TransportIdError::BadId(_))
872 ));
873 assert!(matches!(
874 TransportId::from_str("Bridge"),
875 Err(TransportIdError::BadId(_))
876 ));
877 }
878
879 #[test]
880 fn addr() {
881 let chk_bridge_addr = |a: &PtTargetAddr, addr: &str| {
882 let ba: BridgeAddr = addr.parse().unwrap();
883 assert_eq!(&ba.to_string(), addr);
884
885 assert_eq!(&PtTargetAddr::from(Some(ba.clone())), a);
886 let reba: Option<BridgeAddr> = a.clone().into();
887 assert_eq!(reba.as_ref(), Some(&ba));
888 };
889
890 for addr in &["1.2.3.4:555", "[::1]:9999"] {
891 let a: PtTargetAddr = addr.parse().unwrap();
892 assert_eq!(&a.to_string(), addr);
893
894 let sa: SocketAddr = addr.parse().unwrap();
895 assert_eq!(a.addrs().collect_vec(), &[sa]);
896
897 chk_bridge_addr(&a, addr);
898 }
899
900 for addr in &["www.example.com:9100", "-"] {
901 let a: PtTargetAddr = addr.parse().unwrap();
902 assert_eq!(&a.to_string(), addr);
903 assert_eq!(a.addrs().collect_vec(), &[]);
904
905 if a == PtTargetAddr::None {
906 let e = BridgeAddr::from_str(addr).unwrap_err();
907 assert!(matches!(e, BridgeAddrError::BadAddress(_)));
908 } else {
909 chk_bridge_addr(&a, addr);
910 }
911 }
912
913 for addr in &["foobar", "<<<>>>"] {
914 let e = PtTargetAddr::from_str(addr).unwrap_err();
915 assert!(matches!(e, BridgeAddrError::BadAddress(_)));
916
917 let e = BridgeAddr::from_str(addr).unwrap_err();
918 assert!(matches!(e, BridgeAddrError::BadAddress(_)));
919 }
920 }
921
922 #[test]
923 fn transport_id() {
924 let id1: TransportId = "<none>".parse().unwrap();
925 assert!(id1.is_builtin());
926 assert_eq!(id1.to_string(), "-".to_string());
927
928 #[cfg(feature = "pt-client")]
929 {
930 let id2: TransportId = "obfs4".parse().unwrap();
931 assert_ne!(id2, id1);
932 assert!(!id2.is_builtin());
933 assert_eq!(id2.to_string(), "obfs4");
934
935 assert!(matches!(
936 TransportId::from_str("==="),
937 Err(TransportIdError::BadId(_))
938 ));
939 }
940
941 #[cfg(not(feature = "pt-client"))]
942 {
943 assert!(matches!(
944 TransportId::from_str("obfs4"),
945 Err(TransportIdError::NoSupport)
946 ));
947 }
948 }
949
950 #[test]
951 fn settings() {
952 let s = PtTargetSettings::try_from(vec![]).unwrap();
953 assert_eq!(Vec::<_>::from(s), vec![]);
954
955 let v = vec![("abc".into(), "def".into()), ("ghi".into(), "jkl".into())];
956 let s = PtTargetSettings::try_from(v.clone()).unwrap();
957 assert_eq!(Vec::<_>::from(s), v);
958
959 let v = vec![("a=b".into(), "def".into())];
960 let s = PtTargetSettings::try_from(v);
961 assert!(matches!(s, Err(PtTargetInvalidSetting::Key(_))));
962
963 let v = vec![("abc".into(), "d ef".into())];
964 let s = PtTargetSettings::try_from(v);
965 assert!(matches!(s, Err(PtTargetInvalidSetting::Value(_))));
966 }
967
968 #[test]
969 fn chanmethod_direct() {
970 let a1 = "127.0.0.1:8080".parse().unwrap();
971 let a2 = "127.0.0.2:8181".parse().unwrap();
972 let a3 = "127.0.0.3:8282".parse().unwrap();
973
974 let m = ChannelMethod::Direct(vec![a1, a2]);
975 assert_eq!(m.socket_addrs(), Some(&[a1, a2][..]));
976 assert_eq!((m.target_addr()), Some(PtTargetAddr::IpPort(a1)));
977 assert!(m.is_direct());
978 assert_eq!(m.transport_id(), TransportId::default());
979
980 let m2 = ChannelMethod::Direct(vec![a1, a2, a3]);
981 assert!(m.contained_by(&m));
982 assert!(m.contained_by(&m2));
983 assert!(!m2.contained_by(&m));
984
985 let mut m3 = m2.clone();
986 m3.retain_addrs(|a| a.port() != 8282).unwrap();
987 assert_eq!(m3, m);
988 assert_ne!(m3, m2);
989 }
990
991 #[test]
992 #[cfg(feature = "pt-client")]
993 fn chanmethod_pt() {
994 use itertools::Itertools;
995
996 let transport = "giraffe".parse().unwrap();
997 let addr1 = PtTargetAddr::HostPort("pt.example.com".into(), 1234);
998 let target1 = PtTarget::new("giraffe".parse().unwrap(), addr1.clone());
999 let m1 = ChannelMethod::Pluggable(target1);
1000
1001 let addr2 = PtTargetAddr::IpPort("127.0.0.1:567".parse().unwrap());
1002 let target2 = PtTarget::new("giraffe".parse().unwrap(), addr2.clone());
1003 let m2 = ChannelMethod::Pluggable(target2);
1004
1005 let addr3 = PtTargetAddr::None;
1006 let target3 = PtTarget::new("giraffe".parse().unwrap(), addr3.clone());
1007 let m3 = ChannelMethod::Pluggable(target3);
1008
1009 assert_eq!(m1.socket_addrs(), None);
1010 assert_eq!(
1011 m2.socket_addrs(),
1012 Some(&["127.0.0.1:567".parse().unwrap()][..])
1013 );
1014 assert_eq!(m3.socket_addrs(), None);
1015
1016 assert_eq!(m1.target_addr(), Some(addr1));
1017 assert_eq!(m2.target_addr(), Some(addr2));
1018 assert_eq!(m3.target_addr(), Some(addr3));
1019
1020 assert!(!m1.is_direct());
1021 assert!(!m2.is_direct());
1022 assert!(!m3.is_direct());
1023
1024 assert_eq!(m1.transport_id(), transport);
1025 assert_eq!(m2.transport_id(), transport);
1026 assert_eq!(m3.transport_id(), transport);
1027
1028 for v in [&m1, &m2, &m3].iter().combinations(2) {
1029 let first = v[0];
1030 let second = v[1];
1031 assert_eq!(first.contained_by(second), first == second);
1032 }
1033
1034 let mut m1new = m1.clone();
1035 let mut m2new = m2.clone();
1036 let mut m3new = m3.clone();
1037 m1new.retain_addrs(|a| a.port() == 567).unwrap();
1039 m2new.retain_addrs(|a| a.port() == 567).unwrap();
1040 m3new.retain_addrs(|a| a.port() == 567).unwrap();
1041 assert_eq!(m1new, m1);
1042 assert_eq!(m2new, m2);
1043 assert_eq!(m3new, m3);
1044
1045 assert!(matches!(
1047 m2new.retain_addrs(|a| a.port() == 999),
1048 Err(RetainAddrsError::NoAddrsLeft)
1049 ));
1050 }
1051}