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 #[deftly(tor_config(default))]
121 pub(crate) allow_resolving_local_addrs: bool,
122
123 #[cfg(feature = "onion-service-client")]
129 #[deftly(tor_config(default = "true"))]
130 pub(crate) allow_onion_addrs: bool,
131}
132
133#[derive(Debug, Clone, Deftly, Eq, PartialEq)]
142#[derive_deftly(TorConfig)]
143#[non_exhaustive]
144pub struct StreamTimeoutConfig {
145 #[deftly(tor_config(default = "default_connect_timeout()"))]
148 pub(crate) connect_timeout: Duration,
149
150 #[deftly(tor_config(default = "default_dns_resolve_timeout()"))]
152 pub(crate) resolve_timeout: Duration,
153
154 #[deftly(tor_config(default = "default_dns_resolve_ptr_timeout()"))]
157 pub(crate) resolve_ptr_timeout: Duration,
158}
159
160fn default_connect_timeout() -> Duration {
162 Duration::new(10, 0)
163}
164
165fn default_dns_resolve_timeout() -> Duration {
167 Duration::new(10, 0)
168}
169
170fn default_dns_resolve_ptr_timeout() -> Duration {
172 Duration::new(10, 0)
173}
174
175#[derive(Debug, Clone, Deftly, Eq, PartialEq)]
191#[derive_deftly(TorConfig)]
192pub struct StorageConfig {
193 #[deftly(tor_config(default = "default_cache_dir()", setter(into)))]
209 pub(crate) cache_dir: CfgPath,
210
211 #[deftly(tor_config(default = "default_state_dir()", setter(into)))]
214 state_dir: CfgPath,
215
216 #[cfg(feature = "keymgr")]
220 #[deftly(tor_config(sub_builder))]
221 keystore: ArtiKeystoreConfig,
222
223 #[deftly(tor_config(
225 sub_builder(build_fn = "build_for_arti"),
226 extend_with = "extend_with_replace"
227 ))]
228 permissions: Mistrust,
229}
230
231fn default_cache_dir() -> CfgPath {
233 CfgPath::new("${ARTI_CACHE}".to_owned())
234}
235
236fn default_state_dir() -> CfgPath {
238 CfgPath::new("${ARTI_LOCAL_DATA}".to_owned())
239}
240
241macro_rules! expand_dir {
244 ($self:ident, $dirname:ident, $dircfg:ident) => {
245 $self
246 .$dirname
247 .path($dircfg)
248 .map_err(|e| ConfigBuildError::Invalid {
249 field: stringify!($dirname).to_owned(),
250 problem: e.to_string(),
251 })
252 };
253}
254
255impl StorageConfig {
256 pub(crate) fn expand_state_dir(
258 &self,
259 path_resolver: &CfgPathResolver,
260 ) -> Result<PathBuf, ConfigBuildError> {
261 expand_dir!(self, state_dir, path_resolver)
262 }
263 pub(crate) fn expand_cache_dir(
265 &self,
266 path_resolver: &CfgPathResolver,
267 ) -> Result<PathBuf, ConfigBuildError> {
268 expand_dir!(self, cache_dir, path_resolver)
269 }
270 #[allow(clippy::unnecessary_wraps)]
272 pub(crate) fn keystore(&self) -> ArtiKeystoreConfig {
273 cfg_if::cfg_if! {
274 if #[cfg(feature="keymgr")] {
275 self.keystore.clone()
276 } else {
277 Default::default()
278 }
279 }
280 }
281 pub(crate) fn permissions(&self) -> &Mistrust {
283 &self.permissions
284 }
285}
286
287#[derive(Debug, Clone, Deftly, Eq, PartialEq)]
360#[derive_deftly(TorConfig)]
361#[deftly(tor_config(pre_build = "validate_bridges_config", attr = "non_exhaustive"))]
362#[non_exhaustive]
363pub struct BridgesConfig {
364 #[deftly(tor_config(default))]
371 pub(crate) enabled: BoolOrAuto,
372
373 #[deftly(tor_config(no_magic, sub_builder, setter(skip)))]
378 bridges: BridgeList,
379
380 #[cfg(feature = "pt-client")] #[deftly(tor_config(
383 list(element(build), listtype = "TransportConfigList"),
384 default = "vec![]"
385 ))]
386 pub(crate) transports: Vec<pt::TransportConfig>,
387}
388
389#[cfg(feature = "pt-client")]
390fn validate_pt_config(bridges: &BridgesConfigBuilder) -> Result<(), ConfigBuildError> {
394 use std::collections::HashSet;
395 use std::str::FromStr;
396
397 let mut protocols_defined: HashSet<PtTransportName> = HashSet::new();
399 if let Some(transportlist) = bridges.opt_transports() {
400 for protocols in transportlist.iter() {
401 for protocol in protocols.get_protocols() {
402 protocols_defined.insert(protocol.clone());
403 }
404 }
405 }
406
407 for maybe_protocol in bridges
410 .bridges
411 .bridges
412 .as_deref()
413 .unwrap_or_default()
414 .iter()
415 {
416 match maybe_protocol.get_transport() {
417 Some(raw_protocol) => {
418 let protocol = TransportId::from_str(raw_protocol)
421 .unwrap_or_default()
424 .into_pluggable();
425 match protocol {
427 Some(protocol_required) => {
428 if protocols_defined.contains(&protocol_required) {
429 return Ok(());
430 }
431 }
432 None => return Ok(()),
433 }
434 }
435 None => {
436 return Ok(());
437 }
438 }
439 }
440
441 Err(ConfigBuildError::Inconsistent {
442 fields: ["bridges.bridges", "bridges.transports"].map(Into::into).into_iter().collect(),
443 problem: "Bridges configured, but all bridges unusable due to lack of corresponding pluggable transport in `[bridges.transports]`".into(),
444 })
445}
446
447#[allow(clippy::unnecessary_wraps)]
449fn validate_bridges_config(bridges: &BridgesConfigBuilder) -> Result<(), ConfigBuildError> {
450 let _ = bridges; use BoolOrAuto as BoA;
453
454 match (
461 bridges.enabled.unwrap_or_default(),
462 bridges.bridges.bridges.as_deref().unwrap_or_default(),
463 ) {
464 (BoA::Auto, _) | (BoA::Explicit(false), _) | (BoA::Explicit(true), [_, ..]) => {}
465 (BoA::Explicit(true), []) => {
466 return Err(ConfigBuildError::Inconsistent {
467 fields: ["enabled", "bridges"].map(Into::into).into_iter().collect(),
468 problem: "bridges.enabled=true, but no bridges defined".into(),
469 });
470 }
471 }
472 #[cfg(feature = "pt-client")]
473 {
474 if bridges_enabled(
475 bridges.enabled.unwrap_or_default(),
476 bridges.bridges.bridges.as_deref().unwrap_or_default(),
477 ) {
478 validate_pt_config(bridges)?;
479 }
480 }
481
482 Ok(())
483}
484
485fn bridges_enabled(enabled: BoolOrAuto, bridges: &[impl Sized]) -> bool {
487 #[cfg(feature = "bridge-client")]
488 {
489 enabled.as_bool().unwrap_or(!bridges.is_empty())
490 }
491
492 #[cfg(not(feature = "bridge-client"))]
493 {
494 let _ = (enabled, bridges);
495 false
496 }
497}
498
499impl BridgesConfig {
500 fn bridges_enabled(&self) -> bool {
502 bridges_enabled(self.enabled, &self.bridges)
503 }
504}
505
506pub type BridgeList = Vec<BridgeConfig>;
511
512define_list_builder_helper! {
513 struct BridgeListBuilder {
514 bridges: [BridgeConfigBuilder],
515 }
516 built: BridgeList = bridges;
517 default = vec![];
518 #[serde(try_from="MultilineListBuilder<BridgeConfigBuilder>")]
519 #[serde(into="MultilineListBuilder<BridgeConfigBuilder>")]
520}
521
522convert_helper_via_multi_line_list_builder! {
523 struct BridgeListBuilder {
524 bridges: [BridgeConfigBuilder],
525 }
526}
527
528#[cfg(feature = "bridge-client")]
529define_list_builder_accessors! {
530 struct BridgesConfigBuilder {
531 pub bridges: [BridgeConfigBuilder],
532 }
533}
534
535#[derive(Clone, Deftly, Debug, AsRef, educe::Educe)]
553#[educe(PartialEq, Eq)]
554#[derive_deftly(TorConfig)]
555#[non_exhaustive]
556pub struct TorClientConfig {
557 #[deftly(tor_config(sub_builder))]
559 tor_network: dir::NetworkConfig,
560
561 #[deftly(tor_config(sub_builder))]
563 pub(crate) storage: StorageConfig,
564
565 #[deftly(tor_config(sub_builder))]
567 download_schedule: dir::DownloadScheduleConfig,
568
569 #[deftly(tor_config(sub_builder))]
576 directory_tolerance: dir::DirTolerance,
577
578 #[deftly(tor_config(
581 setter(skip), field(ty = "HashMap<String, i32>"),
583 build = "|this: &Self| default_extend(this.override_net_params.clone())",
584 extend_with = "extend_with_replace"
585 ))]
586 pub(crate) override_net_params: tor_netdoc::doc::netstatus::NetParams<i32>,
587
588 #[deftly(tor_config(sub_builder))]
590 pub(crate) bridges: BridgesConfig,
591
592 #[deftly(tor_config(sub_builder))]
594 pub(crate) channel: ChannelConfig,
595
596 #[deftly(tor_config(sub_builder))]
602 pub(crate) system: SystemConfig,
603
604 #[as_ref]
606 #[deftly(tor_config(sub_builder))]
607 path_rules: circ::PathConfig,
608
609 #[as_ref]
611 #[deftly(tor_config(sub_builder))]
612 preemptive_circuits: circ::PreemptiveCircuitConfig,
613
614 #[as_ref]
616 #[deftly(tor_config(sub_builder))]
617 circuit_timing: circ::CircuitTiming,
618
619 #[deftly(tor_config(sub_builder))]
621 pub(crate) address_filter: ClientAddrConfig,
622
623 #[deftly(tor_config(sub_builder))]
625 pub(crate) stream_timeouts: StreamTimeoutConfig,
626
627 #[deftly(tor_config(sub_builder))]
631 pub(crate) vanguards: vanguards::VanguardConfig,
632
633 #[as_ref]
641 #[deftly(tor_config(skip, build = "|_| tor_config_path::arti_client_base_resolver()"))]
642 #[educe(PartialEq(ignore), Eq(ignore))]
643 pub(crate) path_resolver: CfgPathResolver,
644}
645
646impl tor_config::load::TopLevel for TorClientConfig {
647 type Builder = TorClientConfigBuilder;
648}
649
650fn default_extend<T: Default + Extend<X>, X>(to_add: impl IntoIterator<Item = X>) -> T {
652 let mut collection = T::default();
653 collection.extend(to_add);
654 collection
655}
656
657#[derive(Debug, Clone, Deftly, Eq, PartialEq)]
664#[derive_deftly(TorConfig)]
665#[non_exhaustive]
666pub struct SystemConfig {
667 #[deftly(tor_config(sub_builder))]
669 pub(crate) memory: tor_memquota::Config,
670}
671
672impl AsRef<tor_guardmgr::VanguardConfig> for TorClientConfig {
673 fn as_ref(&self) -> &tor_guardmgr::VanguardConfig {
674 cfg_if::cfg_if! {
675 if #[cfg(all(
676 feature = "vanguards",
677 any(feature = "onion-service-client", feature = "onion-service-service"),
678 ))]
679 {
680 &self.vanguards
681 } else {
682 &DISABLED_VANGUARDS
683 }
684 }
685 }
686}
687
688impl tor_circmgr::CircMgrConfig for TorClientConfig {}
689
690#[cfg(feature = "onion-service-client")]
691impl tor_hsclient::HsClientConnectorConfig for TorClientConfig {}
692
693#[cfg(any(feature = "onion-service-client", feature = "onion-service-service"))]
694impl tor_circmgr::hspool::HsCircPoolConfig for TorClientConfig {
695 #[cfg(all(
696 feature = "vanguards",
697 any(feature = "onion-service-client", feature = "onion-service-service")
698 ))]
699 fn vanguard_config(&self) -> &tor_guardmgr::VanguardConfig {
700 &self.vanguards
701 }
702}
703
704impl AsRef<tor_dircommon::fallback::FallbackList> for TorClientConfig {
705 fn as_ref(&self) -> &tor_dircommon::fallback::FallbackList {
706 self.tor_network.fallback_caches()
707 }
708}
709impl AsRef<[BridgeConfig]> for TorClientConfig {
710 fn as_ref(&self) -> &[BridgeConfig] {
711 #[cfg(feature = "bridge-client")]
712 {
713 &self.bridges.bridges
714 }
715
716 #[cfg(not(feature = "bridge-client"))]
717 {
718 &[]
719 }
720 }
721}
722impl AsRef<BridgesConfig> for TorClientConfig {
723 fn as_ref(&self) -> &BridgesConfig {
724 &self.bridges
725 }
726}
727impl tor_guardmgr::GuardMgrConfig for TorClientConfig {
728 fn bridges_enabled(&self) -> bool {
729 self.bridges.bridges_enabled()
730 }
731}
732
733impl TorClientConfig {
734 #[rustfmt::skip]
736 pub fn dir_mgr_config(&self) -> Result<dir::DirMgrConfig, ConfigBuildError> {
737 Ok(dir::DirMgrConfig {
738 network: self.tor_network .clone(),
739 schedule: self.download_schedule .clone(),
740 tolerance: self.directory_tolerance.clone(),
741 cache_dir: self.storage.expand_cache_dir(&self.path_resolver)?,
742 cache_trust: self.storage.permissions.clone(),
743 override_net_params: self.override_net_params.clone(),
744 extensions: Default::default(),
745 })
746 }
747
748 pub fn fs_mistrust(&self) -> &Mistrust {
764 self.storage.permissions()
765 }
766
767 pub fn keystore(&self) -> ArtiKeystoreConfig {
769 self.storage.keystore()
770 }
771
772 pub(crate) fn state_dir(&self) -> StdResult<(PathBuf, &fs_mistrust::Mistrust), ErrorDetail> {
775 let state_dir = self
776 .storage
777 .expand_state_dir(&self.path_resolver)
778 .map_err(ErrorDetail::Configuration)?;
779 let mistrust = self.storage.permissions();
780
781 Ok((state_dir, mistrust))
782 }
783
784 #[cfg(feature = "testing")]
789 pub fn system_memory(&self) -> &tor_memquota::Config {
790 &self.system.memory
791 }
792}
793
794impl TorClientConfigBuilder {
795 pub fn from_directories<P, Q>(state_dir: P, cache_dir: Q) -> Self
800 where
801 P: AsRef<Path>,
802 Q: AsRef<Path>,
803 {
804 let mut builder = Self::default();
805
806 builder
807 .storage()
808 .cache_dir(CfgPath::new_literal(cache_dir.as_ref()))
809 .state_dir(CfgPath::new_literal(state_dir.as_ref()));
810
811 builder
812 }
813
814 pub fn override_net_params(&mut self) -> &mut HashMap<String, i32> {
820 &mut self.override_net_params
821 }
822}
823
824pub fn default_config_files() -> Result<Vec<ConfigurationSource>, CfgPathError> {
826 let path_resolver = tor_config_path::arti_client_base_resolver();
828
829 ["${ARTI_CONFIG}/arti.toml", "${ARTI_CONFIG}/arti.d/"]
830 .into_iter()
831 .map(|f| {
832 let path = CfgPath::new(f.into()).path(&path_resolver)?;
833 Ok(ConfigurationSource::from_path(path))
834 })
835 .collect()
836}
837
838#[deprecated = "use tor-config::mistrust::ARTI_FS_DISABLE_PERMISSION_CHECKS instead"]
840pub const FS_PERMISSIONS_CHECKS_DISABLE_VAR: &str = "ARTI_FS_DISABLE_PERMISSION_CHECKS";
841
842#[deprecated(since = "0.5.0")]
849#[allow(deprecated)]
850pub fn fs_permissions_checks_disabled_via_env() -> bool {
851 std::env::var_os(FS_PERMISSIONS_CHECKS_DISABLE_VAR).is_some()
852}
853
854#[cfg(test)]
855mod test {
856 #![allow(clippy::bool_assert_comparison)]
858 #![allow(clippy::clone_on_copy)]
859 #![allow(clippy::dbg_macro)]
860 #![allow(clippy::mixed_attributes_style)]
861 #![allow(clippy::print_stderr)]
862 #![allow(clippy::print_stdout)]
863 #![allow(clippy::single_char_pattern)]
864 #![allow(clippy::unwrap_used)]
865 #![allow(clippy::unchecked_time_subtraction)]
866 #![allow(clippy::useless_vec)]
867 #![allow(clippy::needless_pass_by_value)]
868 #![allow(clippy::string_slice)] use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
871
872 use super::*;
873
874 #[test]
875 fn defaults() {
876 let dflt = TorClientConfig::default();
877 let b2 = TorClientConfigBuilder::default();
878 let dflt2 = b2.build().unwrap();
879 assert_eq!(&dflt, &dflt2);
880 }
881
882 #[test]
883 fn builder() {
884 let sec = std::time::Duration::from_secs(1);
885
886 let mut authorities = dir::AuthorityContacts::builder();
887 authorities.v3idents().push([22; 20].into());
888 authorities.v3idents().push([44; 20].into());
889 authorities.uploads().push(vec![
890 SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 80)),
891 SocketAddr::V6(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 80, 0, 0)),
892 ]);
893
894 let mut fallback = dir::FallbackDir::builder();
895 fallback
896 .rsa_identity([23; 20].into())
897 .ed_identity([99; 32].into())
898 .orports()
899 .push("127.0.0.7:7".parse().unwrap());
900
901 let mut bld = TorClientConfig::builder();
902 *bld.tor_network().authorities() = authorities;
903 bld.tor_network().set_fallback_caches(vec![fallback]);
904 bld.storage()
905 .cache_dir(CfgPath::new("/var/tmp/foo".to_owned()))
906 .state_dir(CfgPath::new("/var/tmp/bar".to_owned()));
907 bld.download_schedule().retry_certs().attempts(10);
908 bld.download_schedule().retry_certs().initial_delay(sec);
909 bld.download_schedule().retry_certs().parallelism(3);
910 bld.download_schedule().retry_microdescs().attempts(30);
911 bld.download_schedule()
912 .retry_microdescs()
913 .initial_delay(10 * sec);
914 bld.download_schedule().retry_microdescs().parallelism(9);
915 bld.override_net_params()
916 .insert("wombats-per-quokka".to_owned(), 7);
917 bld.path_rules()
918 .ipv4_subnet_family_prefix(20)
919 .ipv6_subnet_family_prefix(48);
920 bld.circuit_timing()
921 .max_dirtiness(90 * sec)
922 .request_timeout(10 * sec)
923 .request_max_retries(22)
924 .request_loyalty(3600 * sec);
925 bld.address_filter().allow_local_addrs(true);
926
927 let val = bld.build().unwrap();
928
929 assert_ne!(val, TorClientConfig::default());
930 }
931
932 #[test]
933 fn bridges_supported() {
934 fn chk(exp: Result<usize, ()>, s: &str) {
937 eprintln!("----------\n{s}\n----------\n");
938 let got = (|| {
939 let cfg: toml::Value = toml::from_str(s).unwrap();
940 let cfg: TorClientConfigBuilder = cfg.try_into()?;
941 let cfg = cfg.build()?;
942 let n_bridges = cfg.bridges.bridges.len();
943 Ok::<_, anyhow::Error>(n_bridges) })()
945 .map_err(|_| ());
946 assert_eq!(got, exp);
947 }
948
949 let chk_enabled_or_auto = |exp, bridges_toml| {
950 for enabled in [r#""#, r#"enabled = true"#, r#"enabled = "auto""#] {
951 chk(exp, &format!("[bridges]\n{}\n{}", enabled, bridges_toml));
952 }
953 };
954
955 let ok_1_if = |b: bool| b.then_some(1).ok_or(());
956
957 chk(
958 Err(()),
959 r#"
960 [bridges]
961 enabled = true
962 "#,
963 );
964
965 chk_enabled_or_auto(
966 ok_1_if(cfg!(feature = "bridge-client")),
967 r#"
968 bridges = ["192.0.2.83:80 $0bac39417268b96b9f514ef763fa6fba1a788956"]
969 "#,
970 );
971
972 chk_enabled_or_auto(
973 ok_1_if(cfg!(feature = "pt-client")),
974 r#"
975 bridges = ["obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1"]
976 [[bridges.transports]]
977 protocols = ["obfs4"]
978 path = "obfs4proxy"
979 "#,
980 );
981 }
982
983 #[test]
984 fn check_default() {
985 let dflt = default_config_files().unwrap();
989 assert!(dflt[0].as_path().unwrap().ends_with("arti.toml"));
990 assert!(dflt[1].as_path().unwrap().ends_with("arti.d"));
991 assert_eq!(dflt.len(), 2);
992 }
993
994 #[test]
995 #[cfg(not(all(
996 feature = "vanguards",
997 any(feature = "onion-service-client", feature = "onion-service-service"),
998 )))]
999 fn check_disabled_vanguards_static() {
1000 #[allow(clippy::borrowed_box)]
1002 let _: &Box<VanguardConfig> = LazyLock::force(&DISABLED_VANGUARDS);
1003 }
1004
1005 #[test]
1006 #[cfg(feature = "pt-client")]
1007 fn check_bridge_pt() {
1008 let from_toml = |s: &str| -> TorClientConfigBuilder {
1009 let cfg: toml::Value = toml::from_str(dbg!(s)).unwrap();
1010 let cfg: TorClientConfigBuilder = cfg.try_into().unwrap();
1011 cfg
1012 };
1013
1014 let chk = |cfg: &TorClientConfigBuilder, expected: Result<(), &str>| match (
1015 cfg.build(),
1016 expected,
1017 ) {
1018 (Ok(_), Ok(())) => {}
1019 (Err(e), Err(ex)) => {
1020 if !e.to_string().contains(ex) {
1021 panic!("\"{e}\" did not contain {ex}");
1022 }
1023 }
1024 (Ok(_), Err(ex)) => {
1025 panic!("Expected {ex} but cfg succeeded");
1026 }
1027 (Err(e), Ok(())) => {
1028 panic!("Expected success but got error {e}")
1029 }
1030 };
1031
1032 let test_cases = [
1033 ("# No bridges", Ok(())),
1034 (
1035 r#"
1036 # No bridges but we still enabled bridges
1037 [bridges]
1038 enabled = true
1039 bridges = []
1040 "#,
1041 Err("bridges.enabled=true, but no bridges defined"),
1042 ),
1043 (
1044 r#"
1045 # One non-PT bridge
1046 [bridges]
1047 enabled = true
1048 bridges = [
1049 "192.0.2.83:80 $0bac39417268b96b9f514ef763fa6fba1a788956",
1050 ]
1051 "#,
1052 Ok(()),
1053 ),
1054 (
1055 r#"
1056 # One obfs4 bridge
1057 [bridges]
1058 enabled = true
1059 bridges = [
1060 "obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1",
1061 ]
1062 [[bridges.transports]]
1063 protocols = ["obfs4"]
1064 path = "obfs4proxy"
1065 "#,
1066 Ok(()),
1067 ),
1068 (
1069 r#"
1070 # One obfs4 bridge with unmanaged transport.
1071 [bridges]
1072 enabled = true
1073 bridges = [
1074 "obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1",
1075 ]
1076 [[bridges.transports]]
1077 protocols = ["obfs4"]
1078 proxy_addr = "127.0.0.1:31337"
1079 "#,
1080 Ok(()),
1081 ),
1082 (
1083 r#"
1084 # Transport is both managed and unmanaged.
1085 [[bridges.transports]]
1086 protocols = ["obfs4"]
1087 path = "obfsproxy"
1088 proxy_addr = "127.0.0.1:9999"
1089 "#,
1090 Err("Cannot provide both path and proxy_addr"),
1091 ),
1092 (
1093 r#"
1094 # One obfs4 bridge and non-PT bridge
1095 [bridges]
1096 enabled = false
1097 bridges = [
1098 "192.0.2.83:80 $0bac39417268b96b9f514ef763fa6fba1a788956",
1099 "obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1",
1100 ]
1101 [[bridges.transports]]
1102 protocols = ["obfs4"]
1103 path = "obfs4proxy"
1104 "#,
1105 Ok(()),
1106 ),
1107 (
1108 r#"
1109 # One obfs4 and non-PT bridge with no transport
1110 [bridges]
1111 enabled = true
1112 bridges = [
1113 "192.0.2.83:80 $0bac39417268b96b9f514ef763fa6fba1a788956",
1114 "obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1",
1115 ]
1116 "#,
1117 Ok(()),
1118 ),
1119 (
1120 r#"
1121 # One obfs4 bridge with no transport
1122 [bridges]
1123 enabled = true
1124 bridges = [
1125 "obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1",
1126 ]
1127 "#,
1128 Err("all bridges unusable due to lack of corresponding pluggable transport"),
1129 ),
1130 (
1131 r#"
1132 # One obfs4 bridge with no transport but bridges are disabled
1133 [bridges]
1134 enabled = false
1135 bridges = [
1136 "obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1",
1137 ]
1138 "#,
1139 Ok(()),
1140 ),
1141 (
1142 r#"
1143 # One non-PT bridge with a redundant transports section
1144 [bridges]
1145 enabled = false
1146 bridges = [
1147 "192.0.2.83:80 $0bac39417268b96b9f514ef763fa6fba1a788956",
1148 ]
1149 [[bridges.transports]]
1150 protocols = ["obfs4"]
1151 path = "obfs4proxy"
1152 "#,
1153 Ok(()),
1154 ),
1155 ];
1156
1157 for (test_case, expected) in test_cases.iter() {
1158 chk(&from_toml(test_case), *expected);
1159 }
1160 }
1161}