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::{define_list_builder_accessors, define_list_builder_helper};
22pub use tor_config::{BoolOrAuto, ConfigError};
23pub use tor_config::{ConfigBuildError, ConfigurationSource, Reconfigure};
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_dirmgr::{
47 Authority, AuthorityBuilder, DirMgrConfig, DirTolerance, DirToleranceBuilder,
48 DownloadSchedule, DownloadScheduleConfig, DownloadScheduleConfigBuilder, FallbackDir,
49 FallbackDirBuilder, NetworkConfig, NetworkConfigBuilder,
50 };
51}
52
53#[cfg(feature = "pt-client")]
55pub mod pt {
56 pub use tor_ptmgr::config::{TransportConfig, TransportConfigBuilder};
57}
58
59#[cfg(feature = "onion-service-service")]
61pub mod onion_service {
62 pub use tor_hsservice::config::{OnionServiceConfig, OnionServiceConfigBuilder};
63}
64
65pub mod vanguards {
67 pub use tor_guardmgr::{VanguardConfig, VanguardConfigBuilder};
68}
69
70#[derive(Debug, Clone, Builder, Eq, PartialEq)]
79#[builder(build_fn(error = "ConfigBuildError"))]
80#[builder(derive(Debug, Serialize, Deserialize))]
81pub struct ClientAddrConfig {
82 #[builder(default)]
87 pub(crate) allow_local_addrs: bool,
88
89 #[cfg(feature = "onion-service-client")]
93 #[builder(default = "true")]
94 pub(crate) allow_onion_addrs: bool,
95}
96impl_standard_builder! { ClientAddrConfig }
97
98#[derive(Debug, Clone, Builder, Eq, PartialEq)]
107#[builder(build_fn(error = "ConfigBuildError"))]
108#[builder(derive(Debug, Serialize, Deserialize))]
109#[non_exhaustive]
110pub struct StreamTimeoutConfig {
111 #[builder(default = "default_connect_timeout()")]
114 #[builder_field_attr(serde(default, with = "humantime_serde::option"))]
115 pub(crate) connect_timeout: Duration,
116
117 #[builder(default = "default_dns_resolve_timeout()")]
119 #[builder_field_attr(serde(default, with = "humantime_serde::option"))]
120 pub(crate) resolve_timeout: Duration,
121
122 #[builder(default = "default_dns_resolve_ptr_timeout()")]
125 #[builder_field_attr(serde(default, with = "humantime_serde::option"))]
126 pub(crate) resolve_ptr_timeout: Duration,
127}
128impl_standard_builder! { StreamTimeoutConfig }
129
130fn default_connect_timeout() -> Duration {
132 Duration::new(10, 0)
133}
134
135fn default_dns_resolve_timeout() -> Duration {
137 Duration::new(10, 0)
138}
139
140fn default_dns_resolve_ptr_timeout() -> Duration {
142 Duration::new(10, 0)
143}
144
145#[derive(Debug, Clone, Builder, Eq, PartialEq)]
153#[builder(build_fn(error = "ConfigBuildError"))]
154#[builder(derive(Debug, Serialize, Deserialize))]
155pub struct SoftwareStatusOverrideConfig {
156 #[builder(field(type = "String", build = "self.parse_protos()?"))]
161 pub(crate) ignore_missing_required_protocols: tor_protover::Protocols,
162}
163
164impl SoftwareStatusOverrideConfigBuilder {
165 fn parse_protos(&self) -> Result<tor_protover::Protocols, ConfigBuildError> {
167 use std::str::FromStr as _;
168 tor_protover::Protocols::from_str(&self.ignore_missing_required_protocols).map_err(|e| {
169 ConfigBuildError::Invalid {
170 field: "ignore_missing_required_protocols".to_string(),
171 problem: e.to_string(),
172 }
173 })
174 }
175}
176
177#[derive(Debug, Clone, Builder, Eq, PartialEq)]
193#[builder(build_fn(error = "ConfigBuildError"))]
194#[builder(derive(Debug, Serialize, Deserialize))]
195#[non_exhaustive]
196pub struct StorageConfig {
197 #[builder(setter(into), default = "default_cache_dir()")]
213 cache_dir: CfgPath,
214
215 #[builder(setter(into), default = "default_state_dir()")]
218 state_dir: CfgPath,
219
220 #[cfg(feature = "keymgr")]
222 #[builder(sub_builder)]
223 #[builder_field_attr(serde(default))]
224 keystore: ArtiKeystoreConfig,
225
226 #[builder(sub_builder(fn_name = "build_for_arti"))]
228 #[builder_field_attr(serde(default))]
229 permissions: Mistrust,
230}
231impl_standard_builder! { StorageConfig }
232
233fn default_cache_dir() -> CfgPath {
235 CfgPath::new("${ARTI_CACHE}".to_owned())
236}
237
238fn default_state_dir() -> CfgPath {
240 CfgPath::new("${ARTI_LOCAL_DATA}".to_owned())
241}
242
243macro_rules! expand_dir {
246 ($self:ident, $dirname:ident, $dircfg:ident) => {
247 $self
248 .$dirname
249 .path($dircfg)
250 .map_err(|e| ConfigBuildError::Invalid {
251 field: stringify!($dirname).to_owned(),
252 problem: e.to_string(),
253 })
254 };
255}
256
257impl StorageConfig {
258 pub(crate) fn expand_state_dir(
260 &self,
261 path_resolver: &CfgPathResolver,
262 ) -> Result<PathBuf, ConfigBuildError> {
263 expand_dir!(self, state_dir, path_resolver)
264 }
265 pub(crate) fn expand_cache_dir(
267 &self,
268 path_resolver: &CfgPathResolver,
269 ) -> Result<PathBuf, ConfigBuildError> {
270 expand_dir!(self, cache_dir, path_resolver)
271 }
272 #[allow(clippy::unnecessary_wraps)]
274 pub(crate) fn keystore(&self) -> ArtiKeystoreConfig {
275 cfg_if::cfg_if! {
276 if #[cfg(feature="keymgr")] {
277 self.keystore.clone()
278 } else {
279 Default::default()
280 }
281 }
282 }
283 pub(crate) fn permissions(&self) -> &Mistrust {
285 &self.permissions
286 }
287}
288
289#[derive(Debug, Clone, Builder, Eq, PartialEq)]
362#[builder(build_fn(validate = "validate_bridges_config", error = "ConfigBuildError"))]
363#[builder(derive(Debug, Serialize, Deserialize))]
364#[non_exhaustive]
365#[builder_struct_attr(non_exhaustive)] pub struct BridgesConfig {
367 #[builder(default)]
374 pub(crate) enabled: BoolOrAuto,
375
376 #[builder(sub_builder, setter(custom))]
378 #[builder_field_attr(serde(default))]
379 bridges: BridgeList,
380
381 #[builder(sub_builder, setter(custom))]
383 #[builder_field_attr(serde(default))]
384 #[cfg(feature = "pt-client")]
385 pub(crate) transports: TransportConfigList,
386}
387
388#[cfg(feature = "pt-client")]
390type TransportConfigList = Vec<pt::TransportConfig>;
391
392#[cfg(feature = "pt-client")]
393define_list_builder_helper! {
394 pub struct TransportConfigListBuilder {
395 transports: [pt::TransportConfigBuilder],
396 }
397 built: TransportConfigList = transports;
398 default = vec![];
399}
400
401#[cfg(feature = "pt-client")]
402define_list_builder_accessors! {
403 struct BridgesConfigBuilder {
404 pub transports: [pt::TransportConfigBuilder],
405 }
406}
407
408impl_standard_builder! { BridgesConfig }
409
410#[cfg(feature = "pt-client")]
411fn validate_pt_config(bridges: &BridgesConfigBuilder) -> Result<(), ConfigBuildError> {
415 use std::collections::HashSet;
416 use std::str::FromStr;
417
418 let mut protocols_defined: HashSet<PtTransportName> = HashSet::new();
420 if let Some(transportlist) = bridges.opt_transports() {
421 for protocols in transportlist.iter() {
422 for protocol in protocols.get_protocols() {
423 protocols_defined.insert(protocol.clone());
424 }
425 }
426 }
427
428 for maybe_protocol in bridges
431 .bridges
432 .bridges
433 .as_deref()
434 .unwrap_or_default()
435 .iter()
436 {
437 match maybe_protocol.get_transport() {
438 Some(raw_protocol) => {
439 let protocol = TransportId::from_str(raw_protocol)
442 .unwrap_or_default()
445 .into_pluggable();
446 match protocol {
448 Some(protocol_required) => {
449 if protocols_defined.contains(&protocol_required) {
450 return Ok(());
451 }
452 }
453 None => return Ok(()),
454 }
455 }
456 None => {
457 return Ok(());
458 }
459 }
460 }
461
462 Err(ConfigBuildError::Inconsistent {
463 fields: ["bridges.bridges", "bridges.transports"].map(Into::into).into_iter().collect(),
464 problem: "Bridges configured, but all bridges unusable due to lack of corresponding pluggable transport in `[bridges.transports]`".into(),
465 })
466}
467
468#[allow(clippy::unnecessary_wraps)]
470fn validate_bridges_config(bridges: &BridgesConfigBuilder) -> Result<(), ConfigBuildError> {
471 let _ = bridges; use BoolOrAuto as BoA;
474
475 match (
482 bridges.enabled.unwrap_or_default(),
483 bridges.bridges.bridges.as_deref().unwrap_or_default(),
484 ) {
485 (BoA::Auto, _) | (BoA::Explicit(false), _) | (BoA::Explicit(true), [_, ..]) => {}
486 (BoA::Explicit(true), []) => {
487 return Err(ConfigBuildError::Inconsistent {
488 fields: ["enabled", "bridges"].map(Into::into).into_iter().collect(),
489 problem: "bridges.enabled=true, but no bridges defined".into(),
490 })
491 }
492 }
493 #[cfg(feature = "pt-client")]
494 {
495 if bridges_enabled(
496 bridges.enabled.unwrap_or_default(),
497 bridges.bridges.bridges.as_deref().unwrap_or_default(),
498 ) {
499 validate_pt_config(bridges)?;
500 }
501 }
502
503 Ok(())
504}
505
506fn bridges_enabled(enabled: BoolOrAuto, bridges: &[impl Sized]) -> bool {
508 #[cfg(feature = "bridge-client")]
509 {
510 enabled.as_bool().unwrap_or(!bridges.is_empty())
511 }
512
513 #[cfg(not(feature = "bridge-client"))]
514 {
515 let _ = (enabled, bridges);
516 false
517 }
518}
519
520impl BridgesConfig {
521 fn bridges_enabled(&self) -> bool {
523 bridges_enabled(self.enabled, &self.bridges)
524 }
525}
526
527pub type BridgeList = Vec<BridgeConfig>;
532
533define_list_builder_helper! {
534 struct BridgeListBuilder {
535 bridges: [BridgeConfigBuilder],
536 }
537 built: BridgeList = bridges;
538 default = vec![];
539 #[serde(try_from="MultilineListBuilder<BridgeConfigBuilder>")]
540 #[serde(into="MultilineListBuilder<BridgeConfigBuilder>")]
541}
542
543convert_helper_via_multi_line_list_builder! {
544 struct BridgeListBuilder {
545 bridges: [BridgeConfigBuilder],
546 }
547}
548
549#[cfg(feature = "bridge-client")]
550define_list_builder_accessors! {
551 struct BridgesConfigBuilder {
552 pub bridges: [BridgeConfigBuilder],
553 }
554}
555
556#[derive(Clone, Builder, Debug, AsRef, educe::Educe)]
574#[educe(PartialEq, Eq)]
575#[builder(build_fn(error = "ConfigBuildError"))]
576#[builder(derive(Serialize, Deserialize, Debug))]
577#[non_exhaustive]
578pub struct TorClientConfig {
579 #[builder(sub_builder)]
581 #[builder_field_attr(serde(default))]
582 tor_network: dir::NetworkConfig,
583
584 #[builder(sub_builder)]
586 #[builder_field_attr(serde(default))]
587 pub(crate) storage: StorageConfig,
588
589 #[builder(sub_builder)]
591 #[builder_field_attr(serde(default))]
592 download_schedule: dir::DownloadScheduleConfig,
593
594 #[builder(sub_builder)]
601 #[builder_field_attr(serde(default))]
602 directory_tolerance: dir::DirTolerance,
603
604 #[builder(
607 sub_builder,
608 field(
609 type = "HashMap<String, i32>",
610 build = "default_extend(self.override_net_params.clone())"
611 )
612 )]
613 #[builder_field_attr(serde(default))]
614 pub(crate) override_net_params: tor_netdoc::doc::netstatus::NetParams<i32>,
615
616 #[builder(sub_builder)]
618 #[builder_field_attr(serde(default))]
619 pub(crate) bridges: BridgesConfig,
620
621 #[builder(sub_builder)]
623 #[builder_field_attr(serde(default))]
624 pub(crate) channel: ChannelConfig,
625
626 #[builder(sub_builder)]
632 #[builder_field_attr(serde(default))]
633 pub(crate) system: SystemConfig,
634
635 #[as_ref]
637 #[builder(sub_builder)]
638 #[builder_field_attr(serde(default))]
639 path_rules: circ::PathConfig,
640
641 #[as_ref]
643 #[builder(sub_builder)]
644 #[builder_field_attr(serde(default))]
645 preemptive_circuits: circ::PreemptiveCircuitConfig,
646
647 #[as_ref]
649 #[builder(sub_builder)]
650 #[builder_field_attr(serde(default))]
651 circuit_timing: circ::CircuitTiming,
652
653 #[builder(sub_builder)]
655 #[builder_field_attr(serde(default))]
656 pub(crate) address_filter: ClientAddrConfig,
657
658 #[builder(sub_builder)]
660 #[builder_field_attr(serde(default))]
661 pub(crate) stream_timeouts: StreamTimeoutConfig,
662
663 #[builder(sub_builder)]
665 #[builder_field_attr(serde(default))]
666 pub(crate) vanguards: vanguards::VanguardConfig,
667
668 #[builder(sub_builder)]
670 #[builder_field_attr(serde(default))]
671 pub(crate) use_obsolete_software: SoftwareStatusOverrideConfig,
672
673 #[as_ref]
681 #[builder(setter(skip))]
682 #[builder_field_attr(serde(skip))]
683 #[educe(PartialEq(ignore), Eq(ignore))]
684 #[builder(default = "tor_config_path::arti_client_base_resolver()")]
685 pub(crate) path_resolver: CfgPathResolver,
686}
687impl_standard_builder! { TorClientConfig }
688
689impl tor_config::load::TopLevel for TorClientConfig {
690 type Builder = TorClientConfigBuilder;
691}
692
693fn default_extend<T: Default + Extend<X>, X>(to_add: impl IntoIterator<Item = X>) -> T {
695 let mut collection = T::default();
696 collection.extend(to_add);
697 collection
698}
699
700#[derive(Debug, Clone, Builder, Eq, PartialEq)]
707#[builder(build_fn(error = "ConfigBuildError"))]
708#[builder(derive(Debug, Serialize, Deserialize))]
709#[non_exhaustive]
710pub struct SystemConfig {
711 #[builder(sub_builder(fn_name = "build"))]
713 #[builder_field_attr(serde(default))]
714 pub(crate) memory: tor_memquota::Config,
715}
716impl_standard_builder! { SystemConfig }
717
718impl tor_circmgr::CircMgrConfig for TorClientConfig {
719 #[cfg(all(
720 feature = "vanguards",
721 any(feature = "onion-service-client", feature = "onion-service-service")
722 ))]
723 fn vanguard_config(&self) -> &tor_guardmgr::VanguardConfig {
724 &self.vanguards
725 }
726}
727#[cfg(feature = "onion-service-client")]
728impl tor_hsclient::HsClientConnectorConfig for TorClientConfig {}
729
730#[cfg(any(feature = "onion-service-client", feature = "onion-service-service"))]
731impl tor_circmgr::hspool::HsCircPoolConfig for TorClientConfig {
732 #[cfg(all(
733 feature = "vanguards",
734 any(feature = "onion-service-client", feature = "onion-service-service")
735 ))]
736 fn vanguard_config(&self) -> &tor_guardmgr::VanguardConfig {
737 &self.vanguards
738 }
739}
740
741impl AsRef<tor_guardmgr::fallback::FallbackList> for TorClientConfig {
742 fn as_ref(&self) -> &tor_guardmgr::fallback::FallbackList {
743 self.tor_network.fallback_caches()
744 }
745}
746impl AsRef<[BridgeConfig]> for TorClientConfig {
747 fn as_ref(&self) -> &[BridgeConfig] {
748 #[cfg(feature = "bridge-client")]
749 {
750 &self.bridges.bridges
751 }
752
753 #[cfg(not(feature = "bridge-client"))]
754 {
755 &[]
756 }
757 }
758}
759impl AsRef<BridgesConfig> for TorClientConfig {
760 fn as_ref(&self) -> &BridgesConfig {
761 &self.bridges
762 }
763}
764impl tor_guardmgr::GuardMgrConfig for TorClientConfig {
765 fn bridges_enabled(&self) -> bool {
766 self.bridges.bridges_enabled()
767 }
768}
769
770impl TorClientConfig {
771 #[rustfmt::skip]
773 pub fn dir_mgr_config(&self) -> Result<dir::DirMgrConfig, ConfigBuildError> {
774 Ok(dir::DirMgrConfig {
775 network: self.tor_network .clone(),
776 schedule: self.download_schedule .clone(),
777 tolerance: self.directory_tolerance.clone(),
778 cache_dir: self.storage.expand_cache_dir(&self.path_resolver)?,
779 cache_trust: self.storage.permissions.clone(),
780 override_net_params: self.override_net_params.clone(),
781 extensions: Default::default(),
782 })
783 }
784
785 pub fn fs_mistrust(&self) -> &Mistrust {
801 self.storage.permissions()
802 }
803
804 pub fn keystore(&self) -> ArtiKeystoreConfig {
806 self.storage.keystore()
807 }
808
809 pub(crate) fn state_dir(&self) -> StdResult<(PathBuf, &fs_mistrust::Mistrust), ErrorDetail> {
812 let state_dir = self
813 .storage
814 .expand_state_dir(&self.path_resolver)
815 .map_err(ErrorDetail::Configuration)?;
816 let mistrust = self.storage.permissions();
817
818 Ok((state_dir, mistrust))
819 }
820
821 #[cfg(feature = "testing")]
826 pub fn system_memory(&self) -> &tor_memquota::Config {
827 &self.system.memory
828 }
829}
830
831impl TorClientConfigBuilder {
832 pub fn from_directories<P, Q>(state_dir: P, cache_dir: Q) -> Self
837 where
838 P: AsRef<Path>,
839 Q: AsRef<Path>,
840 {
841 let mut builder = Self::default();
842
843 builder
844 .storage()
845 .cache_dir(CfgPath::new_literal(cache_dir.as_ref()))
846 .state_dir(CfgPath::new_literal(state_dir.as_ref()));
847
848 builder
849 }
850}
851
852pub fn default_config_files() -> Result<Vec<ConfigurationSource>, CfgPathError> {
854 let path_resolver = tor_config_path::arti_client_base_resolver();
856
857 ["${ARTI_CONFIG}/arti.toml", "${ARTI_CONFIG}/arti.d/"]
858 .into_iter()
859 .map(|f| {
860 let path = CfgPath::new(f.into()).path(&path_resolver)?;
861 Ok(ConfigurationSource::from_path(path))
862 })
863 .collect()
864}
865
866#[deprecated = "use tor-config::mistrust::ARTI_FS_DISABLE_PERMISSION_CHECKS instead"]
868pub const FS_PERMISSIONS_CHECKS_DISABLE_VAR: &str = "ARTI_FS_DISABLE_PERMISSION_CHECKS";
869
870#[deprecated(since = "0.5.0")]
877#[allow(deprecated)]
878pub fn fs_permissions_checks_disabled_via_env() -> bool {
879 std::env::var_os(FS_PERMISSIONS_CHECKS_DISABLE_VAR).is_some()
880}
881
882#[cfg(test)]
883mod test {
884 #![allow(clippy::bool_assert_comparison)]
886 #![allow(clippy::clone_on_copy)]
887 #![allow(clippy::dbg_macro)]
888 #![allow(clippy::mixed_attributes_style)]
889 #![allow(clippy::print_stderr)]
890 #![allow(clippy::print_stdout)]
891 #![allow(clippy::single_char_pattern)]
892 #![allow(clippy::unwrap_used)]
893 #![allow(clippy::unchecked_duration_subtraction)]
894 #![allow(clippy::useless_vec)]
895 #![allow(clippy::needless_pass_by_value)]
896 use super::*;
898
899 #[test]
900 fn defaults() {
901 let dflt = TorClientConfig::default();
902 let b2 = TorClientConfigBuilder::default();
903 let dflt2 = b2.build().unwrap();
904 assert_eq!(&dflt, &dflt2);
905 }
906
907 #[test]
908 fn builder() {
909 let sec = std::time::Duration::from_secs(1);
910
911 let auth = dir::Authority::builder()
912 .name("Fred")
913 .v3ident([22; 20].into())
914 .clone();
915 let mut fallback = dir::FallbackDir::builder();
916 fallback
917 .rsa_identity([23; 20].into())
918 .ed_identity([99; 32].into())
919 .orports()
920 .push("127.0.0.7:7".parse().unwrap());
921
922 let mut bld = TorClientConfig::builder();
923 bld.tor_network().set_authorities(vec![auth]);
924 bld.tor_network().set_fallback_caches(vec![fallback]);
925 bld.storage()
926 .cache_dir(CfgPath::new("/var/tmp/foo".to_owned()))
927 .state_dir(CfgPath::new("/var/tmp/bar".to_owned()));
928 bld.download_schedule().retry_certs().attempts(10);
929 bld.download_schedule().retry_certs().initial_delay(sec);
930 bld.download_schedule().retry_certs().parallelism(3);
931 bld.download_schedule().retry_microdescs().attempts(30);
932 bld.download_schedule()
933 .retry_microdescs()
934 .initial_delay(10 * sec);
935 bld.download_schedule().retry_microdescs().parallelism(9);
936 bld.override_net_params()
937 .insert("wombats-per-quokka".to_owned(), 7);
938 bld.path_rules()
939 .ipv4_subnet_family_prefix(20)
940 .ipv6_subnet_family_prefix(48);
941 bld.circuit_timing()
942 .max_dirtiness(90 * sec)
943 .request_timeout(10 * sec)
944 .request_max_retries(22)
945 .request_loyalty(3600 * sec);
946 bld.address_filter().allow_local_addrs(true);
947
948 let val = bld.build().unwrap();
949
950 assert_ne!(val, TorClientConfig::default());
951 }
952
953 #[test]
954 fn bridges_supported() {
955 fn chk(exp: Result<usize, ()>, s: &str) {
958 eprintln!("----------\n{s}\n----------\n");
959 let got = (|| {
960 let cfg: toml::Value = toml::from_str(s).unwrap();
961 let cfg: TorClientConfigBuilder = cfg.try_into()?;
962 let cfg = cfg.build()?;
963 let n_bridges = cfg.bridges.bridges.len();
964 Ok::<_, anyhow::Error>(n_bridges) })()
966 .map_err(|_| ());
967 assert_eq!(got, exp);
968 }
969
970 let chk_enabled_or_auto = |exp, bridges_toml| {
971 for enabled in [r#""#, r#"enabled = true"#, r#"enabled = "auto""#] {
972 chk(exp, &format!("[bridges]\n{}\n{}", enabled, bridges_toml));
973 }
974 };
975
976 let ok_1_if = |b: bool| b.then_some(1).ok_or(());
977
978 chk(
979 Err(()),
980 r#"
981 [bridges]
982 enabled = true
983 "#,
984 );
985
986 chk_enabled_or_auto(
987 ok_1_if(cfg!(feature = "bridge-client")),
988 r#"
989 bridges = ["192.0.2.83:80 $0bac39417268b96b9f514ef763fa6fba1a788956"]
990 "#,
991 );
992
993 chk_enabled_or_auto(
994 ok_1_if(cfg!(feature = "pt-client")),
995 r#"
996 bridges = ["obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1"]
997 [[bridges.transports]]
998 protocols = ["obfs4"]
999 path = "obfs4proxy"
1000 "#,
1001 );
1002 }
1003
1004 #[test]
1005 fn check_default() {
1006 let dflt = default_config_files().unwrap();
1010 assert!(dflt[0].as_path().unwrap().ends_with("arti.toml"));
1011 assert!(dflt[1].as_path().unwrap().ends_with("arti.d"));
1012 assert_eq!(dflt.len(), 2);
1013 }
1014
1015 #[test]
1016 #[cfg(feature = "pt-client")]
1017 fn check_bridge_pt() {
1018 let from_toml = |s: &str| -> TorClientConfigBuilder {
1019 let cfg: toml::Value = toml::from_str(dbg!(s)).unwrap();
1020 let cfg: TorClientConfigBuilder = cfg.try_into().unwrap();
1021 cfg
1022 };
1023
1024 let chk = |cfg: &TorClientConfigBuilder, expected: Result<(), &str>| match (
1025 cfg.build(),
1026 expected,
1027 ) {
1028 (Ok(_), Ok(())) => {}
1029 (Err(e), Err(ex)) => {
1030 if !e.to_string().contains(ex) {
1031 panic!("\"{e}\" did not contain {ex}");
1032 }
1033 }
1034 (Ok(_), Err(ex)) => {
1035 panic!("Expected {ex} but cfg succeeded");
1036 }
1037 (Err(e), Ok(())) => {
1038 panic!("Expected success but got error {e}")
1039 }
1040 };
1041
1042 let test_cases = [
1043 ("# No bridges", Ok(())),
1044 (
1045 r#"
1046 # No bridges but we still enabled bridges
1047 [bridges]
1048 enabled = true
1049 bridges = []
1050 "#,
1051 Err("bridges.enabled=true, but no bridges defined"),
1052 ),
1053 (
1054 r#"
1055 # One non-PT bridge
1056 [bridges]
1057 enabled = true
1058 bridges = [
1059 "192.0.2.83:80 $0bac39417268b96b9f514ef763fa6fba1a788956",
1060 ]
1061 "#,
1062 Ok(()),
1063 ),
1064 (
1065 r#"
1066 # One obfs4 bridge
1067 [bridges]
1068 enabled = true
1069 bridges = [
1070 "obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1",
1071 ]
1072 [[bridges.transports]]
1073 protocols = ["obfs4"]
1074 path = "obfs4proxy"
1075 "#,
1076 Ok(()),
1077 ),
1078 (
1079 r#"
1080 # One obfs4 bridge with unmanaged transport.
1081 [bridges]
1082 enabled = true
1083 bridges = [
1084 "obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1",
1085 ]
1086 [[bridges.transports]]
1087 protocols = ["obfs4"]
1088 proxy_addr = "127.0.0.1:31337"
1089 "#,
1090 Ok(()),
1091 ),
1092 (
1093 r#"
1094 # Transport is both managed and unmanaged.
1095 [[bridges.transports]]
1096 protocols = ["obfs4"]
1097 path = "obfsproxy"
1098 proxy_addr = "127.0.0.1:9999"
1099 "#,
1100 Err("Cannot provide both path and proxy_addr"),
1101 ),
1102 (
1103 r#"
1104 # One obfs4 bridge and non-PT bridge
1105 [bridges]
1106 enabled = false
1107 bridges = [
1108 "192.0.2.83:80 $0bac39417268b96b9f514ef763fa6fba1a788956",
1109 "obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1",
1110 ]
1111 [[bridges.transports]]
1112 protocols = ["obfs4"]
1113 path = "obfs4proxy"
1114 "#,
1115 Ok(()),
1116 ),
1117 (
1118 r#"
1119 # One obfs4 and non-PT bridge with no transport
1120 [bridges]
1121 enabled = true
1122 bridges = [
1123 "192.0.2.83:80 $0bac39417268b96b9f514ef763fa6fba1a788956",
1124 "obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1",
1125 ]
1126 "#,
1127 Ok(()),
1128 ),
1129 (
1130 r#"
1131 # One obfs4 bridge with no transport
1132 [bridges]
1133 enabled = true
1134 bridges = [
1135 "obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1",
1136 ]
1137 "#,
1138 Err("all bridges unusable due to lack of corresponding pluggable transport"),
1139 ),
1140 (
1141 r#"
1142 # One obfs4 bridge with no transport but bridges are disabled
1143 [bridges]
1144 enabled = false
1145 bridges = [
1146 "obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1",
1147 ]
1148 "#,
1149 Ok(()),
1150 ),
1151 (
1152 r#"
1153 # One non-PT bridge with a redundant transports section
1154 [bridges]
1155 enabled = false
1156 bridges = [
1157 "192.0.2.83:80 $0bac39417268b96b9f514ef763fa6fba1a788956",
1158 ]
1159 [[bridges.transports]]
1160 protocols = ["obfs4"]
1161 path = "obfs4proxy"
1162 "#,
1163 Ok(()),
1164 ),
1165 ];
1166
1167 for (test_case, expected) in test_cases.iter() {
1168 chk(&from_toml(test_case), *expected);
1169 }
1170 }
1171}