1use crate::err::ErrorDetail;
6use derive_builder::Builder;
7use derive_more::AsRef;
8use fs_mistrust::{Mistrust, MistrustBuilder};
9use serde::{Deserialize, Serialize};
10use std::collections::HashMap;
11use std::path::Path;
12use std::path::PathBuf;
13use std::result::Result as StdResult;
14use std::time::Duration;
15
16pub use tor_chanmgr::{ChannelConfig, ChannelConfigBuilder};
17pub use tor_config::convert_helper_via_multi_line_list_builder;
18pub use tor_config::impl_standard_builder;
19pub use tor_config::list_builder::{MultilineListBuilder, MultilineListBuilderError};
20pub use tor_config::mistrust::BuilderExt as _;
21pub use tor_config::{BoolOrAuto, ConfigError};
22pub use tor_config::{ConfigBuildError, ConfigurationSource, ConfigurationSources, Reconfigure};
23pub use tor_config::{define_list_builder_accessors, define_list_builder_helper};
24pub use tor_config_path::{CfgPath, CfgPathError, CfgPathResolver};
25pub use tor_linkspec::{ChannelMethod, HasChanMethod, PtTransportName, TransportId};
26
27pub use tor_guardmgr::bridge::BridgeConfigBuilder;
28
29#[cfg(feature = "bridge-client")]
30#[cfg_attr(docsrs, doc(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, Builder, Eq, PartialEq)]
107#[builder(build_fn(error = "ConfigBuildError"))]
108#[builder(derive(Debug, Serialize, Deserialize))]
109pub struct ClientAddrConfig {
110 #[builder(default)]
115 pub(crate) allow_local_addrs: bool,
116
117 #[cfg(feature = "onion-service-client")]
121 #[builder(default = "true")]
122 pub(crate) allow_onion_addrs: bool,
123}
124impl_standard_builder! { ClientAddrConfig }
125
126#[derive(Debug, Clone, Builder, Eq, PartialEq)]
135#[builder(build_fn(error = "ConfigBuildError"))]
136#[builder(derive(Debug, Serialize, Deserialize))]
137#[non_exhaustive]
138pub struct StreamTimeoutConfig {
139 #[builder(default = "default_connect_timeout()")]
142 #[builder_field_attr(serde(default, with = "humantime_serde::option"))]
143 pub(crate) connect_timeout: Duration,
144
145 #[builder(default = "default_dns_resolve_timeout()")]
147 #[builder_field_attr(serde(default, with = "humantime_serde::option"))]
148 pub(crate) resolve_timeout: Duration,
149
150 #[builder(default = "default_dns_resolve_ptr_timeout()")]
153 #[builder_field_attr(serde(default, with = "humantime_serde::option"))]
154 pub(crate) resolve_ptr_timeout: Duration,
155}
156impl_standard_builder! { StreamTimeoutConfig }
157
158fn default_connect_timeout() -> Duration {
160 Duration::new(10, 0)
161}
162
163fn default_dns_resolve_timeout() -> Duration {
165 Duration::new(10, 0)
166}
167
168fn default_dns_resolve_ptr_timeout() -> Duration {
170 Duration::new(10, 0)
171}
172
173#[derive(Debug, Clone, Builder, Eq, PartialEq)]
181#[builder(build_fn(error = "ConfigBuildError"))]
182#[builder(derive(Debug, Serialize, Deserialize))]
183pub struct SoftwareStatusOverrideConfig {
184 #[builder(field(type = "String", build = "self.parse_protos()?"))]
189 pub(crate) ignore_missing_required_protocols: tor_protover::Protocols,
190}
191
192impl SoftwareStatusOverrideConfigBuilder {
193 fn parse_protos(&self) -> Result<tor_protover::Protocols, ConfigBuildError> {
195 use std::str::FromStr as _;
196 tor_protover::Protocols::from_str(&self.ignore_missing_required_protocols).map_err(|e| {
197 ConfigBuildError::Invalid {
198 field: "ignore_missing_required_protocols".to_string(),
199 problem: e.to_string(),
200 }
201 })
202 }
203}
204
205#[derive(Debug, Clone, Builder, Eq, PartialEq)]
221#[builder(build_fn(error = "ConfigBuildError"))]
222#[builder(derive(Debug, Serialize, Deserialize))]
223#[non_exhaustive]
224pub struct StorageConfig {
225 #[builder(setter(into), default = "default_cache_dir()")]
241 cache_dir: CfgPath,
242
243 #[builder(setter(into), default = "default_state_dir()")]
246 state_dir: CfgPath,
247
248 #[cfg(feature = "keymgr")]
250 #[builder(sub_builder)]
251 #[builder_field_attr(serde(default))]
252 keystore: ArtiKeystoreConfig,
253
254 #[builder(sub_builder(fn_name = "build_for_arti"))]
256 #[builder_field_attr(serde(default))]
257 permissions: Mistrust,
258}
259impl_standard_builder! { StorageConfig }
260
261fn default_cache_dir() -> CfgPath {
263 CfgPath::new("${ARTI_CACHE}".to_owned())
264}
265
266fn default_state_dir() -> CfgPath {
268 CfgPath::new("${ARTI_LOCAL_DATA}".to_owned())
269}
270
271macro_rules! expand_dir {
274 ($self:ident, $dirname:ident, $dircfg:ident) => {
275 $self
276 .$dirname
277 .path($dircfg)
278 .map_err(|e| ConfigBuildError::Invalid {
279 field: stringify!($dirname).to_owned(),
280 problem: e.to_string(),
281 })
282 };
283}
284
285impl StorageConfig {
286 pub(crate) fn expand_state_dir(
288 &self,
289 path_resolver: &CfgPathResolver,
290 ) -> Result<PathBuf, ConfigBuildError> {
291 expand_dir!(self, state_dir, path_resolver)
292 }
293 pub(crate) fn expand_cache_dir(
295 &self,
296 path_resolver: &CfgPathResolver,
297 ) -> Result<PathBuf, ConfigBuildError> {
298 expand_dir!(self, cache_dir, path_resolver)
299 }
300 #[allow(clippy::unnecessary_wraps)]
302 pub(crate) fn keystore(&self) -> ArtiKeystoreConfig {
303 cfg_if::cfg_if! {
304 if #[cfg(feature="keymgr")] {
305 self.keystore.clone()
306 } else {
307 Default::default()
308 }
309 }
310 }
311 pub(crate) fn permissions(&self) -> &Mistrust {
313 &self.permissions
314 }
315}
316
317#[derive(Debug, Clone, Builder, Eq, PartialEq)]
390#[builder(build_fn(validate = "validate_bridges_config", error = "ConfigBuildError"))]
391#[builder(derive(Debug, Serialize, Deserialize))]
392#[non_exhaustive]
393#[builder_struct_attr(non_exhaustive)] pub struct BridgesConfig {
395 #[builder(default)]
402 pub(crate) enabled: BoolOrAuto,
403
404 #[builder(sub_builder, setter(custom))]
406 #[builder_field_attr(serde(default))]
407 bridges: BridgeList,
408
409 #[builder(sub_builder, setter(custom))]
411 #[builder_field_attr(serde(default))]
412 #[cfg(feature = "pt-client")]
413 pub(crate) transports: TransportConfigList,
414}
415
416#[cfg(feature = "pt-client")]
418type TransportConfigList = Vec<pt::TransportConfig>;
419
420#[cfg(feature = "pt-client")]
421define_list_builder_helper! {
422 pub struct TransportConfigListBuilder {
423 transports: [pt::TransportConfigBuilder],
424 }
425 built: TransportConfigList = transports;
426 default = vec![];
427}
428
429#[cfg(feature = "pt-client")]
430define_list_builder_accessors! {
431 struct BridgesConfigBuilder {
432 pub transports: [pt::TransportConfigBuilder],
433 }
434}
435
436impl_standard_builder! { BridgesConfig }
437
438#[cfg(feature = "pt-client")]
439fn validate_pt_config(bridges: &BridgesConfigBuilder) -> Result<(), ConfigBuildError> {
443 use std::collections::HashSet;
444 use std::str::FromStr;
445
446 let mut protocols_defined: HashSet<PtTransportName> = HashSet::new();
448 if let Some(transportlist) = bridges.opt_transports() {
449 for protocols in transportlist.iter() {
450 for protocol in protocols.get_protocols() {
451 protocols_defined.insert(protocol.clone());
452 }
453 }
454 }
455
456 for maybe_protocol in bridges
459 .bridges
460 .bridges
461 .as_deref()
462 .unwrap_or_default()
463 .iter()
464 {
465 match maybe_protocol.get_transport() {
466 Some(raw_protocol) => {
467 let protocol = TransportId::from_str(raw_protocol)
470 .unwrap_or_default()
473 .into_pluggable();
474 match protocol {
476 Some(protocol_required) => {
477 if protocols_defined.contains(&protocol_required) {
478 return Ok(());
479 }
480 }
481 None => return Ok(()),
482 }
483 }
484 None => {
485 return Ok(());
486 }
487 }
488 }
489
490 Err(ConfigBuildError::Inconsistent {
491 fields: ["bridges.bridges", "bridges.transports"].map(Into::into).into_iter().collect(),
492 problem: "Bridges configured, but all bridges unusable due to lack of corresponding pluggable transport in `[bridges.transports]`".into(),
493 })
494}
495
496#[allow(clippy::unnecessary_wraps)]
498fn validate_bridges_config(bridges: &BridgesConfigBuilder) -> Result<(), ConfigBuildError> {
499 let _ = bridges; use BoolOrAuto as BoA;
502
503 match (
510 bridges.enabled.unwrap_or_default(),
511 bridges.bridges.bridges.as_deref().unwrap_or_default(),
512 ) {
513 (BoA::Auto, _) | (BoA::Explicit(false), _) | (BoA::Explicit(true), [_, ..]) => {}
514 (BoA::Explicit(true), []) => {
515 return Err(ConfigBuildError::Inconsistent {
516 fields: ["enabled", "bridges"].map(Into::into).into_iter().collect(),
517 problem: "bridges.enabled=true, but no bridges defined".into(),
518 });
519 }
520 }
521 #[cfg(feature = "pt-client")]
522 {
523 if bridges_enabled(
524 bridges.enabled.unwrap_or_default(),
525 bridges.bridges.bridges.as_deref().unwrap_or_default(),
526 ) {
527 validate_pt_config(bridges)?;
528 }
529 }
530
531 Ok(())
532}
533
534fn bridges_enabled(enabled: BoolOrAuto, bridges: &[impl Sized]) -> bool {
536 #[cfg(feature = "bridge-client")]
537 {
538 enabled.as_bool().unwrap_or(!bridges.is_empty())
539 }
540
541 #[cfg(not(feature = "bridge-client"))]
542 {
543 let _ = (enabled, bridges);
544 false
545 }
546}
547
548impl BridgesConfig {
549 fn bridges_enabled(&self) -> bool {
551 bridges_enabled(self.enabled, &self.bridges)
552 }
553}
554
555pub type BridgeList = Vec<BridgeConfig>;
560
561define_list_builder_helper! {
562 struct BridgeListBuilder {
563 bridges: [BridgeConfigBuilder],
564 }
565 built: BridgeList = bridges;
566 default = vec![];
567 #[serde(try_from="MultilineListBuilder<BridgeConfigBuilder>")]
568 #[serde(into="MultilineListBuilder<BridgeConfigBuilder>")]
569}
570
571convert_helper_via_multi_line_list_builder! {
572 struct BridgeListBuilder {
573 bridges: [BridgeConfigBuilder],
574 }
575}
576
577#[cfg(feature = "bridge-client")]
578define_list_builder_accessors! {
579 struct BridgesConfigBuilder {
580 pub bridges: [BridgeConfigBuilder],
581 }
582}
583
584#[derive(Clone, Builder, Debug, AsRef, educe::Educe)]
602#[educe(PartialEq, Eq)]
603#[builder(build_fn(error = "ConfigBuildError"))]
604#[builder(derive(Serialize, Deserialize, Debug))]
605#[non_exhaustive]
606pub struct TorClientConfig {
607 #[builder(sub_builder)]
609 #[builder_field_attr(serde(default))]
610 tor_network: dir::NetworkConfig,
611
612 #[builder(sub_builder)]
614 #[builder_field_attr(serde(default))]
615 pub(crate) storage: StorageConfig,
616
617 #[builder(sub_builder)]
619 #[builder_field_attr(serde(default))]
620 download_schedule: dir::DownloadScheduleConfig,
621
622 #[builder(sub_builder)]
629 #[builder_field_attr(serde(default))]
630 directory_tolerance: dir::DirTolerance,
631
632 #[builder(
635 sub_builder,
636 field(
637 type = "HashMap<String, i32>",
638 build = "default_extend(self.override_net_params.clone())"
639 )
640 )]
641 #[builder_field_attr(serde(default))]
642 pub(crate) override_net_params: tor_netdoc::doc::netstatus::NetParams<i32>,
643
644 #[builder(sub_builder)]
646 #[builder_field_attr(serde(default))]
647 pub(crate) bridges: BridgesConfig,
648
649 #[builder(sub_builder)]
651 #[builder_field_attr(serde(default))]
652 pub(crate) channel: ChannelConfig,
653
654 #[builder(sub_builder)]
660 #[builder_field_attr(serde(default))]
661 pub(crate) system: SystemConfig,
662
663 #[as_ref]
665 #[builder(sub_builder)]
666 #[builder_field_attr(serde(default))]
667 path_rules: circ::PathConfig,
668
669 #[as_ref]
671 #[builder(sub_builder)]
672 #[builder_field_attr(serde(default))]
673 preemptive_circuits: circ::PreemptiveCircuitConfig,
674
675 #[as_ref]
677 #[builder(sub_builder)]
678 #[builder_field_attr(serde(default))]
679 circuit_timing: circ::CircuitTiming,
680
681 #[builder(sub_builder)]
683 #[builder_field_attr(serde(default))]
684 pub(crate) address_filter: ClientAddrConfig,
685
686 #[builder(sub_builder)]
688 #[builder_field_attr(serde(default))]
689 pub(crate) stream_timeouts: StreamTimeoutConfig,
690
691 #[builder(sub_builder)]
695 #[builder_field_attr(serde(default))]
696 pub(crate) vanguards: vanguards::VanguardConfig,
697
698 #[builder(sub_builder)]
700 #[builder_field_attr(serde(default))]
701 pub(crate) use_obsolete_software: SoftwareStatusOverrideConfig,
702
703 #[as_ref]
711 #[builder(setter(skip))]
712 #[builder_field_attr(serde(skip))]
713 #[educe(PartialEq(ignore), Eq(ignore))]
714 #[builder(default = "tor_config_path::arti_client_base_resolver()")]
715 pub(crate) path_resolver: CfgPathResolver,
716}
717impl_standard_builder! { TorClientConfig }
718
719impl tor_config::load::TopLevel for TorClientConfig {
720 type Builder = TorClientConfigBuilder;
721}
722
723fn default_extend<T: Default + Extend<X>, X>(to_add: impl IntoIterator<Item = X>) -> T {
725 let mut collection = T::default();
726 collection.extend(to_add);
727 collection
728}
729
730#[derive(Debug, Clone, Builder, Eq, PartialEq)]
737#[builder(build_fn(error = "ConfigBuildError"))]
738#[builder(derive(Debug, Serialize, Deserialize))]
739#[non_exhaustive]
740pub struct SystemConfig {
741 #[builder(sub_builder(fn_name = "build"))]
743 #[builder_field_attr(serde(default))]
744 pub(crate) memory: tor_memquota::Config,
745}
746impl_standard_builder! { SystemConfig }
747
748impl AsRef<tor_guardmgr::VanguardConfig> for TorClientConfig {
749 fn as_ref(&self) -> &tor_guardmgr::VanguardConfig {
750 cfg_if::cfg_if! {
751 if #[cfg(all(
752 feature = "vanguards",
753 any(feature = "onion-service-client", feature = "onion-service-service"),
754 ))]
755 {
756 &self.vanguards
757 } else {
758 &DISABLED_VANGUARDS
759 }
760 }
761 }
762}
763
764impl tor_circmgr::CircMgrConfig for TorClientConfig {}
765
766#[cfg(feature = "onion-service-client")]
767impl tor_hsclient::HsClientConnectorConfig for TorClientConfig {}
768
769#[cfg(any(feature = "onion-service-client", feature = "onion-service-service"))]
770impl tor_circmgr::hspool::HsCircPoolConfig for TorClientConfig {
771 #[cfg(all(
772 feature = "vanguards",
773 any(feature = "onion-service-client", feature = "onion-service-service")
774 ))]
775 fn vanguard_config(&self) -> &tor_guardmgr::VanguardConfig {
776 &self.vanguards
777 }
778}
779
780impl AsRef<tor_dircommon::fallback::FallbackList> for TorClientConfig {
781 fn as_ref(&self) -> &tor_dircommon::fallback::FallbackList {
782 self.tor_network.fallback_caches()
783 }
784}
785impl AsRef<[BridgeConfig]> for TorClientConfig {
786 fn as_ref(&self) -> &[BridgeConfig] {
787 #[cfg(feature = "bridge-client")]
788 {
789 &self.bridges.bridges
790 }
791
792 #[cfg(not(feature = "bridge-client"))]
793 {
794 &[]
795 }
796 }
797}
798impl AsRef<BridgesConfig> for TorClientConfig {
799 fn as_ref(&self) -> &BridgesConfig {
800 &self.bridges
801 }
802}
803impl tor_guardmgr::GuardMgrConfig for TorClientConfig {
804 fn bridges_enabled(&self) -> bool {
805 self.bridges.bridges_enabled()
806 }
807}
808
809impl TorClientConfig {
810 #[rustfmt::skip]
812 pub fn dir_mgr_config(&self) -> Result<dir::DirMgrConfig, ConfigBuildError> {
813 Ok(dir::DirMgrConfig {
814 network: self.tor_network .clone(),
815 schedule: self.download_schedule .clone(),
816 tolerance: self.directory_tolerance.clone(),
817 cache_dir: self.storage.expand_cache_dir(&self.path_resolver)?,
818 cache_trust: self.storage.permissions.clone(),
819 override_net_params: self.override_net_params.clone(),
820 extensions: Default::default(),
821 })
822 }
823
824 pub fn fs_mistrust(&self) -> &Mistrust {
840 self.storage.permissions()
841 }
842
843 pub fn keystore(&self) -> ArtiKeystoreConfig {
845 self.storage.keystore()
846 }
847
848 pub(crate) fn state_dir(&self) -> StdResult<(PathBuf, &fs_mistrust::Mistrust), ErrorDetail> {
851 let state_dir = self
852 .storage
853 .expand_state_dir(&self.path_resolver)
854 .map_err(ErrorDetail::Configuration)?;
855 let mistrust = self.storage.permissions();
856
857 Ok((state_dir, mistrust))
858 }
859
860 #[cfg(feature = "testing")]
865 pub fn system_memory(&self) -> &tor_memquota::Config {
866 &self.system.memory
867 }
868}
869
870impl TorClientConfigBuilder {
871 pub fn from_directories<P, Q>(state_dir: P, cache_dir: Q) -> Self
876 where
877 P: AsRef<Path>,
878 Q: AsRef<Path>,
879 {
880 let mut builder = Self::default();
881
882 builder
883 .storage()
884 .cache_dir(CfgPath::new_literal(cache_dir.as_ref()))
885 .state_dir(CfgPath::new_literal(state_dir.as_ref()));
886
887 builder
888 }
889}
890
891pub fn default_config_files() -> Result<Vec<ConfigurationSource>, CfgPathError> {
893 let path_resolver = tor_config_path::arti_client_base_resolver();
895
896 ["${ARTI_CONFIG}/arti.toml", "${ARTI_CONFIG}/arti.d/"]
897 .into_iter()
898 .map(|f| {
899 let path = CfgPath::new(f.into()).path(&path_resolver)?;
900 Ok(ConfigurationSource::from_path(path))
901 })
902 .collect()
903}
904
905#[deprecated = "use tor-config::mistrust::ARTI_FS_DISABLE_PERMISSION_CHECKS instead"]
907pub const FS_PERMISSIONS_CHECKS_DISABLE_VAR: &str = "ARTI_FS_DISABLE_PERMISSION_CHECKS";
908
909#[deprecated(since = "0.5.0")]
916#[allow(deprecated)]
917pub fn fs_permissions_checks_disabled_via_env() -> bool {
918 std::env::var_os(FS_PERMISSIONS_CHECKS_DISABLE_VAR).is_some()
919}
920
921#[cfg(test)]
922mod test {
923 #![allow(clippy::bool_assert_comparison)]
925 #![allow(clippy::clone_on_copy)]
926 #![allow(clippy::dbg_macro)]
927 #![allow(clippy::mixed_attributes_style)]
928 #![allow(clippy::print_stderr)]
929 #![allow(clippy::print_stdout)]
930 #![allow(clippy::single_char_pattern)]
931 #![allow(clippy::unwrap_used)]
932 #![allow(clippy::unchecked_time_subtraction)]
933 #![allow(clippy::useless_vec)]
934 #![allow(clippy::needless_pass_by_value)]
935 use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6};
937
938 use super::*;
939
940 #[test]
941 fn defaults() {
942 let dflt = TorClientConfig::default();
943 let b2 = TorClientConfigBuilder::default();
944 let dflt2 = b2.build().unwrap();
945 assert_eq!(&dflt, &dflt2);
946 }
947
948 #[test]
949 fn builder() {
950 let sec = std::time::Duration::from_secs(1);
951
952 let mut authorities = dir::AuthorityContacts::builder();
953 authorities.v3idents().push([22; 20].into());
954 authorities.v3idents().push([44; 20].into());
955 authorities.uploads().push(vec![
956 SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::LOCALHOST, 80)),
957 SocketAddr::V6(SocketAddrV6::new(Ipv6Addr::LOCALHOST, 80, 0, 0)),
958 ]);
959
960 let mut fallback = dir::FallbackDir::builder();
961 fallback
962 .rsa_identity([23; 20].into())
963 .ed_identity([99; 32].into())
964 .orports()
965 .push("127.0.0.7:7".parse().unwrap());
966
967 let mut bld = TorClientConfig::builder();
968 *bld.tor_network().authorities() = authorities;
969 bld.tor_network().set_fallback_caches(vec![fallback]);
970 bld.storage()
971 .cache_dir(CfgPath::new("/var/tmp/foo".to_owned()))
972 .state_dir(CfgPath::new("/var/tmp/bar".to_owned()));
973 bld.download_schedule().retry_certs().attempts(10);
974 bld.download_schedule().retry_certs().initial_delay(sec);
975 bld.download_schedule().retry_certs().parallelism(3);
976 bld.download_schedule().retry_microdescs().attempts(30);
977 bld.download_schedule()
978 .retry_microdescs()
979 .initial_delay(10 * sec);
980 bld.download_schedule().retry_microdescs().parallelism(9);
981 bld.override_net_params()
982 .insert("wombats-per-quokka".to_owned(), 7);
983 bld.path_rules()
984 .ipv4_subnet_family_prefix(20)
985 .ipv6_subnet_family_prefix(48);
986 bld.circuit_timing()
987 .max_dirtiness(90 * sec)
988 .request_timeout(10 * sec)
989 .request_max_retries(22)
990 .request_loyalty(3600 * sec);
991 bld.address_filter().allow_local_addrs(true);
992
993 let val = bld.build().unwrap();
994
995 assert_ne!(val, TorClientConfig::default());
996 }
997
998 #[test]
999 fn bridges_supported() {
1000 fn chk(exp: Result<usize, ()>, s: &str) {
1003 eprintln!("----------\n{s}\n----------\n");
1004 let got = (|| {
1005 let cfg: toml::Value = toml::from_str(s).unwrap();
1006 let cfg: TorClientConfigBuilder = cfg.try_into()?;
1007 let cfg = cfg.build()?;
1008 let n_bridges = cfg.bridges.bridges.len();
1009 Ok::<_, anyhow::Error>(n_bridges) })()
1011 .map_err(|_| ());
1012 assert_eq!(got, exp);
1013 }
1014
1015 let chk_enabled_or_auto = |exp, bridges_toml| {
1016 for enabled in [r#""#, r#"enabled = true"#, r#"enabled = "auto""#] {
1017 chk(exp, &format!("[bridges]\n{}\n{}", enabled, bridges_toml));
1018 }
1019 };
1020
1021 let ok_1_if = |b: bool| b.then_some(1).ok_or(());
1022
1023 chk(
1024 Err(()),
1025 r#"
1026 [bridges]
1027 enabled = true
1028 "#,
1029 );
1030
1031 chk_enabled_or_auto(
1032 ok_1_if(cfg!(feature = "bridge-client")),
1033 r#"
1034 bridges = ["192.0.2.83:80 $0bac39417268b96b9f514ef763fa6fba1a788956"]
1035 "#,
1036 );
1037
1038 chk_enabled_or_auto(
1039 ok_1_if(cfg!(feature = "pt-client")),
1040 r#"
1041 bridges = ["obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1"]
1042 [[bridges.transports]]
1043 protocols = ["obfs4"]
1044 path = "obfs4proxy"
1045 "#,
1046 );
1047 }
1048
1049 #[test]
1050 fn check_default() {
1051 let dflt = default_config_files().unwrap();
1055 assert!(dflt[0].as_path().unwrap().ends_with("arti.toml"));
1056 assert!(dflt[1].as_path().unwrap().ends_with("arti.d"));
1057 assert_eq!(dflt.len(), 2);
1058 }
1059
1060 #[test]
1061 #[cfg(not(all(
1062 feature = "vanguards",
1063 any(feature = "onion-service-client", feature = "onion-service-service"),
1064 )))]
1065 fn check_disabled_vanguards_static() {
1066 #[allow(clippy::borrowed_box)]
1068 let _: &Box<VanguardConfig> = LazyLock::force(&DISABLED_VANGUARDS);
1069 }
1070
1071 #[test]
1072 #[cfg(feature = "pt-client")]
1073 fn check_bridge_pt() {
1074 let from_toml = |s: &str| -> TorClientConfigBuilder {
1075 let cfg: toml::Value = toml::from_str(dbg!(s)).unwrap();
1076 let cfg: TorClientConfigBuilder = cfg.try_into().unwrap();
1077 cfg
1078 };
1079
1080 let chk = |cfg: &TorClientConfigBuilder, expected: Result<(), &str>| match (
1081 cfg.build(),
1082 expected,
1083 ) {
1084 (Ok(_), Ok(())) => {}
1085 (Err(e), Err(ex)) => {
1086 if !e.to_string().contains(ex) {
1087 panic!("\"{e}\" did not contain {ex}");
1088 }
1089 }
1090 (Ok(_), Err(ex)) => {
1091 panic!("Expected {ex} but cfg succeeded");
1092 }
1093 (Err(e), Ok(())) => {
1094 panic!("Expected success but got error {e}")
1095 }
1096 };
1097
1098 let test_cases = [
1099 ("# No bridges", Ok(())),
1100 (
1101 r#"
1102 # No bridges but we still enabled bridges
1103 [bridges]
1104 enabled = true
1105 bridges = []
1106 "#,
1107 Err("bridges.enabled=true, but no bridges defined"),
1108 ),
1109 (
1110 r#"
1111 # One non-PT bridge
1112 [bridges]
1113 enabled = true
1114 bridges = [
1115 "192.0.2.83:80 $0bac39417268b96b9f514ef763fa6fba1a788956",
1116 ]
1117 "#,
1118 Ok(()),
1119 ),
1120 (
1121 r#"
1122 # One obfs4 bridge
1123 [bridges]
1124 enabled = true
1125 bridges = [
1126 "obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1",
1127 ]
1128 [[bridges.transports]]
1129 protocols = ["obfs4"]
1130 path = "obfs4proxy"
1131 "#,
1132 Ok(()),
1133 ),
1134 (
1135 r#"
1136 # One obfs4 bridge with unmanaged transport.
1137 [bridges]
1138 enabled = true
1139 bridges = [
1140 "obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1",
1141 ]
1142 [[bridges.transports]]
1143 protocols = ["obfs4"]
1144 proxy_addr = "127.0.0.1:31337"
1145 "#,
1146 Ok(()),
1147 ),
1148 (
1149 r#"
1150 # Transport is both managed and unmanaged.
1151 [[bridges.transports]]
1152 protocols = ["obfs4"]
1153 path = "obfsproxy"
1154 proxy_addr = "127.0.0.1:9999"
1155 "#,
1156 Err("Cannot provide both path and proxy_addr"),
1157 ),
1158 (
1159 r#"
1160 # One obfs4 bridge and non-PT bridge
1161 [bridges]
1162 enabled = false
1163 bridges = [
1164 "192.0.2.83:80 $0bac39417268b96b9f514ef763fa6fba1a788956",
1165 "obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1",
1166 ]
1167 [[bridges.transports]]
1168 protocols = ["obfs4"]
1169 path = "obfs4proxy"
1170 "#,
1171 Ok(()),
1172 ),
1173 (
1174 r#"
1175 # One obfs4 and non-PT bridge with no transport
1176 [bridges]
1177 enabled = true
1178 bridges = [
1179 "192.0.2.83:80 $0bac39417268b96b9f514ef763fa6fba1a788956",
1180 "obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1",
1181 ]
1182 "#,
1183 Ok(()),
1184 ),
1185 (
1186 r#"
1187 # One obfs4 bridge with no transport
1188 [bridges]
1189 enabled = true
1190 bridges = [
1191 "obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1",
1192 ]
1193 "#,
1194 Err("all bridges unusable due to lack of corresponding pluggable transport"),
1195 ),
1196 (
1197 r#"
1198 # One obfs4 bridge with no transport but bridges are disabled
1199 [bridges]
1200 enabled = false
1201 bridges = [
1202 "obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1",
1203 ]
1204 "#,
1205 Ok(()),
1206 ),
1207 (
1208 r#"
1209 # One non-PT bridge with a redundant transports section
1210 [bridges]
1211 enabled = false
1212 bridges = [
1213 "192.0.2.83:80 $0bac39417268b96b9f514ef763fa6fba1a788956",
1214 ]
1215 [[bridges.transports]]
1216 protocols = ["obfs4"]
1217 path = "obfs4proxy"
1218 "#,
1219 Ok(()),
1220 ),
1221 ];
1222
1223 for (test_case, expected) in test_cases.iter() {
1224 chk(&from_toml(test_case), *expected);
1225 }
1226 }
1227}