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)]
176#[derive_deftly(TorConfig)]
177pub struct SoftwareStatusOverrideConfig {
178 #[deftly(tor_config(
186 no_magic,
187 field(ty = "String"),
188 setter(skip),
189 try_build = "Self::parse_protos",
190 extend_with = "extend_with_replace"
191 ))]
192 pub(crate) ignore_missing_required_protocols: tor_protover::Protocols,
193}
194
195impl SoftwareStatusOverrideConfigBuilder {
196 fn parse_protos(&self) -> Result<tor_protover::Protocols, ConfigBuildError> {
198 use std::str::FromStr as _;
199
200 tor_protover::Protocols::from_str(&self.ignore_missing_required_protocols).map_err(|e| {
201 ConfigBuildError::Invalid {
202 field: "ignore_missing_required_protocols".to_string(),
203 problem: e.to_string(),
204 }
205 })
206 }
207
208 pub fn ignore_missing_required_protocols(&mut self, s: impl AsRef<str>) -> &mut Self {
211 self.ignore_missing_required_protocols = s.as_ref().to_string();
212 self
213 }
214}
215
216#[derive(Debug, Clone, Deftly, Eq, PartialEq)]
232#[derive_deftly(TorConfig)]
233pub struct StorageConfig {
234 #[deftly(tor_config(default = "default_cache_dir()", setter(into)))]
250 cache_dir: CfgPath,
251
252 #[deftly(tor_config(default = "default_state_dir()", setter(into)))]
255 state_dir: CfgPath,
256
257 #[cfg(feature = "keymgr")]
261 #[deftly(tor_config(sub_builder))]
262 keystore: ArtiKeystoreConfig,
263
264 #[deftly(tor_config(
266 sub_builder(build_fn = "build_for_arti"),
267 extend_with = "extend_with_replace"
268 ))]
269 permissions: Mistrust,
270}
271
272fn default_cache_dir() -> CfgPath {
274 CfgPath::new("${ARTI_CACHE}".to_owned())
275}
276
277fn default_state_dir() -> CfgPath {
279 CfgPath::new("${ARTI_LOCAL_DATA}".to_owned())
280}
281
282macro_rules! expand_dir {
285 ($self:ident, $dirname:ident, $dircfg:ident) => {
286 $self
287 .$dirname
288 .path($dircfg)
289 .map_err(|e| ConfigBuildError::Invalid {
290 field: stringify!($dirname).to_owned(),
291 problem: e.to_string(),
292 })
293 };
294}
295
296impl StorageConfig {
297 pub(crate) fn expand_state_dir(
299 &self,
300 path_resolver: &CfgPathResolver,
301 ) -> Result<PathBuf, ConfigBuildError> {
302 expand_dir!(self, state_dir, path_resolver)
303 }
304 pub(crate) fn expand_cache_dir(
306 &self,
307 path_resolver: &CfgPathResolver,
308 ) -> Result<PathBuf, ConfigBuildError> {
309 expand_dir!(self, cache_dir, path_resolver)
310 }
311 #[allow(clippy::unnecessary_wraps)]
313 pub(crate) fn keystore(&self) -> ArtiKeystoreConfig {
314 cfg_if::cfg_if! {
315 if #[cfg(feature="keymgr")] {
316 self.keystore.clone()
317 } else {
318 Default::default()
319 }
320 }
321 }
322 pub(crate) fn permissions(&self) -> &Mistrust {
324 &self.permissions
325 }
326}
327
328#[derive(Debug, Clone, Deftly, Eq, PartialEq)]
401#[derive_deftly(TorConfig)]
402#[deftly(tor_config(pre_build = "validate_bridges_config", attr = "non_exhaustive"))]
403#[non_exhaustive]
404pub struct BridgesConfig {
405 #[deftly(tor_config(default))]
412 pub(crate) enabled: BoolOrAuto,
413
414 #[deftly(tor_config(no_magic, sub_builder, setter(skip)))]
419 bridges: BridgeList,
420
421 #[cfg(feature = "pt-client")] #[deftly(tor_config(
424 list(element(build), listtype = "TransportConfigList"),
425 default = "vec![]"
426 ))]
427 pub(crate) transports: Vec<pt::TransportConfig>,
428}
429
430#[cfg(feature = "pt-client")]
431fn validate_pt_config(bridges: &BridgesConfigBuilder) -> Result<(), ConfigBuildError> {
435 use std::collections::HashSet;
436 use std::str::FromStr;
437
438 let mut protocols_defined: HashSet<PtTransportName> = HashSet::new();
440 if let Some(transportlist) = bridges.opt_transports() {
441 for protocols in transportlist.iter() {
442 for protocol in protocols.get_protocols() {
443 protocols_defined.insert(protocol.clone());
444 }
445 }
446 }
447
448 for maybe_protocol in bridges
451 .bridges
452 .bridges
453 .as_deref()
454 .unwrap_or_default()
455 .iter()
456 {
457 match maybe_protocol.get_transport() {
458 Some(raw_protocol) => {
459 let protocol = TransportId::from_str(raw_protocol)
462 .unwrap_or_default()
465 .into_pluggable();
466 match protocol {
468 Some(protocol_required) => {
469 if protocols_defined.contains(&protocol_required) {
470 return Ok(());
471 }
472 }
473 None => return Ok(()),
474 }
475 }
476 None => {
477 return Ok(());
478 }
479 }
480 }
481
482 Err(ConfigBuildError::Inconsistent {
483 fields: ["bridges.bridges", "bridges.transports"].map(Into::into).into_iter().collect(),
484 problem: "Bridges configured, but all bridges unusable due to lack of corresponding pluggable transport in `[bridges.transports]`".into(),
485 })
486}
487
488#[allow(clippy::unnecessary_wraps)]
490fn validate_bridges_config(bridges: &BridgesConfigBuilder) -> Result<(), ConfigBuildError> {
491 let _ = bridges; use BoolOrAuto as BoA;
494
495 match (
502 bridges.enabled.unwrap_or_default(),
503 bridges.bridges.bridges.as_deref().unwrap_or_default(),
504 ) {
505 (BoA::Auto, _) | (BoA::Explicit(false), _) | (BoA::Explicit(true), [_, ..]) => {}
506 (BoA::Explicit(true), []) => {
507 return Err(ConfigBuildError::Inconsistent {
508 fields: ["enabled", "bridges"].map(Into::into).into_iter().collect(),
509 problem: "bridges.enabled=true, but no bridges defined".into(),
510 });
511 }
512 }
513 #[cfg(feature = "pt-client")]
514 {
515 if bridges_enabled(
516 bridges.enabled.unwrap_or_default(),
517 bridges.bridges.bridges.as_deref().unwrap_or_default(),
518 ) {
519 validate_pt_config(bridges)?;
520 }
521 }
522
523 Ok(())
524}
525
526fn bridges_enabled(enabled: BoolOrAuto, bridges: &[impl Sized]) -> bool {
528 #[cfg(feature = "bridge-client")]
529 {
530 enabled.as_bool().unwrap_or(!bridges.is_empty())
531 }
532
533 #[cfg(not(feature = "bridge-client"))]
534 {
535 let _ = (enabled, bridges);
536 false
537 }
538}
539
540impl BridgesConfig {
541 fn bridges_enabled(&self) -> bool {
543 bridges_enabled(self.enabled, &self.bridges)
544 }
545}
546
547pub type BridgeList = Vec<BridgeConfig>;
552
553define_list_builder_helper! {
554 struct BridgeListBuilder {
555 bridges: [BridgeConfigBuilder],
556 }
557 built: BridgeList = bridges;
558 default = vec![];
559 #[serde(try_from="MultilineListBuilder<BridgeConfigBuilder>")]
560 #[serde(into="MultilineListBuilder<BridgeConfigBuilder>")]
561}
562
563convert_helper_via_multi_line_list_builder! {
564 struct BridgeListBuilder {
565 bridges: [BridgeConfigBuilder],
566 }
567}
568
569#[cfg(feature = "bridge-client")]
570define_list_builder_accessors! {
571 struct BridgesConfigBuilder {
572 pub bridges: [BridgeConfigBuilder],
573 }
574}
575
576#[derive(Clone, Deftly, Debug, AsRef, educe::Educe)]
594#[educe(PartialEq, Eq)]
595#[derive_deftly(TorConfig)]
596#[non_exhaustive]
597pub struct TorClientConfig {
598 #[deftly(tor_config(sub_builder))]
600 tor_network: dir::NetworkConfig,
601
602 #[deftly(tor_config(sub_builder))]
604 pub(crate) storage: StorageConfig,
605
606 #[deftly(tor_config(sub_builder))]
608 download_schedule: dir::DownloadScheduleConfig,
609
610 #[deftly(tor_config(sub_builder))]
617 directory_tolerance: dir::DirTolerance,
618
619 #[deftly(tor_config(
622 setter(skip), field(ty = "HashMap<String, i32>"),
624 build = "|this: &Self| default_extend(this.override_net_params.clone())",
625 extend_with = "extend_with_replace"
626 ))]
627 pub(crate) override_net_params: tor_netdoc::doc::netstatus::NetParams<i32>,
628
629 #[deftly(tor_config(sub_builder))]
631 pub(crate) bridges: BridgesConfig,
632
633 #[deftly(tor_config(sub_builder))]
635 pub(crate) channel: ChannelConfig,
636
637 #[deftly(tor_config(sub_builder))]
643 pub(crate) system: SystemConfig,
644
645 #[as_ref]
647 #[deftly(tor_config(sub_builder))]
648 path_rules: circ::PathConfig,
649
650 #[as_ref]
652 #[deftly(tor_config(sub_builder))]
653 preemptive_circuits: circ::PreemptiveCircuitConfig,
654
655 #[as_ref]
657 #[deftly(tor_config(sub_builder))]
658 circuit_timing: circ::CircuitTiming,
659
660 #[deftly(tor_config(sub_builder))]
662 pub(crate) address_filter: ClientAddrConfig,
663
664 #[deftly(tor_config(sub_builder))]
666 pub(crate) stream_timeouts: StreamTimeoutConfig,
667
668 #[deftly(tor_config(sub_builder))]
672 pub(crate) vanguards: vanguards::VanguardConfig,
673
674 #[deftly(tor_config(sub_builder))]
676 pub(crate) use_obsolete_software: SoftwareStatusOverrideConfig,
677
678 #[as_ref]
686 #[deftly(tor_config(skip, build = "|_| tor_config_path::arti_client_base_resolver()"))]
687 #[educe(PartialEq(ignore), Eq(ignore))]
688 pub(crate) path_resolver: CfgPathResolver,
689}
690
691impl tor_config::load::TopLevel for TorClientConfig {
692 type Builder = TorClientConfigBuilder;
693}
694
695fn default_extend<T: Default + Extend<X>, X>(to_add: impl IntoIterator<Item = X>) -> T {
697 let mut collection = T::default();
698 collection.extend(to_add);
699 collection
700}
701
702#[derive(Debug, Clone, Deftly, Eq, PartialEq)]
709#[derive_deftly(TorConfig)]
710#[non_exhaustive]
711pub struct SystemConfig {
712 #[deftly(tor_config(sub_builder))]
714 pub(crate) memory: tor_memquota::Config,
715}
716
717impl AsRef<tor_guardmgr::VanguardConfig> for TorClientConfig {
718 fn as_ref(&self) -> &tor_guardmgr::VanguardConfig {
719 cfg_if::cfg_if! {
720 if #[cfg(all(
721 feature = "vanguards",
722 any(feature = "onion-service-client", feature = "onion-service-service"),
723 ))]
724 {
725 &self.vanguards
726 } else {
727 &DISABLED_VANGUARDS
728 }
729 }
730 }
731}
732
733impl tor_circmgr::CircMgrConfig for TorClientConfig {}
734
735#[cfg(feature = "onion-service-client")]
736impl tor_hsclient::HsClientConnectorConfig for TorClientConfig {}
737
738#[cfg(any(feature = "onion-service-client", feature = "onion-service-service"))]
739impl tor_circmgr::hspool::HsCircPoolConfig for TorClientConfig {
740 #[cfg(all(
741 feature = "vanguards",
742 any(feature = "onion-service-client", feature = "onion-service-service")
743 ))]
744 fn vanguard_config(&self) -> &tor_guardmgr::VanguardConfig {
745 &self.vanguards
746 }
747}
748
749impl AsRef<tor_dircommon::fallback::FallbackList> for TorClientConfig {
750 fn as_ref(&self) -> &tor_dircommon::fallback::FallbackList {
751 self.tor_network.fallback_caches()
752 }
753}
754impl AsRef<[BridgeConfig]> for TorClientConfig {
755 fn as_ref(&self) -> &[BridgeConfig] {
756 #[cfg(feature = "bridge-client")]
757 {
758 &self.bridges.bridges
759 }
760
761 #[cfg(not(feature = "bridge-client"))]
762 {
763 &[]
764 }
765 }
766}
767impl AsRef<BridgesConfig> for TorClientConfig {
768 fn as_ref(&self) -> &BridgesConfig {
769 &self.bridges
770 }
771}
772impl tor_guardmgr::GuardMgrConfig for TorClientConfig {
773 fn bridges_enabled(&self) -> bool {
774 self.bridges.bridges_enabled()
775 }
776}
777
778impl TorClientConfig {
779 #[rustfmt::skip]
781 pub fn dir_mgr_config(&self) -> Result<dir::DirMgrConfig, ConfigBuildError> {
782 Ok(dir::DirMgrConfig {
783 network: self.tor_network .clone(),
784 schedule: self.download_schedule .clone(),
785 tolerance: self.directory_tolerance.clone(),
786 cache_dir: self.storage.expand_cache_dir(&self.path_resolver)?,
787 cache_trust: self.storage.permissions.clone(),
788 override_net_params: self.override_net_params.clone(),
789 extensions: Default::default(),
790 })
791 }
792
793 pub fn fs_mistrust(&self) -> &Mistrust {
809 self.storage.permissions()
810 }
811
812 pub fn keystore(&self) -> ArtiKeystoreConfig {
814 self.storage.keystore()
815 }
816
817 pub(crate) fn state_dir(&self) -> StdResult<(PathBuf, &fs_mistrust::Mistrust), ErrorDetail> {
820 let state_dir = self
821 .storage
822 .expand_state_dir(&self.path_resolver)
823 .map_err(ErrorDetail::Configuration)?;
824 let mistrust = self.storage.permissions();
825
826 Ok((state_dir, mistrust))
827 }
828
829 #[cfg(feature = "testing")]
834 pub fn system_memory(&self) -> &tor_memquota::Config {
835 &self.system.memory
836 }
837}
838
839impl TorClientConfigBuilder {
840 pub fn from_directories<P, Q>(state_dir: P, cache_dir: Q) -> Self
845 where
846 P: AsRef<Path>,
847 Q: AsRef<Path>,
848 {
849 let mut builder = Self::default();
850
851 builder
852 .storage()
853 .cache_dir(CfgPath::new_literal(cache_dir.as_ref()))
854 .state_dir(CfgPath::new_literal(state_dir.as_ref()));
855
856 builder
857 }
858
859 pub fn override_net_params(&mut self) -> &mut HashMap<String, i32> {
865 &mut self.override_net_params
866 }
867}
868
869pub fn default_config_files() -> Result<Vec<ConfigurationSource>, CfgPathError> {
871 let path_resolver = tor_config_path::arti_client_base_resolver();
873
874 ["${ARTI_CONFIG}/arti.toml", "${ARTI_CONFIG}/arti.d/"]
875 .into_iter()
876 .map(|f| {
877 let path = CfgPath::new(f.into()).path(&path_resolver)?;
878 Ok(ConfigurationSource::from_path(path))
879 })
880 .collect()
881}
882
883#[deprecated = "use tor-config::mistrust::ARTI_FS_DISABLE_PERMISSION_CHECKS instead"]
885pub const FS_PERMISSIONS_CHECKS_DISABLE_VAR: &str = "ARTI_FS_DISABLE_PERMISSION_CHECKS";
886
887#[deprecated(since = "0.5.0")]
894#[allow(deprecated)]
895pub fn fs_permissions_checks_disabled_via_env() -> bool {
896 std::env::var_os(FS_PERMISSIONS_CHECKS_DISABLE_VAR).is_some()
897}
898
899#[cfg(test)]
900mod test {
901 #![allow(clippy::bool_assert_comparison)]
903 #![allow(clippy::clone_on_copy)]
904 #![allow(clippy::dbg_macro)]
905 #![allow(clippy::mixed_attributes_style)]
906 #![allow(clippy::print_stderr)]
907 #![allow(clippy::print_stdout)]
908 #![allow(clippy::single_char_pattern)]
909 #![allow(clippy::unwrap_used)]
910 #![allow(clippy::unchecked_time_subtraction)]
911 #![allow(clippy::useless_vec)]
912 #![allow(clippy::needless_pass_by_value)]
913 use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
915
916 use super::*;
917
918 #[test]
919 fn defaults() {
920 let dflt = TorClientConfig::default();
921 let b2 = TorClientConfigBuilder::default();
922 let dflt2 = b2.build().unwrap();
923 assert_eq!(&dflt, &dflt2);
924 }
925
926 #[test]
927 fn builder() {
928 let sec = std::time::Duration::from_secs(1);
929
930 let mut authorities = dir::AuthorityContacts::builder();
931 authorities.v3idents().push([22; 20].into());
932 authorities.v3idents().push([44; 20].into());
933 authorities.uploads().push(vec![
934 SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 80)),
935 SocketAddr::V6(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 80, 0, 0)),
936 ]);
937
938 let mut fallback = dir::FallbackDir::builder();
939 fallback
940 .rsa_identity([23; 20].into())
941 .ed_identity([99; 32].into())
942 .orports()
943 .push("127.0.0.7:7".parse().unwrap());
944
945 let mut bld = TorClientConfig::builder();
946 *bld.tor_network().authorities() = authorities;
947 bld.tor_network().set_fallback_caches(vec![fallback]);
948 bld.storage()
949 .cache_dir(CfgPath::new("/var/tmp/foo".to_owned()))
950 .state_dir(CfgPath::new("/var/tmp/bar".to_owned()));
951 bld.download_schedule().retry_certs().attempts(10);
952 bld.download_schedule().retry_certs().initial_delay(sec);
953 bld.download_schedule().retry_certs().parallelism(3);
954 bld.download_schedule().retry_microdescs().attempts(30);
955 bld.download_schedule()
956 .retry_microdescs()
957 .initial_delay(10 * sec);
958 bld.download_schedule().retry_microdescs().parallelism(9);
959 bld.override_net_params()
960 .insert("wombats-per-quokka".to_owned(), 7);
961 bld.path_rules()
962 .ipv4_subnet_family_prefix(20)
963 .ipv6_subnet_family_prefix(48);
964 bld.circuit_timing()
965 .max_dirtiness(90 * sec)
966 .request_timeout(10 * sec)
967 .request_max_retries(22)
968 .request_loyalty(3600 * sec);
969 bld.address_filter().allow_local_addrs(true);
970
971 let val = bld.build().unwrap();
972
973 assert_ne!(val, TorClientConfig::default());
974 }
975
976 #[test]
977 fn bridges_supported() {
978 fn chk(exp: Result<usize, ()>, s: &str) {
981 eprintln!("----------\n{s}\n----------\n");
982 let got = (|| {
983 let cfg: toml::Value = toml::from_str(s).unwrap();
984 let cfg: TorClientConfigBuilder = cfg.try_into()?;
985 let cfg = cfg.build()?;
986 let n_bridges = cfg.bridges.bridges.len();
987 Ok::<_, anyhow::Error>(n_bridges) })()
989 .map_err(|_| ());
990 assert_eq!(got, exp);
991 }
992
993 let chk_enabled_or_auto = |exp, bridges_toml| {
994 for enabled in [r#""#, r#"enabled = true"#, r#"enabled = "auto""#] {
995 chk(exp, &format!("[bridges]\n{}\n{}", enabled, bridges_toml));
996 }
997 };
998
999 let ok_1_if = |b: bool| b.then_some(1).ok_or(());
1000
1001 chk(
1002 Err(()),
1003 r#"
1004 [bridges]
1005 enabled = true
1006 "#,
1007 );
1008
1009 chk_enabled_or_auto(
1010 ok_1_if(cfg!(feature = "bridge-client")),
1011 r#"
1012 bridges = ["192.0.2.83:80 $0bac39417268b96b9f514ef763fa6fba1a788956"]
1013 "#,
1014 );
1015
1016 chk_enabled_or_auto(
1017 ok_1_if(cfg!(feature = "pt-client")),
1018 r#"
1019 bridges = ["obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1"]
1020 [[bridges.transports]]
1021 protocols = ["obfs4"]
1022 path = "obfs4proxy"
1023 "#,
1024 );
1025 }
1026
1027 #[test]
1028 fn check_default() {
1029 let dflt = default_config_files().unwrap();
1033 assert!(dflt[0].as_path().unwrap().ends_with("arti.toml"));
1034 assert!(dflt[1].as_path().unwrap().ends_with("arti.d"));
1035 assert_eq!(dflt.len(), 2);
1036 }
1037
1038 #[test]
1039 #[cfg(not(all(
1040 feature = "vanguards",
1041 any(feature = "onion-service-client", feature = "onion-service-service"),
1042 )))]
1043 fn check_disabled_vanguards_static() {
1044 #[allow(clippy::borrowed_box)]
1046 let _: &Box<VanguardConfig> = LazyLock::force(&DISABLED_VANGUARDS);
1047 }
1048
1049 #[test]
1050 #[cfg(feature = "pt-client")]
1051 fn check_bridge_pt() {
1052 let from_toml = |s: &str| -> TorClientConfigBuilder {
1053 let cfg: toml::Value = toml::from_str(dbg!(s)).unwrap();
1054 let cfg: TorClientConfigBuilder = cfg.try_into().unwrap();
1055 cfg
1056 };
1057
1058 let chk = |cfg: &TorClientConfigBuilder, expected: Result<(), &str>| match (
1059 cfg.build(),
1060 expected,
1061 ) {
1062 (Ok(_), Ok(())) => {}
1063 (Err(e), Err(ex)) => {
1064 if !e.to_string().contains(ex) {
1065 panic!("\"{e}\" did not contain {ex}");
1066 }
1067 }
1068 (Ok(_), Err(ex)) => {
1069 panic!("Expected {ex} but cfg succeeded");
1070 }
1071 (Err(e), Ok(())) => {
1072 panic!("Expected success but got error {e}")
1073 }
1074 };
1075
1076 let test_cases = [
1077 ("# No bridges", Ok(())),
1078 (
1079 r#"
1080 # No bridges but we still enabled bridges
1081 [bridges]
1082 enabled = true
1083 bridges = []
1084 "#,
1085 Err("bridges.enabled=true, but no bridges defined"),
1086 ),
1087 (
1088 r#"
1089 # One non-PT bridge
1090 [bridges]
1091 enabled = true
1092 bridges = [
1093 "192.0.2.83:80 $0bac39417268b96b9f514ef763fa6fba1a788956",
1094 ]
1095 "#,
1096 Ok(()),
1097 ),
1098 (
1099 r#"
1100 # One obfs4 bridge
1101 [bridges]
1102 enabled = true
1103 bridges = [
1104 "obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1",
1105 ]
1106 [[bridges.transports]]
1107 protocols = ["obfs4"]
1108 path = "obfs4proxy"
1109 "#,
1110 Ok(()),
1111 ),
1112 (
1113 r#"
1114 # One obfs4 bridge with unmanaged transport.
1115 [bridges]
1116 enabled = true
1117 bridges = [
1118 "obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1",
1119 ]
1120 [[bridges.transports]]
1121 protocols = ["obfs4"]
1122 proxy_addr = "127.0.0.1:31337"
1123 "#,
1124 Ok(()),
1125 ),
1126 (
1127 r#"
1128 # Transport is both managed and unmanaged.
1129 [[bridges.transports]]
1130 protocols = ["obfs4"]
1131 path = "obfsproxy"
1132 proxy_addr = "127.0.0.1:9999"
1133 "#,
1134 Err("Cannot provide both path and proxy_addr"),
1135 ),
1136 (
1137 r#"
1138 # One obfs4 bridge and non-PT bridge
1139 [bridges]
1140 enabled = false
1141 bridges = [
1142 "192.0.2.83:80 $0bac39417268b96b9f514ef763fa6fba1a788956",
1143 "obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1",
1144 ]
1145 [[bridges.transports]]
1146 protocols = ["obfs4"]
1147 path = "obfs4proxy"
1148 "#,
1149 Ok(()),
1150 ),
1151 (
1152 r#"
1153 # One obfs4 and non-PT bridge with no transport
1154 [bridges]
1155 enabled = true
1156 bridges = [
1157 "192.0.2.83:80 $0bac39417268b96b9f514ef763fa6fba1a788956",
1158 "obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1",
1159 ]
1160 "#,
1161 Ok(()),
1162 ),
1163 (
1164 r#"
1165 # One obfs4 bridge with no transport
1166 [bridges]
1167 enabled = true
1168 bridges = [
1169 "obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1",
1170 ]
1171 "#,
1172 Err("all bridges unusable due to lack of corresponding pluggable transport"),
1173 ),
1174 (
1175 r#"
1176 # One obfs4 bridge with no transport but bridges are disabled
1177 [bridges]
1178 enabled = false
1179 bridges = [
1180 "obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1",
1181 ]
1182 "#,
1183 Ok(()),
1184 ),
1185 (
1186 r#"
1187 # One non-PT bridge with a redundant transports section
1188 [bridges]
1189 enabled = false
1190 bridges = [
1191 "192.0.2.83:80 $0bac39417268b96b9f514ef763fa6fba1a788956",
1192 ]
1193 [[bridges.transports]]
1194 protocols = ["obfs4"]
1195 path = "obfs4proxy"
1196 "#,
1197 Ok(()),
1198 ),
1199 ];
1200
1201 for (test_case, expected) in test_cases.iter() {
1202 chk(&from_toml(test_case), *expected);
1203 }
1204 }
1205}