1use crate::err::ErrorDetail;
6use derive_deftly::Deftly;
7use derive_more::AsRef;
8use fs_mistrust::{Mistrust, MistrustBuilder};
9use std::collections::HashMap;
10use std::path::Path;
11use std::path::PathBuf;
12use std::result::Result as StdResult;
13use std::time::Duration;
14
15pub use tor_chanmgr::{ChannelConfig, ChannelConfigBuilder};
16pub use tor_config::convert_helper_via_multi_line_list_builder;
17use tor_config::derive::prelude::*;
18use tor_config::extend_builder::extend_with_replace;
19pub use tor_config::impl_standard_builder;
20pub use tor_config::list_builder::{MultilineListBuilder, MultilineListBuilderError};
21pub use tor_config::mistrust::BuilderExt as _;
22pub use tor_config::{BoolOrAuto, ConfigError};
23pub use tor_config::{ConfigBuildError, ConfigurationSource, ConfigurationSources, Reconfigure};
24pub use tor_config::{define_list_builder_accessors, define_list_builder_helper};
25pub use tor_config_path::{CfgPath, CfgPathError, CfgPathResolver};
26pub use tor_linkspec::{ChannelMethod, HasChanMethod, PtTransportName, TransportId};
27
28pub use tor_guardmgr::bridge::BridgeConfigBuilder;
29
30#[cfg(feature = "bridge-client")]
31pub use tor_guardmgr::bridge::BridgeParseError;
32
33use tor_guardmgr::bridge::BridgeConfig;
34use tor_keymgr::config::{ArtiKeystoreConfig, ArtiKeystoreConfigBuilder};
35
36pub mod circ {
38 pub use tor_circmgr::{
39 CircMgrConfig, CircuitTiming, CircuitTimingBuilder, PathConfig, PathConfigBuilder,
40 PreemptiveCircuitConfig, PreemptiveCircuitConfigBuilder,
41 };
42}
43
44pub mod dir {
46 pub use tor_dircommon::authority::{AuthorityContacts, AuthorityContactsBuilder};
47 pub use tor_dircommon::config::{
48 DirTolerance, DirToleranceBuilder, DownloadScheduleConfig, DownloadScheduleConfigBuilder,
49 NetworkConfig, NetworkConfigBuilder,
50 };
51 pub use tor_dircommon::retry::{DownloadSchedule, DownloadScheduleBuilder};
52 pub use tor_dirmgr::{DirMgrConfig, FallbackDir, FallbackDirBuilder};
53}
54
55#[cfg(feature = "pt-client")]
57pub mod pt {
58 pub use tor_ptmgr::config::{TransportConfig, TransportConfigBuilder};
59}
60
61#[cfg(feature = "onion-service-service")]
63pub mod onion_service {
64 pub use tor_hsservice::config::{OnionServiceConfig, OnionServiceConfigBuilder};
65}
66
67pub mod vanguards {
69 pub use tor_guardmgr::{VanguardConfig, VanguardConfigBuilder};
70}
71
72#[cfg(not(all(
73 feature = "vanguards",
74 any(feature = "onion-service-client", feature = "onion-service-service"),
75)))]
76use {
77 std::sync::LazyLock,
78 tor_config::ExplicitOrAuto,
79 tor_guardmgr::{VanguardConfig, VanguardConfigBuilder, VanguardMode},
80};
81
82#[cfg(not(all(
86 feature = "vanguards",
87 any(feature = "onion-service-client", feature = "onion-service-service"),
88)))]
89static DISABLED_VANGUARDS: LazyLock<Box<VanguardConfig>> = LazyLock::new(|| {
90 Box::new(
91 VanguardConfigBuilder::default()
92 .mode(ExplicitOrAuto::Explicit(VanguardMode::Disabled))
93 .build()
94 .expect("Could not build a disabled `VanguardConfig`"),
95 )
96});
97
98#[derive(Debug, Clone, Deftly, Eq, PartialEq)]
107#[derive_deftly(TorConfig)]
108pub struct ClientAddrConfig {
109 #[deftly(tor_config(default))]
114 pub(crate) allow_local_addrs: bool,
115
116 #[cfg(feature = "onion-service-client")]
122 #[deftly(tor_config(default = "true"))]
123 pub(crate) allow_onion_addrs: bool,
124}
125
126#[derive(Debug, Clone, Deftly, Eq, PartialEq)]
135#[derive_deftly(TorConfig)]
136#[non_exhaustive]
137pub struct StreamTimeoutConfig {
138 #[deftly(tor_config(default = "default_connect_timeout()"))]
141 pub(crate) connect_timeout: Duration,
142
143 #[deftly(tor_config(default = "default_dns_resolve_timeout()"))]
145 pub(crate) resolve_timeout: Duration,
146
147 #[deftly(tor_config(default = "default_dns_resolve_ptr_timeout()"))]
150 pub(crate) resolve_ptr_timeout: Duration,
151}
152
153fn default_connect_timeout() -> Duration {
155 Duration::new(10, 0)
156}
157
158fn default_dns_resolve_timeout() -> Duration {
160 Duration::new(10, 0)
161}
162
163fn default_dns_resolve_ptr_timeout() -> Duration {
165 Duration::new(10, 0)
166}
167
168#[derive(Debug, Clone, Deftly, Eq, PartialEq)]
184#[derive_deftly(TorConfig)]
185pub struct StorageConfig {
186 #[deftly(tor_config(default = "default_cache_dir()", setter(into)))]
202 pub(crate) cache_dir: CfgPath,
203
204 #[deftly(tor_config(default = "default_state_dir()", setter(into)))]
207 state_dir: CfgPath,
208
209 #[cfg(feature = "keymgr")]
213 #[deftly(tor_config(sub_builder))]
214 keystore: ArtiKeystoreConfig,
215
216 #[deftly(tor_config(
218 sub_builder(build_fn = "build_for_arti"),
219 extend_with = "extend_with_replace"
220 ))]
221 permissions: Mistrust,
222}
223
224fn default_cache_dir() -> CfgPath {
226 CfgPath::new("${ARTI_CACHE}".to_owned())
227}
228
229fn default_state_dir() -> CfgPath {
231 CfgPath::new("${ARTI_LOCAL_DATA}".to_owned())
232}
233
234macro_rules! expand_dir {
237 ($self:ident, $dirname:ident, $dircfg:ident) => {
238 $self
239 .$dirname
240 .path($dircfg)
241 .map_err(|e| ConfigBuildError::Invalid {
242 field: stringify!($dirname).to_owned(),
243 problem: e.to_string(),
244 })
245 };
246}
247
248impl StorageConfig {
249 pub(crate) fn expand_state_dir(
251 &self,
252 path_resolver: &CfgPathResolver,
253 ) -> Result<PathBuf, ConfigBuildError> {
254 expand_dir!(self, state_dir, path_resolver)
255 }
256 pub(crate) fn expand_cache_dir(
258 &self,
259 path_resolver: &CfgPathResolver,
260 ) -> Result<PathBuf, ConfigBuildError> {
261 expand_dir!(self, cache_dir, path_resolver)
262 }
263 #[allow(clippy::unnecessary_wraps)]
265 pub(crate) fn keystore(&self) -> ArtiKeystoreConfig {
266 cfg_if::cfg_if! {
267 if #[cfg(feature="keymgr")] {
268 self.keystore.clone()
269 } else {
270 Default::default()
271 }
272 }
273 }
274 pub(crate) fn permissions(&self) -> &Mistrust {
276 &self.permissions
277 }
278}
279
280#[derive(Debug, Clone, Deftly, Eq, PartialEq)]
353#[derive_deftly(TorConfig)]
354#[deftly(tor_config(pre_build = "validate_bridges_config", attr = "non_exhaustive"))]
355#[non_exhaustive]
356pub struct BridgesConfig {
357 #[deftly(tor_config(default))]
364 pub(crate) enabled: BoolOrAuto,
365
366 #[deftly(tor_config(no_magic, sub_builder, setter(skip)))]
371 bridges: BridgeList,
372
373 #[cfg(feature = "pt-client")] #[deftly(tor_config(
376 list(element(build), listtype = "TransportConfigList"),
377 default = "vec![]"
378 ))]
379 pub(crate) transports: Vec<pt::TransportConfig>,
380}
381
382#[cfg(feature = "pt-client")]
383fn validate_pt_config(bridges: &BridgesConfigBuilder) -> Result<(), ConfigBuildError> {
387 use std::collections::HashSet;
388 use std::str::FromStr;
389
390 let mut protocols_defined: HashSet<PtTransportName> = HashSet::new();
392 if let Some(transportlist) = bridges.opt_transports() {
393 for protocols in transportlist.iter() {
394 for protocol in protocols.get_protocols() {
395 protocols_defined.insert(protocol.clone());
396 }
397 }
398 }
399
400 for maybe_protocol in bridges
403 .bridges
404 .bridges
405 .as_deref()
406 .unwrap_or_default()
407 .iter()
408 {
409 match maybe_protocol.get_transport() {
410 Some(raw_protocol) => {
411 let protocol = TransportId::from_str(raw_protocol)
414 .unwrap_or_default()
417 .into_pluggable();
418 match protocol {
420 Some(protocol_required) => {
421 if protocols_defined.contains(&protocol_required) {
422 return Ok(());
423 }
424 }
425 None => return Ok(()),
426 }
427 }
428 None => {
429 return Ok(());
430 }
431 }
432 }
433
434 Err(ConfigBuildError::Inconsistent {
435 fields: ["bridges.bridges", "bridges.transports"].map(Into::into).into_iter().collect(),
436 problem: "Bridges configured, but all bridges unusable due to lack of corresponding pluggable transport in `[bridges.transports]`".into(),
437 })
438}
439
440#[allow(clippy::unnecessary_wraps)]
442fn validate_bridges_config(bridges: &BridgesConfigBuilder) -> Result<(), ConfigBuildError> {
443 let _ = bridges; use BoolOrAuto as BoA;
446
447 match (
454 bridges.enabled.unwrap_or_default(),
455 bridges.bridges.bridges.as_deref().unwrap_or_default(),
456 ) {
457 (BoA::Auto, _) | (BoA::Explicit(false), _) | (BoA::Explicit(true), [_, ..]) => {}
458 (BoA::Explicit(true), []) => {
459 return Err(ConfigBuildError::Inconsistent {
460 fields: ["enabled", "bridges"].map(Into::into).into_iter().collect(),
461 problem: "bridges.enabled=true, but no bridges defined".into(),
462 });
463 }
464 }
465 #[cfg(feature = "pt-client")]
466 {
467 if bridges_enabled(
468 bridges.enabled.unwrap_or_default(),
469 bridges.bridges.bridges.as_deref().unwrap_or_default(),
470 ) {
471 validate_pt_config(bridges)?;
472 }
473 }
474
475 Ok(())
476}
477
478fn bridges_enabled(enabled: BoolOrAuto, bridges: &[impl Sized]) -> bool {
480 #[cfg(feature = "bridge-client")]
481 {
482 enabled.as_bool().unwrap_or(!bridges.is_empty())
483 }
484
485 #[cfg(not(feature = "bridge-client"))]
486 {
487 let _ = (enabled, bridges);
488 false
489 }
490}
491
492impl BridgesConfig {
493 fn bridges_enabled(&self) -> bool {
495 bridges_enabled(self.enabled, &self.bridges)
496 }
497}
498
499pub type BridgeList = Vec<BridgeConfig>;
504
505define_list_builder_helper! {
506 struct BridgeListBuilder {
507 bridges: [BridgeConfigBuilder],
508 }
509 built: BridgeList = bridges;
510 default = vec![];
511 #[serde(try_from="MultilineListBuilder<BridgeConfigBuilder>")]
512 #[serde(into="MultilineListBuilder<BridgeConfigBuilder>")]
513}
514
515convert_helper_via_multi_line_list_builder! {
516 struct BridgeListBuilder {
517 bridges: [BridgeConfigBuilder],
518 }
519}
520
521#[cfg(feature = "bridge-client")]
522define_list_builder_accessors! {
523 struct BridgesConfigBuilder {
524 pub bridges: [BridgeConfigBuilder],
525 }
526}
527
528#[derive(Clone, Deftly, Debug, AsRef, educe::Educe)]
546#[educe(PartialEq, Eq)]
547#[derive_deftly(TorConfig)]
548#[non_exhaustive]
549pub struct TorClientConfig {
550 #[deftly(tor_config(sub_builder))]
552 tor_network: dir::NetworkConfig,
553
554 #[deftly(tor_config(sub_builder))]
556 pub(crate) storage: StorageConfig,
557
558 #[deftly(tor_config(sub_builder))]
560 download_schedule: dir::DownloadScheduleConfig,
561
562 #[deftly(tor_config(sub_builder))]
569 directory_tolerance: dir::DirTolerance,
570
571 #[deftly(tor_config(
574 setter(skip), field(ty = "HashMap<String, i32>"),
576 build = "|this: &Self| default_extend(this.override_net_params.clone())",
577 extend_with = "extend_with_replace"
578 ))]
579 pub(crate) override_net_params: tor_netdoc::doc::netstatus::NetParams<i32>,
580
581 #[deftly(tor_config(sub_builder))]
583 pub(crate) bridges: BridgesConfig,
584
585 #[deftly(tor_config(sub_builder))]
587 pub(crate) channel: ChannelConfig,
588
589 #[deftly(tor_config(sub_builder))]
595 pub(crate) system: SystemConfig,
596
597 #[as_ref]
599 #[deftly(tor_config(sub_builder))]
600 path_rules: circ::PathConfig,
601
602 #[as_ref]
604 #[deftly(tor_config(sub_builder))]
605 preemptive_circuits: circ::PreemptiveCircuitConfig,
606
607 #[as_ref]
609 #[deftly(tor_config(sub_builder))]
610 circuit_timing: circ::CircuitTiming,
611
612 #[deftly(tor_config(sub_builder))]
614 pub(crate) address_filter: ClientAddrConfig,
615
616 #[deftly(tor_config(sub_builder))]
618 pub(crate) stream_timeouts: StreamTimeoutConfig,
619
620 #[deftly(tor_config(sub_builder))]
624 pub(crate) vanguards: vanguards::VanguardConfig,
625
626 #[as_ref]
634 #[deftly(tor_config(skip, build = "|_| tor_config_path::arti_client_base_resolver()"))]
635 #[educe(PartialEq(ignore), Eq(ignore))]
636 pub(crate) path_resolver: CfgPathResolver,
637}
638
639impl tor_config::load::TopLevel for TorClientConfig {
640 type Builder = TorClientConfigBuilder;
641}
642
643fn default_extend<T: Default + Extend<X>, X>(to_add: impl IntoIterator<Item = X>) -> T {
645 let mut collection = T::default();
646 collection.extend(to_add);
647 collection
648}
649
650#[derive(Debug, Clone, Deftly, Eq, PartialEq)]
657#[derive_deftly(TorConfig)]
658#[non_exhaustive]
659pub struct SystemConfig {
660 #[deftly(tor_config(sub_builder))]
662 pub(crate) memory: tor_memquota::Config,
663}
664
665impl AsRef<tor_guardmgr::VanguardConfig> for TorClientConfig {
666 fn as_ref(&self) -> &tor_guardmgr::VanguardConfig {
667 cfg_if::cfg_if! {
668 if #[cfg(all(
669 feature = "vanguards",
670 any(feature = "onion-service-client", feature = "onion-service-service"),
671 ))]
672 {
673 &self.vanguards
674 } else {
675 &DISABLED_VANGUARDS
676 }
677 }
678 }
679}
680
681impl tor_circmgr::CircMgrConfig for TorClientConfig {}
682
683#[cfg(feature = "onion-service-client")]
684impl tor_hsclient::HsClientConnectorConfig for TorClientConfig {}
685
686#[cfg(any(feature = "onion-service-client", feature = "onion-service-service"))]
687impl tor_circmgr::hspool::HsCircPoolConfig for TorClientConfig {
688 #[cfg(all(
689 feature = "vanguards",
690 any(feature = "onion-service-client", feature = "onion-service-service")
691 ))]
692 fn vanguard_config(&self) -> &tor_guardmgr::VanguardConfig {
693 &self.vanguards
694 }
695}
696
697impl AsRef<tor_dircommon::fallback::FallbackList> for TorClientConfig {
698 fn as_ref(&self) -> &tor_dircommon::fallback::FallbackList {
699 self.tor_network.fallback_caches()
700 }
701}
702impl AsRef<[BridgeConfig]> for TorClientConfig {
703 fn as_ref(&self) -> &[BridgeConfig] {
704 #[cfg(feature = "bridge-client")]
705 {
706 &self.bridges.bridges
707 }
708
709 #[cfg(not(feature = "bridge-client"))]
710 {
711 &[]
712 }
713 }
714}
715impl AsRef<BridgesConfig> for TorClientConfig {
716 fn as_ref(&self) -> &BridgesConfig {
717 &self.bridges
718 }
719}
720impl tor_guardmgr::GuardMgrConfig for TorClientConfig {
721 fn bridges_enabled(&self) -> bool {
722 self.bridges.bridges_enabled()
723 }
724}
725
726impl TorClientConfig {
727 #[rustfmt::skip]
729 pub fn dir_mgr_config(&self) -> Result<dir::DirMgrConfig, ConfigBuildError> {
730 Ok(dir::DirMgrConfig {
731 network: self.tor_network .clone(),
732 schedule: self.download_schedule .clone(),
733 tolerance: self.directory_tolerance.clone(),
734 cache_dir: self.storage.expand_cache_dir(&self.path_resolver)?,
735 cache_trust: self.storage.permissions.clone(),
736 override_net_params: self.override_net_params.clone(),
737 extensions: Default::default(),
738 })
739 }
740
741 pub fn fs_mistrust(&self) -> &Mistrust {
757 self.storage.permissions()
758 }
759
760 pub fn keystore(&self) -> ArtiKeystoreConfig {
762 self.storage.keystore()
763 }
764
765 pub(crate) fn state_dir(&self) -> StdResult<(PathBuf, &fs_mistrust::Mistrust), ErrorDetail> {
768 let state_dir = self
769 .storage
770 .expand_state_dir(&self.path_resolver)
771 .map_err(ErrorDetail::Configuration)?;
772 let mistrust = self.storage.permissions();
773
774 Ok((state_dir, mistrust))
775 }
776
777 #[cfg(feature = "testing")]
782 pub fn system_memory(&self) -> &tor_memquota::Config {
783 &self.system.memory
784 }
785}
786
787impl TorClientConfigBuilder {
788 pub fn from_directories<P, Q>(state_dir: P, cache_dir: Q) -> Self
793 where
794 P: AsRef<Path>,
795 Q: AsRef<Path>,
796 {
797 let mut builder = Self::default();
798
799 builder
800 .storage()
801 .cache_dir(CfgPath::new_literal(cache_dir.as_ref()))
802 .state_dir(CfgPath::new_literal(state_dir.as_ref()));
803
804 builder
805 }
806
807 pub fn override_net_params(&mut self) -> &mut HashMap<String, i32> {
813 &mut self.override_net_params
814 }
815}
816
817pub fn default_config_files() -> Result<Vec<ConfigurationSource>, CfgPathError> {
819 let path_resolver = tor_config_path::arti_client_base_resolver();
821
822 ["${ARTI_CONFIG}/arti.toml", "${ARTI_CONFIG}/arti.d/"]
823 .into_iter()
824 .map(|f| {
825 let path = CfgPath::new(f.into()).path(&path_resolver)?;
826 Ok(ConfigurationSource::from_path(path))
827 })
828 .collect()
829}
830
831#[deprecated = "use tor-config::mistrust::ARTI_FS_DISABLE_PERMISSION_CHECKS instead"]
833pub const FS_PERMISSIONS_CHECKS_DISABLE_VAR: &str = "ARTI_FS_DISABLE_PERMISSION_CHECKS";
834
835#[deprecated(since = "0.5.0")]
842#[allow(deprecated)]
843pub fn fs_permissions_checks_disabled_via_env() -> bool {
844 std::env::var_os(FS_PERMISSIONS_CHECKS_DISABLE_VAR).is_some()
845}
846
847#[cfg(test)]
848mod test {
849 #![allow(clippy::bool_assert_comparison)]
851 #![allow(clippy::clone_on_copy)]
852 #![allow(clippy::dbg_macro)]
853 #![allow(clippy::mixed_attributes_style)]
854 #![allow(clippy::print_stderr)]
855 #![allow(clippy::print_stdout)]
856 #![allow(clippy::single_char_pattern)]
857 #![allow(clippy::unwrap_used)]
858 #![allow(clippy::unchecked_time_subtraction)]
859 #![allow(clippy::useless_vec)]
860 #![allow(clippy::needless_pass_by_value)]
861 #![allow(clippy::string_slice)] use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
864
865 use super::*;
866
867 #[test]
868 fn defaults() {
869 let dflt = TorClientConfig::default();
870 let b2 = TorClientConfigBuilder::default();
871 let dflt2 = b2.build().unwrap();
872 assert_eq!(&dflt, &dflt2);
873 }
874
875 #[test]
876 fn builder() {
877 let sec = std::time::Duration::from_secs(1);
878
879 let mut authorities = dir::AuthorityContacts::builder();
880 authorities.v3idents().push([22; 20].into());
881 authorities.v3idents().push([44; 20].into());
882 authorities.uploads().push(vec![
883 SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 80)),
884 SocketAddr::V6(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 80, 0, 0)),
885 ]);
886
887 let mut fallback = dir::FallbackDir::builder();
888 fallback
889 .rsa_identity([23; 20].into())
890 .ed_identity([99; 32].into())
891 .orports()
892 .push("127.0.0.7:7".parse().unwrap());
893
894 let mut bld = TorClientConfig::builder();
895 *bld.tor_network().authorities() = authorities;
896 bld.tor_network().set_fallback_caches(vec![fallback]);
897 bld.storage()
898 .cache_dir(CfgPath::new("/var/tmp/foo".to_owned()))
899 .state_dir(CfgPath::new("/var/tmp/bar".to_owned()));
900 bld.download_schedule().retry_certs().attempts(10);
901 bld.download_schedule().retry_certs().initial_delay(sec);
902 bld.download_schedule().retry_certs().parallelism(3);
903 bld.download_schedule().retry_microdescs().attempts(30);
904 bld.download_schedule()
905 .retry_microdescs()
906 .initial_delay(10 * sec);
907 bld.download_schedule().retry_microdescs().parallelism(9);
908 bld.override_net_params()
909 .insert("wombats-per-quokka".to_owned(), 7);
910 bld.path_rules()
911 .ipv4_subnet_family_prefix(20)
912 .ipv6_subnet_family_prefix(48);
913 bld.circuit_timing()
914 .max_dirtiness(90 * sec)
915 .request_timeout(10 * sec)
916 .request_max_retries(22)
917 .request_loyalty(3600 * sec);
918 bld.address_filter().allow_local_addrs(true);
919
920 let val = bld.build().unwrap();
921
922 assert_ne!(val, TorClientConfig::default());
923 }
924
925 #[test]
926 fn bridges_supported() {
927 fn chk(exp: Result<usize, ()>, s: &str) {
930 eprintln!("----------\n{s}\n----------\n");
931 let got = (|| {
932 let cfg: toml::Value = toml::from_str(s).unwrap();
933 let cfg: TorClientConfigBuilder = cfg.try_into()?;
934 let cfg = cfg.build()?;
935 let n_bridges = cfg.bridges.bridges.len();
936 Ok::<_, anyhow::Error>(n_bridges) })()
938 .map_err(|_| ());
939 assert_eq!(got, exp);
940 }
941
942 let chk_enabled_or_auto = |exp, bridges_toml| {
943 for enabled in [r#""#, r#"enabled = true"#, r#"enabled = "auto""#] {
944 chk(exp, &format!("[bridges]\n{}\n{}", enabled, bridges_toml));
945 }
946 };
947
948 let ok_1_if = |b: bool| b.then_some(1).ok_or(());
949
950 chk(
951 Err(()),
952 r#"
953 [bridges]
954 enabled = true
955 "#,
956 );
957
958 chk_enabled_or_auto(
959 ok_1_if(cfg!(feature = "bridge-client")),
960 r#"
961 bridges = ["192.0.2.83:80 $0bac39417268b96b9f514ef763fa6fba1a788956"]
962 "#,
963 );
964
965 chk_enabled_or_auto(
966 ok_1_if(cfg!(feature = "pt-client")),
967 r#"
968 bridges = ["obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1"]
969 [[bridges.transports]]
970 protocols = ["obfs4"]
971 path = "obfs4proxy"
972 "#,
973 );
974 }
975
976 #[test]
977 fn check_default() {
978 let dflt = default_config_files().unwrap();
982 assert!(dflt[0].as_path().unwrap().ends_with("arti.toml"));
983 assert!(dflt[1].as_path().unwrap().ends_with("arti.d"));
984 assert_eq!(dflt.len(), 2);
985 }
986
987 #[test]
988 #[cfg(not(all(
989 feature = "vanguards",
990 any(feature = "onion-service-client", feature = "onion-service-service"),
991 )))]
992 fn check_disabled_vanguards_static() {
993 #[allow(clippy::borrowed_box)]
995 let _: &Box<VanguardConfig> = LazyLock::force(&DISABLED_VANGUARDS);
996 }
997
998 #[test]
999 #[cfg(feature = "pt-client")]
1000 fn check_bridge_pt() {
1001 let from_toml = |s: &str| -> TorClientConfigBuilder {
1002 let cfg: toml::Value = toml::from_str(dbg!(s)).unwrap();
1003 let cfg: TorClientConfigBuilder = cfg.try_into().unwrap();
1004 cfg
1005 };
1006
1007 let chk = |cfg: &TorClientConfigBuilder, expected: Result<(), &str>| match (
1008 cfg.build(),
1009 expected,
1010 ) {
1011 (Ok(_), Ok(())) => {}
1012 (Err(e), Err(ex)) => {
1013 if !e.to_string().contains(ex) {
1014 panic!("\"{e}\" did not contain {ex}");
1015 }
1016 }
1017 (Ok(_), Err(ex)) => {
1018 panic!("Expected {ex} but cfg succeeded");
1019 }
1020 (Err(e), Ok(())) => {
1021 panic!("Expected success but got error {e}")
1022 }
1023 };
1024
1025 let test_cases = [
1026 ("# No bridges", Ok(())),
1027 (
1028 r#"
1029 # No bridges but we still enabled bridges
1030 [bridges]
1031 enabled = true
1032 bridges = []
1033 "#,
1034 Err("bridges.enabled=true, but no bridges defined"),
1035 ),
1036 (
1037 r#"
1038 # One non-PT bridge
1039 [bridges]
1040 enabled = true
1041 bridges = [
1042 "192.0.2.83:80 $0bac39417268b96b9f514ef763fa6fba1a788956",
1043 ]
1044 "#,
1045 Ok(()),
1046 ),
1047 (
1048 r#"
1049 # One obfs4 bridge
1050 [bridges]
1051 enabled = true
1052 bridges = [
1053 "obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1",
1054 ]
1055 [[bridges.transports]]
1056 protocols = ["obfs4"]
1057 path = "obfs4proxy"
1058 "#,
1059 Ok(()),
1060 ),
1061 (
1062 r#"
1063 # One obfs4 bridge with unmanaged transport.
1064 [bridges]
1065 enabled = true
1066 bridges = [
1067 "obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1",
1068 ]
1069 [[bridges.transports]]
1070 protocols = ["obfs4"]
1071 proxy_addr = "127.0.0.1:31337"
1072 "#,
1073 Ok(()),
1074 ),
1075 (
1076 r#"
1077 # Transport is both managed and unmanaged.
1078 [[bridges.transports]]
1079 protocols = ["obfs4"]
1080 path = "obfsproxy"
1081 proxy_addr = "127.0.0.1:9999"
1082 "#,
1083 Err("Cannot provide both path and proxy_addr"),
1084 ),
1085 (
1086 r#"
1087 # One obfs4 bridge and non-PT bridge
1088 [bridges]
1089 enabled = false
1090 bridges = [
1091 "192.0.2.83:80 $0bac39417268b96b9f514ef763fa6fba1a788956",
1092 "obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1",
1093 ]
1094 [[bridges.transports]]
1095 protocols = ["obfs4"]
1096 path = "obfs4proxy"
1097 "#,
1098 Ok(()),
1099 ),
1100 (
1101 r#"
1102 # One obfs4 and non-PT bridge with no transport
1103 [bridges]
1104 enabled = true
1105 bridges = [
1106 "192.0.2.83:80 $0bac39417268b96b9f514ef763fa6fba1a788956",
1107 "obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1",
1108 ]
1109 "#,
1110 Ok(()),
1111 ),
1112 (
1113 r#"
1114 # One obfs4 bridge with no transport
1115 [bridges]
1116 enabled = true
1117 bridges = [
1118 "obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1",
1119 ]
1120 "#,
1121 Err("all bridges unusable due to lack of corresponding pluggable transport"),
1122 ),
1123 (
1124 r#"
1125 # One obfs4 bridge with no transport but bridges are disabled
1126 [bridges]
1127 enabled = false
1128 bridges = [
1129 "obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1",
1130 ]
1131 "#,
1132 Ok(()),
1133 ),
1134 (
1135 r#"
1136 # One non-PT bridge with a redundant transports section
1137 [bridges]
1138 enabled = false
1139 bridges = [
1140 "192.0.2.83:80 $0bac39417268b96b9f514ef763fa6fba1a788956",
1141 ]
1142 [[bridges.transports]]
1143 protocols = ["obfs4"]
1144 path = "obfs4proxy"
1145 "#,
1146 Ok(()),
1147 ),
1148 ];
1149
1150 for (test_case, expected) in test_cases.iter() {
1151 chk(&from_toml(test_case), *expected);
1152 }
1153 }
1154}