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