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