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)]
161#[builder(build_fn(error = "ConfigBuildError"))]
162#[builder(derive(Debug, Serialize, Deserialize))]
163#[non_exhaustive]
164pub struct StorageConfig {
165 #[builder(setter(into), default = "default_cache_dir()")]
181 cache_dir: CfgPath,
182
183 #[builder(setter(into), default = "default_state_dir()")]
186 state_dir: CfgPath,
187
188 #[cfg(feature = "keymgr")]
190 #[builder(sub_builder)]
191 #[builder_field_attr(serde(default))]
192 keystore: ArtiKeystoreConfig,
193
194 #[builder(sub_builder(fn_name = "build_for_arti"))]
196 #[builder_field_attr(serde(default))]
197 permissions: Mistrust,
198}
199impl_standard_builder! { StorageConfig }
200
201fn default_cache_dir() -> CfgPath {
203 CfgPath::new("${ARTI_CACHE}".to_owned())
204}
205
206fn default_state_dir() -> CfgPath {
208 CfgPath::new("${ARTI_LOCAL_DATA}".to_owned())
209}
210
211macro_rules! expand_dir {
214 ($self:ident, $dirname:ident, $dircfg:ident) => {
215 $self
216 .$dirname
217 .path($dircfg)
218 .map_err(|e| ConfigBuildError::Invalid {
219 field: stringify!($dirname).to_owned(),
220 problem: e.to_string(),
221 })
222 };
223}
224
225impl StorageConfig {
226 pub(crate) fn expand_state_dir(
228 &self,
229 path_resolver: &CfgPathResolver,
230 ) -> Result<PathBuf, ConfigBuildError> {
231 expand_dir!(self, state_dir, path_resolver)
232 }
233 pub(crate) fn expand_cache_dir(
235 &self,
236 path_resolver: &CfgPathResolver,
237 ) -> Result<PathBuf, ConfigBuildError> {
238 expand_dir!(self, cache_dir, path_resolver)
239 }
240 #[allow(clippy::unnecessary_wraps)]
242 pub(crate) fn keystore(&self) -> ArtiKeystoreConfig {
243 cfg_if::cfg_if! {
244 if #[cfg(feature="keymgr")] {
245 self.keystore.clone()
246 } else {
247 Default::default()
248 }
249 }
250 }
251 pub(crate) fn permissions(&self) -> &Mistrust {
253 &self.permissions
254 }
255}
256
257#[derive(Debug, Clone, Builder, Eq, PartialEq)]
330#[builder(build_fn(validate = "validate_bridges_config", error = "ConfigBuildError"))]
331#[builder(derive(Debug, Serialize, Deserialize))]
332#[non_exhaustive]
333#[builder_struct_attr(non_exhaustive)] pub struct BridgesConfig {
335 #[builder(default)]
342 pub(crate) enabled: BoolOrAuto,
343
344 #[builder(sub_builder, setter(custom))]
346 #[builder_field_attr(serde(default))]
347 bridges: BridgeList,
348
349 #[builder(sub_builder, setter(custom))]
351 #[builder_field_attr(serde(default))]
352 #[cfg(feature = "pt-client")]
353 pub(crate) transports: TransportConfigList,
354}
355
356#[cfg(feature = "pt-client")]
358type TransportConfigList = Vec<pt::TransportConfig>;
359
360#[cfg(feature = "pt-client")]
361define_list_builder_helper! {
362 pub struct TransportConfigListBuilder {
363 transports: [pt::TransportConfigBuilder],
364 }
365 built: TransportConfigList = transports;
366 default = vec![];
367}
368
369#[cfg(feature = "pt-client")]
370define_list_builder_accessors! {
371 struct BridgesConfigBuilder {
372 pub transports: [pt::TransportConfigBuilder],
373 }
374}
375
376impl_standard_builder! { BridgesConfig }
377
378#[cfg(feature = "pt-client")]
379fn validate_pt_config(bridges: &BridgesConfigBuilder) -> Result<(), ConfigBuildError> {
383 use std::collections::HashSet;
384 use std::str::FromStr;
385
386 let mut protocols_defined: HashSet<PtTransportName> = HashSet::new();
388 if let Some(transportlist) = bridges.opt_transports() {
389 for protocols in transportlist.iter() {
390 for protocol in protocols.get_protocols() {
391 protocols_defined.insert(protocol.clone());
392 }
393 }
394 }
395
396 for maybe_protocol in bridges
399 .bridges
400 .bridges
401 .as_deref()
402 .unwrap_or_default()
403 .iter()
404 {
405 match maybe_protocol.get_transport() {
406 Some(raw_protocol) => {
407 let protocol = TransportId::from_str(raw_protocol)
410 .unwrap_or_default()
413 .into_pluggable();
414 match protocol {
416 Some(protocol_required) => {
417 if protocols_defined.contains(&protocol_required) {
418 return Ok(());
419 }
420 }
421 None => return Ok(()),
422 }
423 }
424 None => {
425 return Ok(());
426 }
427 }
428 }
429
430 Err(ConfigBuildError::Inconsistent {
431 fields: ["bridges.bridges", "bridges.transports"].map(Into::into).into_iter().collect(),
432 problem: "Bridges configured, but all bridges unusable due to lack of corresponding pluggable transport in `[bridges.transports]`".into(),
433 })
434}
435
436#[allow(clippy::unnecessary_wraps)]
438fn validate_bridges_config(bridges: &BridgesConfigBuilder) -> Result<(), ConfigBuildError> {
439 let _ = bridges; use BoolOrAuto as BoA;
442
443 match (
450 bridges.enabled.unwrap_or_default(),
451 bridges.bridges.bridges.as_deref().unwrap_or_default(),
452 ) {
453 (BoA::Auto, _) | (BoA::Explicit(false), _) | (BoA::Explicit(true), [_, ..]) => {}
454 (BoA::Explicit(true), []) => {
455 return Err(ConfigBuildError::Inconsistent {
456 fields: ["enabled", "bridges"].map(Into::into).into_iter().collect(),
457 problem: "bridges.enabled=true, but no bridges defined".into(),
458 })
459 }
460 }
461 #[cfg(feature = "pt-client")]
462 {
463 if bridges_enabled(
464 bridges.enabled.unwrap_or_default(),
465 bridges.bridges.bridges.as_deref().unwrap_or_default(),
466 ) {
467 validate_pt_config(bridges)?;
468 }
469 }
470
471 Ok(())
472}
473
474fn bridges_enabled(enabled: BoolOrAuto, bridges: &[impl Sized]) -> bool {
476 #[cfg(feature = "bridge-client")]
477 {
478 enabled.as_bool().unwrap_or(!bridges.is_empty())
479 }
480
481 #[cfg(not(feature = "bridge-client"))]
482 {
483 let _ = (enabled, bridges);
484 false
485 }
486}
487
488impl BridgesConfig {
489 fn bridges_enabled(&self) -> bool {
491 bridges_enabled(self.enabled, &self.bridges)
492 }
493}
494
495pub type BridgeList = Vec<BridgeConfig>;
500
501define_list_builder_helper! {
502 struct BridgeListBuilder {
503 bridges: [BridgeConfigBuilder],
504 }
505 built: BridgeList = bridges;
506 default = vec![];
507 #[serde(try_from="MultilineListBuilder<BridgeConfigBuilder>")]
508 #[serde(into="MultilineListBuilder<BridgeConfigBuilder>")]
509}
510
511convert_helper_via_multi_line_list_builder! {
512 struct BridgeListBuilder {
513 bridges: [BridgeConfigBuilder],
514 }
515}
516
517#[cfg(feature = "bridge-client")]
518define_list_builder_accessors! {
519 struct BridgesConfigBuilder {
520 pub bridges: [BridgeConfigBuilder],
521 }
522}
523
524#[derive(Clone, Builder, Debug, AsRef, educe::Educe)]
542#[educe(PartialEq, Eq)]
543#[builder(build_fn(error = "ConfigBuildError"))]
544#[builder(derive(Serialize, Deserialize, Debug))]
545#[non_exhaustive]
546pub struct TorClientConfig {
547 #[builder(sub_builder)]
549 #[builder_field_attr(serde(default))]
550 tor_network: dir::NetworkConfig,
551
552 #[builder(sub_builder)]
554 #[builder_field_attr(serde(default))]
555 pub(crate) storage: StorageConfig,
556
557 #[builder(sub_builder)]
559 #[builder_field_attr(serde(default))]
560 download_schedule: dir::DownloadScheduleConfig,
561
562 #[builder(sub_builder)]
569 #[builder_field_attr(serde(default))]
570 directory_tolerance: dir::DirTolerance,
571
572 #[builder(
575 sub_builder,
576 field(
577 type = "HashMap<String, i32>",
578 build = "default_extend(self.override_net_params.clone())"
579 )
580 )]
581 #[builder_field_attr(serde(default))]
582 pub(crate) override_net_params: tor_netdoc::doc::netstatus::NetParams<i32>,
583
584 #[builder(sub_builder)]
586 #[builder_field_attr(serde(default))]
587 pub(crate) bridges: BridgesConfig,
588
589 #[builder(sub_builder)]
591 #[builder_field_attr(serde(default))]
592 pub(crate) channel: ChannelConfig,
593
594 #[builder(sub_builder)]
600 #[builder_field_attr(serde(default))]
601 pub(crate) system: SystemConfig,
602
603 #[as_ref]
605 #[builder(sub_builder)]
606 #[builder_field_attr(serde(default))]
607 path_rules: circ::PathConfig,
608
609 #[as_ref]
611 #[builder(sub_builder)]
612 #[builder_field_attr(serde(default))]
613 preemptive_circuits: circ::PreemptiveCircuitConfig,
614
615 #[as_ref]
617 #[builder(sub_builder)]
618 #[builder_field_attr(serde(default))]
619 circuit_timing: circ::CircuitTiming,
620
621 #[builder(sub_builder)]
623 #[builder_field_attr(serde(default))]
624 pub(crate) address_filter: ClientAddrConfig,
625
626 #[builder(sub_builder)]
628 #[builder_field_attr(serde(default))]
629 pub(crate) stream_timeouts: StreamTimeoutConfig,
630
631 #[builder(sub_builder)]
633 #[builder_field_attr(serde(default))]
634 pub(crate) vanguards: vanguards::VanguardConfig,
635
636 #[as_ref]
644 #[builder(setter(skip))]
645 #[builder_field_attr(serde(skip))]
646 #[educe(PartialEq(ignore), Eq(ignore))]
647 #[builder(default = "tor_config_path::arti_client_base_resolver()")]
648 pub(crate) path_resolver: CfgPathResolver,
649}
650impl_standard_builder! { TorClientConfig }
651
652impl tor_config::load::TopLevel for TorClientConfig {
653 type Builder = TorClientConfigBuilder;
654}
655
656fn default_extend<T: Default + Extend<X>, X>(to_add: impl IntoIterator<Item = X>) -> T {
658 let mut collection = T::default();
659 collection.extend(to_add);
660 collection
661}
662
663#[derive(Debug, Clone, Builder, Eq, PartialEq)]
670#[builder(build_fn(error = "ConfigBuildError"))]
671#[builder(derive(Debug, Serialize, Deserialize))]
672#[non_exhaustive]
673pub struct SystemConfig {
674 #[builder(sub_builder(fn_name = "build"))]
676 #[builder_field_attr(serde(default))]
677 pub(crate) memory: tor_memquota::Config,
678}
679impl_standard_builder! { SystemConfig }
680
681impl tor_circmgr::CircMgrConfig for TorClientConfig {
682 #[cfg(all(
683 feature = "vanguards",
684 any(feature = "onion-service-client", feature = "onion-service-service")
685 ))]
686 fn vanguard_config(&self) -> &tor_guardmgr::VanguardConfig {
687 &self.vanguards
688 }
689}
690#[cfg(feature = "onion-service-client")]
691impl tor_hsclient::HsClientConnectorConfig for TorClientConfig {}
692
693#[cfg(any(feature = "onion-service-client", feature = "onion-service-service"))]
694impl tor_circmgr::hspool::HsCircPoolConfig for TorClientConfig {
695 #[cfg(all(
696 feature = "vanguards",
697 any(feature = "onion-service-client", feature = "onion-service-service")
698 ))]
699 fn vanguard_config(&self) -> &tor_guardmgr::VanguardConfig {
700 &self.vanguards
701 }
702}
703
704impl AsRef<tor_guardmgr::fallback::FallbackList> for TorClientConfig {
705 fn as_ref(&self) -> &tor_guardmgr::fallback::FallbackList {
706 self.tor_network.fallback_caches()
707 }
708}
709impl AsRef<[BridgeConfig]> for TorClientConfig {
710 fn as_ref(&self) -> &[BridgeConfig] {
711 #[cfg(feature = "bridge-client")]
712 {
713 &self.bridges.bridges
714 }
715
716 #[cfg(not(feature = "bridge-client"))]
717 {
718 &[]
719 }
720 }
721}
722impl AsRef<BridgesConfig> for TorClientConfig {
723 fn as_ref(&self) -> &BridgesConfig {
724 &self.bridges
725 }
726}
727impl tor_guardmgr::GuardMgrConfig for TorClientConfig {
728 fn bridges_enabled(&self) -> bool {
729 self.bridges.bridges_enabled()
730 }
731}
732
733impl TorClientConfig {
734 #[rustfmt::skip]
736 pub fn dir_mgr_config(&self) -> Result<dir::DirMgrConfig, ConfigBuildError> {
737 Ok(dir::DirMgrConfig {
738 network: self.tor_network .clone(),
739 schedule: self.download_schedule .clone(),
740 tolerance: self.directory_tolerance.clone(),
741 cache_dir: self.storage.expand_cache_dir(&self.path_resolver)?,
742 cache_trust: self.storage.permissions.clone(),
743 override_net_params: self.override_net_params.clone(),
744 extensions: Default::default(),
745 })
746 }
747
748 pub fn fs_mistrust(&self) -> &Mistrust {
764 self.storage.permissions()
765 }
766
767 pub fn keystore(&self) -> ArtiKeystoreConfig {
769 self.storage.keystore()
770 }
771
772 pub(crate) fn state_dir(&self) -> StdResult<(PathBuf, &fs_mistrust::Mistrust), ErrorDetail> {
775 let state_dir = self
776 .storage
777 .expand_state_dir(&self.path_resolver)
778 .map_err(ErrorDetail::Configuration)?;
779 let mistrust = self.storage.permissions();
780
781 Ok((state_dir, mistrust))
782 }
783
784 #[cfg(feature = "testing")]
789 pub fn system_memory(&self) -> &tor_memquota::Config {
790 &self.system.memory
791 }
792}
793
794impl TorClientConfigBuilder {
795 pub fn from_directories<P, Q>(state_dir: P, cache_dir: Q) -> Self
800 where
801 P: AsRef<Path>,
802 Q: AsRef<Path>,
803 {
804 let mut builder = Self::default();
805
806 builder
807 .storage()
808 .cache_dir(CfgPath::new_literal(cache_dir.as_ref()))
809 .state_dir(CfgPath::new_literal(state_dir.as_ref()));
810
811 builder
812 }
813}
814
815pub fn default_config_files() -> Result<Vec<ConfigurationSource>, CfgPathError> {
817 let path_resolver = tor_config_path::arti_client_base_resolver();
819
820 ["${ARTI_CONFIG}/arti.toml", "${ARTI_CONFIG}/arti.d/"]
821 .into_iter()
822 .map(|f| {
823 let path = CfgPath::new(f.into()).path(&path_resolver)?;
824 Ok(ConfigurationSource::from_path(path))
825 })
826 .collect()
827}
828
829#[deprecated = "use tor-config::mistrust::ARTI_FS_DISABLE_PERMISSION_CHECKS instead"]
831pub const FS_PERMISSIONS_CHECKS_DISABLE_VAR: &str = "ARTI_FS_DISABLE_PERMISSION_CHECKS";
832
833#[deprecated(since = "0.5.0")]
840#[allow(deprecated)]
841pub fn fs_permissions_checks_disabled_via_env() -> bool {
842 std::env::var_os(FS_PERMISSIONS_CHECKS_DISABLE_VAR).is_some()
843}
844
845#[cfg(test)]
846mod test {
847 #![allow(clippy::bool_assert_comparison)]
849 #![allow(clippy::clone_on_copy)]
850 #![allow(clippy::dbg_macro)]
851 #![allow(clippy::mixed_attributes_style)]
852 #![allow(clippy::print_stderr)]
853 #![allow(clippy::print_stdout)]
854 #![allow(clippy::single_char_pattern)]
855 #![allow(clippy::unwrap_used)]
856 #![allow(clippy::unchecked_duration_subtraction)]
857 #![allow(clippy::useless_vec)]
858 #![allow(clippy::needless_pass_by_value)]
859 use super::*;
861
862 #[test]
863 fn defaults() {
864 let dflt = TorClientConfig::default();
865 let b2 = TorClientConfigBuilder::default();
866 let dflt2 = b2.build().unwrap();
867 assert_eq!(&dflt, &dflt2);
868 }
869
870 #[test]
871 fn builder() {
872 let sec = std::time::Duration::from_secs(1);
873
874 let auth = dir::Authority::builder()
875 .name("Fred")
876 .v3ident([22; 20].into())
877 .clone();
878 let mut fallback = dir::FallbackDir::builder();
879 fallback
880 .rsa_identity([23; 20].into())
881 .ed_identity([99; 32].into())
882 .orports()
883 .push("127.0.0.7:7".parse().unwrap());
884
885 let mut bld = TorClientConfig::builder();
886 bld.tor_network().set_authorities(vec![auth]);
887 bld.tor_network().set_fallback_caches(vec![fallback]);
888 bld.storage()
889 .cache_dir(CfgPath::new("/var/tmp/foo".to_owned()))
890 .state_dir(CfgPath::new("/var/tmp/bar".to_owned()));
891 bld.download_schedule().retry_certs().attempts(10);
892 bld.download_schedule().retry_certs().initial_delay(sec);
893 bld.download_schedule().retry_certs().parallelism(3);
894 bld.download_schedule().retry_microdescs().attempts(30);
895 bld.download_schedule()
896 .retry_microdescs()
897 .initial_delay(10 * sec);
898 bld.download_schedule().retry_microdescs().parallelism(9);
899 bld.override_net_params()
900 .insert("wombats-per-quokka".to_owned(), 7);
901 bld.path_rules()
902 .ipv4_subnet_family_prefix(20)
903 .ipv6_subnet_family_prefix(48);
904 bld.circuit_timing()
905 .max_dirtiness(90 * sec)
906 .request_timeout(10 * sec)
907 .request_max_retries(22)
908 .request_loyalty(3600 * sec);
909 bld.address_filter().allow_local_addrs(true);
910
911 let val = bld.build().unwrap();
912
913 assert_ne!(val, TorClientConfig::default());
914 }
915
916 #[test]
917 fn bridges_supported() {
918 fn chk(exp: Result<usize, ()>, s: &str) {
921 eprintln!("----------\n{s}\n----------\n");
922 let got = (|| {
923 let cfg: toml::Value = toml::from_str(s).unwrap();
924 let cfg: TorClientConfigBuilder = cfg.try_into()?;
925 let cfg = cfg.build()?;
926 let n_bridges = cfg.bridges.bridges.len();
927 Ok::<_, anyhow::Error>(n_bridges) })()
929 .map_err(|_| ());
930 assert_eq!(got, exp);
931 }
932
933 let chk_enabled_or_auto = |exp, bridges_toml| {
934 for enabled in [r#""#, r#"enabled = true"#, r#"enabled = "auto""#] {
935 chk(exp, &format!("[bridges]\n{}\n{}", enabled, bridges_toml));
936 }
937 };
938
939 let ok_1_if = |b: bool| b.then_some(1).ok_or(());
940
941 chk(
942 Err(()),
943 r#"
944 [bridges]
945 enabled = true
946 "#,
947 );
948
949 chk_enabled_or_auto(
950 ok_1_if(cfg!(feature = "bridge-client")),
951 r#"
952 bridges = ["192.0.2.83:80 $0bac39417268b96b9f514ef763fa6fba1a788956"]
953 "#,
954 );
955
956 chk_enabled_or_auto(
957 ok_1_if(cfg!(feature = "pt-client")),
958 r#"
959 bridges = ["obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1"]
960 [[bridges.transports]]
961 protocols = ["obfs4"]
962 path = "obfs4proxy"
963 "#,
964 );
965 }
966
967 #[test]
968 fn check_default() {
969 let dflt = default_config_files().unwrap();
973 assert!(dflt[0].as_path().unwrap().ends_with("arti.toml"));
974 assert!(dflt[1].as_path().unwrap().ends_with("arti.d"));
975 assert_eq!(dflt.len(), 2);
976 }
977
978 #[test]
979 #[cfg(feature = "pt-client")]
980 fn check_bridge_pt() {
981 let from_toml = |s: &str| -> TorClientConfigBuilder {
982 let cfg: toml::Value = toml::from_str(dbg!(s)).unwrap();
983 let cfg: TorClientConfigBuilder = cfg.try_into().unwrap();
984 cfg
985 };
986
987 let chk = |cfg: &TorClientConfigBuilder, expected: Result<(), &str>| match (
988 cfg.build(),
989 expected,
990 ) {
991 (Ok(_), Ok(())) => {}
992 (Err(e), Err(ex)) => {
993 if !e.to_string().contains(ex) {
994 panic!("\"{e}\" did not contain {ex}");
995 }
996 }
997 (Ok(_), Err(ex)) => {
998 panic!("Expected {ex} but cfg succeeded");
999 }
1000 (Err(e), Ok(())) => {
1001 panic!("Expected success but got error {e}")
1002 }
1003 };
1004
1005 let test_cases = [
1006 ("# No bridges", Ok(())),
1007 (
1008 r#"
1009 # No bridges but we still enabled bridges
1010 [bridges]
1011 enabled = true
1012 bridges = []
1013 "#,
1014 Err("bridges.enabled=true, but no bridges defined"),
1015 ),
1016 (
1017 r#"
1018 # One non-PT bridge
1019 [bridges]
1020 enabled = true
1021 bridges = [
1022 "192.0.2.83:80 $0bac39417268b96b9f514ef763fa6fba1a788956",
1023 ]
1024 "#,
1025 Ok(()),
1026 ),
1027 (
1028 r#"
1029 # One obfs4 bridge
1030 [bridges]
1031 enabled = true
1032 bridges = [
1033 "obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1",
1034 ]
1035 [[bridges.transports]]
1036 protocols = ["obfs4"]
1037 path = "obfs4proxy"
1038 "#,
1039 Ok(()),
1040 ),
1041 (
1042 r#"
1043 # One obfs4 bridge with unmanaged transport.
1044 [bridges]
1045 enabled = true
1046 bridges = [
1047 "obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1",
1048 ]
1049 [[bridges.transports]]
1050 protocols = ["obfs4"]
1051 proxy_addr = "127.0.0.1:31337"
1052 "#,
1053 Ok(()),
1054 ),
1055 (
1056 r#"
1057 # Transport is both managed and unmanaged.
1058 [[bridges.transports]]
1059 protocols = ["obfs4"]
1060 path = "obfsproxy"
1061 proxy_addr = "127.0.0.1:9999"
1062 "#,
1063 Err("Cannot provide both path and proxy_addr"),
1064 ),
1065 (
1066 r#"
1067 # One obfs4 bridge and non-PT bridge
1068 [bridges]
1069 enabled = false
1070 bridges = [
1071 "192.0.2.83:80 $0bac39417268b96b9f514ef763fa6fba1a788956",
1072 "obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1",
1073 ]
1074 [[bridges.transports]]
1075 protocols = ["obfs4"]
1076 path = "obfs4proxy"
1077 "#,
1078 Ok(()),
1079 ),
1080 (
1081 r#"
1082 # One obfs4 and non-PT bridge with no transport
1083 [bridges]
1084 enabled = true
1085 bridges = [
1086 "192.0.2.83:80 $0bac39417268b96b9f514ef763fa6fba1a788956",
1087 "obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1",
1088 ]
1089 "#,
1090 Ok(()),
1091 ),
1092 (
1093 r#"
1094 # One obfs4 bridge with no transport
1095 [bridges]
1096 enabled = true
1097 bridges = [
1098 "obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1",
1099 ]
1100 "#,
1101 Err("all bridges unusable due to lack of corresponding pluggable transport"),
1102 ),
1103 (
1104 r#"
1105 # One obfs4 bridge with no transport but bridges are disabled
1106 [bridges]
1107 enabled = false
1108 bridges = [
1109 "obfs4 bridge.example.net:80 $0bac39417268b69b9f514e7f63fa6fba1a788958 ed25519:dGhpcyBpcyBbpmNyZWRpYmx5IHNpbGx5ISEhISEhISA iat-mode=1",
1110 ]
1111 "#,
1112 Ok(()),
1113 ),
1114 (
1115 r#"
1116 # One non-PT bridge with a redundant transports section
1117 [bridges]
1118 enabled = false
1119 bridges = [
1120 "192.0.2.83:80 $0bac39417268b96b9f514ef763fa6fba1a788956",
1121 ]
1122 [[bridges.transports]]
1123 protocols = ["obfs4"]
1124 path = "obfs4proxy"
1125 "#,
1126 Ok(()),
1127 ),
1128 ];
1129
1130 for (test_case, expected) in test_cases.iter() {
1131 chk(&from_toml(test_case), *expected);
1132 }
1133 }
1134}