1use std::fmt::{self, Display};
4use std::iter;
5use std::net::SocketAddr;
6use std::str::FromStr;
7use std::sync::Arc;
8
9use itertools::{Itertools, chain};
10use serde::{Deserialize, Serialize};
11
12use tor_basic_utils::derive_serde_raw;
13use tor_config::define_list_builder_accessors;
14use tor_config::{ConfigBuildError, impl_standard_builder};
15use tor_linkspec::RelayId;
16use tor_linkspec::TransportId;
17use tor_linkspec::{ChanTarget, ChannelMethod, HasChanMethod};
18use tor_linkspec::{HasAddrs, HasRelayIds, RelayIdRef, RelayIdType};
19use tor_llcrypto::pk::{ed25519::Ed25519Identity, rsa::RsaIdentity};
20
21use tor_linkspec::BridgeAddr;
22
23#[cfg(feature = "pt-client")]
24use tor_linkspec::{PtTarget, PtTargetAddr};
25
26mod err;
27pub use err::BridgeParseError;
28
29#[derive(Debug, Clone, Eq, PartialEq, Hash)]
78pub struct BridgeConfig(Arc<Inner>);
79
80#[derive(Debug, Clone, Eq, PartialEq, Hash)]
82struct Inner {
83 addrs: ChannelMethod,
90
91 rsa_id: RsaIdentity,
93
94 ed_id: Option<Ed25519Identity>,
96}
97
98impl HasRelayIds for BridgeConfig {
99 fn identity(&self, key_type: RelayIdType) -> Option<RelayIdRef<'_>> {
100 match key_type {
101 RelayIdType::Ed25519 => self.0.ed_id.as_ref().map(RelayIdRef::Ed25519),
102 RelayIdType::Rsa => Some(RelayIdRef::Rsa(&self.0.rsa_id)),
103 _ => None,
104 }
105 }
106}
107
108impl HasChanMethod for BridgeConfig {
109 fn chan_method(&self) -> ChannelMethod {
110 self.0.addrs.clone()
111 }
112}
113
114impl HasAddrs for BridgeConfig {
115 fn addrs(&self) -> impl Iterator<Item = SocketAddr> {
116 self.0.addrs.addrs()
117 }
118}
119
120impl ChanTarget for BridgeConfig {}
121
122derive_serde_raw! {
123#[derive(Deserialize, Serialize, Default, Clone, Debug)]
131#[serde(try_from="BridgeConfigBuilderSerde", into="BridgeConfigBuilderSerde")]
132#[cfg_attr(test, derive(Eq, PartialEq))]
133pub struct BridgeConfigBuilder = "BridgeConfigBuilder" {
134 transport: Option<String>,
138
139 addrs: Option<Vec<BridgeAddr>>,
143
144 ids: Option<Vec<RelayId>>,
148
149 settings: Option<Vec<(String, String)>>,
151}
152}
153impl_standard_builder! { BridgeConfig: !Default }
154
155#[derive(Serialize, Deserialize)]
157#[serde(untagged)]
158enum BridgeConfigBuilderSerde {
159 BridgeLine(String),
161 Dict(#[serde(with = "BridgeConfigBuilder_Raw")] BridgeConfigBuilder),
163}
164
165impl TryFrom<BridgeConfigBuilderSerde> for BridgeConfigBuilder {
166 type Error = BridgeParseError;
167 fn try_from(input: BridgeConfigBuilderSerde) -> Result<Self, Self::Error> {
168 use BridgeConfigBuilderSerde::*;
169 match input {
170 BridgeLine(s) => s.parse(),
171 Dict(d) => Ok(d),
172 }
173 }
174}
175
176impl From<BridgeConfigBuilder> for BridgeConfigBuilderSerde {
177 fn from(input: BridgeConfigBuilder) -> BridgeConfigBuilderSerde {
178 use BridgeConfigBuilderSerde::*;
179 match input.build() {
181 Ok(bridge) => BridgeLine(bridge.to_string()),
182 Err(_) => Dict(input),
183 }
184 }
185}
186
187impl BridgeConfigBuilder {
188 pub fn transport(&mut self, transport: impl Into<String>) -> &mut Self {
197 self.transport = Some(transport.into());
198 self
199 }
200
201 pub fn direct(&mut self) -> &mut Self {
203 self.transport("")
204 }
205
206 pub fn push_setting(&mut self, k: impl Into<String>, v: impl Into<String>) -> &mut Self {
208 self.settings().push((k.into(), v.into()));
209 self
210 }
211
212 pub fn get_transport(&self) -> Option<&str> {
217 self.transport.as_deref()
218 }
219}
220
221impl BridgeConfigBuilder {
222 pub fn build(&self) -> Result<BridgeConfig, ConfigBuildError> {
224 let transport = self.transport.as_deref().unwrap_or_default();
225 let addrs = self.addrs.as_deref().unwrap_or_default();
226 let settings = self.settings.as_deref().unwrap_or_default();
227
228 let inconsist_transp = |field: &str, problem: &str| ConfigBuildError::Inconsistent {
230 fields: vec![field.into(), "transport".into()],
231 problem: problem.into(),
232 };
233 let unsupported =
234 |field: String, problem: &dyn Display| ConfigBuildError::NoCompileTimeSupport {
235 field,
236 problem: problem.to_string(),
237 };
238 #[cfg_attr(not(feature = "pt-client"), allow(unused_variables))]
239 let invalid = |field: String, problem: &dyn Display| ConfigBuildError::Invalid {
240 field,
241 problem: problem.to_string(),
242 };
243
244 let transp: TransportId = transport
245 .parse()
246 .map_err(|e| invalid("transport".into(), &e))?;
247
248 let addrs = match () {
251 () if transp.is_builtin() => {
252 if !settings.is_empty() {
253 return Err(inconsist_transp(
254 "settings",
255 "Specified `settings` for a direct bridge connection",
256 ));
257 }
258 #[allow(clippy::unnecessary_filter_map)] let addrs = addrs.iter().filter_map(|ba| {
260 #[allow(clippy::redundant_pattern_matching)] if let Some(sa) = ba.as_socketaddr() {
262 Some(Ok(*sa))
263 } else if let Some(_) = ba.as_host_port() {
264 Some(Err(
265 "`addrs` contains hostname and port, but only numeric addresses are supported for a direct bridge connection",
266 ))
267 } else {
268 unreachable!("BridgeAddr is neither addr nor named")
269 }
270 }).collect::<Result<Vec<SocketAddr>,&str>>().map_err(|problem| inconsist_transp(
271 "addrs",
272 problem,
273 ))?;
274 if addrs.is_empty() {
275 return Err(inconsist_transp(
276 "addrs",
277 "Missing `addrs` for a direct bridge connection",
278 ));
279 }
280 ChannelMethod::Direct(addrs)
281 }
282
283 #[cfg(feature = "pt-client")]
284 () if transp.as_pluggable().is_some() => {
285 let transport = transp.into_pluggable().expect("became not pluggable!");
286 let addr = match addrs {
287 [] => PtTargetAddr::None,
288 [addr] => Some(addr.clone()).into(),
289 [_, _, ..] => {
290 return Err(inconsist_transp(
291 "addrs",
292 "Transport (non-direct bridge) only supports a single nominal address",
293 ));
294 }
295 };
296 let mut target = PtTarget::new(transport, addr);
297 for (i, (k, v)) in settings.iter().enumerate() {
298 target
300 .push_setting(k, v)
301 .map_err(|e| invalid(format!("settings.{}", i), &e))?;
302 }
303 ChannelMethod::Pluggable(target)
304 }
305
306 () => {
307 return Err(unsupported(
311 "transport".into(),
312 &format_args!(
313 "support for selected transport '{}' disabled in tor-guardmgr cargo features",
314 transp
315 ),
316 ));
317 }
318 };
319
320 let mut rsa_id = None;
321 let mut ed_id = None;
322
323 fn store_id<T: Clone>(
325 u: &mut Option<T>,
326 desc: &str,
327 v: &T,
328 ) -> Result<(), ConfigBuildError> {
329 if u.is_some() {
330 Err(ConfigBuildError::Invalid {
331 field: "ids".into(),
332 problem: format!("multiple different ids of the same type ({})", desc),
333 })
334 } else {
335 *u = Some(v.clone());
336 Ok(())
337 }
338 }
339
340 for (i, id) in self.ids.as_deref().unwrap_or_default().iter().enumerate() {
341 match id {
342 RelayId::Rsa(rsa) => store_id(&mut rsa_id, "RSA", rsa)?,
343 RelayId::Ed25519(ed) => store_id(&mut ed_id, "ed25519", ed)?,
344 other => {
345 return Err(unsupported(
346 format!("ids.{}", i),
347 &format_args!("unsupported bridge id type {}", other.id_type()),
348 ));
349 }
350 }
351 }
352
353 let rsa_id = rsa_id.ok_or_else(|| ConfigBuildError::Invalid {
354 field: "ids".into(),
355 problem: "need an RSA identity".into(),
356 })?;
357
358 Ok(BridgeConfig(
359 Inner {
360 addrs,
361 rsa_id,
362 ed_id,
363 }
364 .into(),
365 ))
366 }
367}
368
369impl FromStr for BridgeConfigBuilder {
377 type Err = BridgeParseError;
378
379 fn from_str(s: &str) -> Result<Self, Self::Err> {
380 let bridge: Inner = s.parse()?;
381
382 let (transport, addrs, settings) = match bridge.addrs {
383 ChannelMethod::Direct(addrs) => (
384 "".into(),
385 addrs
386 .into_iter()
387 .map(BridgeAddr::new_addr_from_sockaddr)
388 .collect(),
389 vec![],
390 ),
391 #[cfg(feature = "pt-client")]
392 ChannelMethod::Pluggable(target) => {
393 let (transport, addr, settings) = target.into_parts();
394 let addr: Option<BridgeAddr> = addr.into();
395 let addrs = addr.into_iter().collect_vec();
396 (transport.to_string(), addrs, settings.into_inner())
400 }
401 other => {
402 return Err(BridgeParseError::UnsupportedChannelMethod {
403 method: Box::new(other),
404 });
405 }
406 };
407
408 let ids = chain!(
409 iter::once(bridge.rsa_id.into()),
410 bridge.ed_id.into_iter().map(Into::into),
411 )
412 .collect_vec();
413
414 Ok(BridgeConfigBuilder {
415 transport: Some(transport),
416 addrs: Some(addrs),
417 settings: Some(settings),
418 ids: Some(ids),
419 })
420 }
421}
422
423define_list_builder_accessors! {
424 struct BridgeConfigBuilder {
425 pub addrs: [BridgeAddr],
426 pub ids: [RelayId],
427 pub settings: [(String,String)],
428 }
429}
430
431impl FromStr for BridgeConfig {
432 type Err = BridgeParseError;
433
434 fn from_str(s: &str) -> Result<Self, Self::Err> {
435 let inner = s.parse()?;
436 Ok(BridgeConfig(Arc::new(inner)))
437 }
438}
439
440impl FromStr for Inner {
441 type Err = BridgeParseError;
442
443 fn from_str(s: &str) -> Result<Self, Self::Err> {
444 use BridgeParseError as BPE;
445
446 let mut s = s.trim().split_ascii_whitespace().peekable();
447
448 let bridge_word = s.peek().ok_or(BPE::Empty)?;
454 if bridge_word.eq_ignore_ascii_case("bridge") {
455 s.next();
456 }
457
458 #[cfg_attr(not(feature = "pt-client"), allow(unused_mut))]
462 let mut method = {
463 let word = s.next().ok_or(BPE::Empty)?;
464 if word.contains(':') {
465 let addr = word.parse().map_err(|addr_error| BPE::InvalidIpAddrOrPt {
467 word: word.to_string(),
468 addr_error,
469 })?;
470 ChannelMethod::Direct(vec![addr])
471 } else {
472 #[cfg(not(feature = "pt-client"))]
473 return Err(BPE::PluggableTransportsNotSupported {
474 word: word.to_string(),
475 });
476
477 #[cfg(feature = "pt-client")]
478 {
479 let pt_name = word.parse().map_err(|pt_error| BPE::InvalidPtOrAddr {
480 word: word.to_string(),
481 pt_error,
482 })?;
483 let addr = s
484 .next()
485 .map(|s| s.parse())
486 .transpose()
487 .map_err(|source| BPE::InvalidIPtHostAddr {
488 word: word.to_string(),
489 source,
490 })?
491 .unwrap_or(PtTargetAddr::None);
492 ChannelMethod::Pluggable(PtTarget::new(pt_name, addr))
493 }
494 }
495 };
496
497 let mut rsa_id = None;
500 let mut ed_id = None;
501
502 while let Some(word) = s.peek() {
503 let check_several = |was_some| {
505 if was_some {
506 Err(BPE::MultipleIdentitiesOfSameType {
507 word: word.to_string(),
508 })
509 } else {
510 Ok(())
511 }
512 };
513
514 match word.parse() {
515 Err(id_error) => {
516 if word.contains('=') {
517 break;
519 }
520 return Err(BPE::InvalidIdentityOrParameter {
521 word: word.to_string(),
522 id_error,
523 });
524 }
525 Ok(RelayId::Ed25519(id)) => check_several(ed_id.replace(id).is_some())?,
526 Ok(RelayId::Rsa(id)) => check_several(rsa_id.replace(id).is_some())?,
527 Ok(_) => {
528 return Err(BPE::UnsupportedIdentityType {
529 word: word.to_string(),
530 })?;
531 }
532 }
533 s.next();
534 }
535
536 #[cfg(not(feature = "pt-client"))]
540 if s.next().is_some() {
541 return Err(BPE::DirectParametersNotAllowed);
542 }
543
544 #[cfg(feature = "pt-client")]
545 for word in s {
546 let (k, v) = word.split_once('=').ok_or_else(|| BPE::InvalidPtKeyValue {
547 word: word.to_string(),
548 })?;
549
550 match &mut method {
551 ChannelMethod::Direct(_) => return Err(BPE::DirectParametersNotAllowed),
552 ChannelMethod::Pluggable(t) => t.push_setting(k, v).map_err(|source| {
553 BPE::InvalidPluggableTransportSetting {
554 word: word.to_string(),
555 source,
556 }
557 })?,
558 other => panic!("made ourselves an unsupported ChannelMethod {:?}", other),
559 }
560 }
561
562 let rsa_id = rsa_id.ok_or(BPE::NoRsaIdentity)?;
563 Ok(Inner {
564 addrs: method,
565 rsa_id,
566 ed_id,
567 })
568 }
569}
570
571impl Display for BridgeConfig {
572 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
573 let Inner {
574 addrs,
575 rsa_id,
576 ed_id,
577 } = &*self.0;
578
579 let settings = match addrs {
583 ChannelMethod::Direct(a) => {
584 if a.len() == 1 {
585 write!(f, "{}", a[0])?;
586 } else {
587 panic!("Somehow created a Bridge config with multiple addrs.");
588 }
589 None
590 }
591
592 #[cfg(feature = "pt-client")]
593 ChannelMethod::Pluggable(target) => {
594 write!(f, "{} {}", target.transport(), target.addr())?;
595 Some(target.settings())
596 }
597
598 _ => {
599 write!(f, "[unsupported channel method, cannot display properly]")?;
601 return Ok(());
602 }
603 };
604
605 write!(f, " {}", rsa_id)?;
608 if let Some(ed_id) = ed_id {
609 write!(f, " ed25519:{}", ed_id)?;
610 }
611
612 #[cfg(not(feature = "pt-client"))]
616 let _: Option<()> = settings;
617
618 #[cfg(feature = "pt-client")]
619 for (k, v) in settings.into_iter().flatten() {
620 write!(f, " {}={}", k, v)?;
621 }
622
623 Ok(())
624 }
625}
626
627#[cfg(test)]
628mod test {
629 #![allow(clippy::bool_assert_comparison)]
631 #![allow(clippy::clone_on_copy)]
632 #![allow(clippy::dbg_macro)]
633 #![allow(clippy::mixed_attributes_style)]
634 #![allow(clippy::print_stderr)]
635 #![allow(clippy::print_stdout)]
636 #![allow(clippy::single_char_pattern)]
637 #![allow(clippy::unwrap_used)]
638 #![allow(clippy::unchecked_time_subtraction)]
639 #![allow(clippy::useless_vec)]
640 #![allow(clippy::needless_pass_by_value)]
641 #![allow(clippy::string_slice)] use super::*;
644
645 #[cfg(feature = "pt-client")]
646 fn mk_pt_target(name: &str, addr: PtTargetAddr, params: &[(&str, &str)]) -> ChannelMethod {
647 let mut target = PtTarget::new(name.parse().unwrap(), addr);
648 for &(k, v) in params {
649 target.push_setting(k, v).unwrap();
650 }
651 ChannelMethod::Pluggable(target)
652 }
653
654 fn mk_direct(s: &str) -> ChannelMethod {
655 ChannelMethod::Direct(vec![s.parse().unwrap()])
656 }
657
658 fn mk_rsa(s: &str) -> RsaIdentity {
659 match s.parse().unwrap() {
660 RelayId::Rsa(y) => y,
661 _ => panic!("not rsa {:?}", s),
662 }
663 }
664 fn mk_ed(s: &str) -> Ed25519Identity {
665 match s.parse().unwrap() {
666 RelayId::Ed25519(y) => y,
667 _ => panic!("not ed {:?}", s),
668 }
669 }
670
671 #[test]
672 fn bridge_lines() {
673 let chk = |sl: &[&str], exp: Inner| {
674 for s in sl {
675 let got: BridgeConfig = s.parse().expect(s);
676 assert_eq!(*got.0, exp, "{:?}", s);
677
678 let display = got.to_string();
679 assert_eq!(display, sl[0]);
680 }
681 };
682
683 let chk_e = |sl: &[&str], exp: &str| {
684 for s in sl {
685 let got: Result<BridgeConfig, _> = s.parse();
686 let got = got.expect_err(s);
687 let got_s = got.to_string();
688 assert!(
689 got_s.contains(exp),
690 "{:?} => {:?} ({}) not {}",
691 s,
692 &got,
693 &got_s,
694 exp
695 );
696 }
697 };
698
699 #[cfg(feature = "pt-client")]
701 chk(
702 &[
703 "obfs4 38.229.33.83:80 $0bac39417268b96b9f514e7f63fa6fba1a788955 cert=VwEFpk9F/UN9JED7XpG1XOjm/O8ZCXK80oPecgWnNDZDv5pdkhq1Op iat-mode=1",
704 "obfs4 38.229.33.83:80 0BAC39417268B96B9F514E7F63FA6FBA1A788955 cert=VwEFpk9F/UN9JED7XpG1XOjm/O8ZCXK80oPecgWnNDZDv5pdkhq1Op iat-mode=1",
705 "Bridge obfs4 38.229.33.83:80 0BAC39417268B96B9F514E7F63FA6FBA1A788955 cert=VwEFpk9F/UN9JED7XpG1XOjm/O8ZCXK80oPecgWnNDZDv5pdkhq1Op iat-mode=1",
706 ],
707 Inner {
708 addrs: mk_pt_target(
709 "obfs4",
710 PtTargetAddr::IpPort("38.229.33.83:80".parse().unwrap()),
711 &[
712 (
713 "cert",
714 "VwEFpk9F/UN9JED7XpG1XOjm/O8ZCXK80oPecgWnNDZDv5pdkhq1Op",
715 ),
716 ("iat-mode", "1"),
717 ],
718 ),
719 rsa_id: mk_rsa("0BAC39417268B96B9F514E7F63FA6FBA1A788955"),
720 ed_id: None,
721 },
722 );
723
724 #[cfg(feature = "pt-client")]
725 chk(
726 &[
727 "obfs4 some-host:80 $0bac39417268b96b9f514e7f63fa6fba1a788955 ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE iat-mode=1",
728 "obfs4 some-host:80 ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE 0BAC39417268B96B9F514E7F63FA6FBA1A788955 iat-mode=1",
729 ],
730 Inner {
731 addrs: mk_pt_target(
732 "obfs4",
733 PtTargetAddr::HostPort("some-host".into(), 80),
734 &[("iat-mode", "1")],
735 ),
736 rsa_id: mk_rsa("0BAC39417268B96B9F514E7F63FA6FBA1A788955"),
737 ed_id: Some(mk_ed("dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE")),
738 },
739 );
740
741 chk(
742 &[
743 "38.229.33.83:80 $0bac39417268b96b9f514e7f63fa6fba1a788955",
744 "Bridge 38.229.33.83:80 0BAC39417268B96B9F514E7F63FA6FBA1A788955",
745 ],
746 Inner {
747 addrs: mk_direct("38.229.33.83:80"),
748 rsa_id: mk_rsa("0BAC39417268B96B9F514E7F63FA6FBA1A788955"),
749 ed_id: None,
750 },
751 );
752
753 chk(
754 &[
755 "[2001:db8::42]:123 $0bac39417268b96b9f514e7f63fa6fba1a788955",
756 "[2001:0db8::42]:123 $0bac39417268b96b9f514e7f63fa6fba1a788955",
757 ],
758 Inner {
759 addrs: mk_direct("[2001:0db8::42]:123"),
760 rsa_id: mk_rsa("0BAC39417268B96B9F514E7F63FA6FBA1A788955"),
761 ed_id: None,
762 },
763 );
764
765 chk(
766 &[
767 "38.229.33.83:80 $0bac39417268b96b9f514e7f63fa6fba1a788955 ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE",
768 "38.229.33.83:80 ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE 0BAC39417268B96B9F514E7F63FA6FBA1A788955",
769 ],
770 Inner {
771 addrs: mk_direct("38.229.33.83:80"),
772 rsa_id: mk_rsa("0BAC39417268B96B9F514E7F63FA6FBA1A788955"),
773 ed_id: Some(mk_ed("dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE")),
774 },
775 );
776
777 chk_e(
778 &[
779 "38.229.33.83:80 ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE",
780 "Bridge 38.229.33.83:80 ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE",
781 ],
782 "lacks specification of RSA identity key",
783 );
784
785 chk_e(&["", "bridge"], "Bridge line was empty");
786
787 chk_e(
788 &["999.329.33.83:80 0BAC39417268B96B9F514E7F63FA6FBA1A788955"],
789 r#"Cannot parse "999.329.33.83:80" as direct bridge IpAddress:ORPort"#,
792 );
793
794 chk_e(
795 &[
796 "38.229.33.83:80 0BAC39417268B96B9F514E7F63FA6FBA1A788955 key=value",
797 "Bridge 38.229.33.83:80 0BAC39417268B96B9F514E7F63FA6FBA1A788955 key=value",
798 ],
799 "Parameters supplied but not valid without a pluggable transport",
800 );
801
802 chk_e(
803 &[
804 "bridge bridge some-host:80 0BAC39417268B96B9F514E7F63FA6FBA1A788955",
805 "yikes! some-host:80 0BAC39417268B96B9F514E7F63FA6FBA1A788955",
806 ],
807 #[cfg(feature = "pt-client")]
808 r" is not a valid pluggable transport ID), nor as direct bridge IpAddress:ORPort",
809 #[cfg(not(feature = "pt-client"))]
810 "is not an IpAddress:ORPort), but support disabled in cargo features",
811 );
812
813 #[cfg(feature = "pt-client")]
814 chk_e(
815 &["obfs4 garbage 0BAC39417268B96B9F514E7F63FA6FBA1A788955"],
816 "as pluggable transport Host:ORPort",
817 );
818
819 #[cfg(feature = "pt-client")]
820 chk_e(
821 &["obfs4 some-host:80 0BAC39417268B96B9F514E7F63FA6FBA1A788955 key=value garbage"],
822 r#"Expected PT key=value parameter, found "garbage" (which lacks an equals sign"#,
823 );
824
825 #[cfg(feature = "pt-client")]
826 chk_e(
827 &["obfs4 some-host:80 garbage"],
828 r#"Cannot parse "garbage" as identity key (Invalid base64 data), or PT key=value"#,
829 );
830
831 chk_e(
832 &[
833 "38.229.33.83:80 0BAC39417268B96B9F514E7F63FA6FBA1A788955 23AC39417268B96B9F514E7F63FA6FBA1A788955",
834 "38.229.33.83:80 0BAC39417268B96B9F514E7F63FA6FBA1A788955 dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE xGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE",
835 ],
836 "More than one identity of the same type specified",
837 );
838 }
839
840 #[test]
841 fn config_api() {
842 let chk_bridgeline = |line: &str, jsons: &[&str], f: &dyn Fn(&mut BridgeConfigBuilder)| {
843 eprintln!(" ---- chk_bridgeline ----\n{}", line);
844
845 let mut bcb = BridgeConfigBuilder::default();
846 f(&mut bcb);
847 let built = bcb.build().unwrap();
848 assert_eq!(&built, &line.parse::<BridgeConfig>().unwrap());
849
850 let parsed_b: BridgeConfigBuilder = line.parse().unwrap();
851 assert_eq!(&built, &parsed_b.build().unwrap());
852
853 let re_serialized = serde_json::to_value(&bcb).unwrap();
854 assert_eq!(re_serialized, serde_json::Value::String(line.to_string()));
855
856 for json in jsons {
857 let from_dict: BridgeConfigBuilder = serde_json::from_str(json).unwrap();
858 assert_eq!(&from_dict, &bcb);
859 assert_eq!(&built, &from_dict.build().unwrap());
860 }
861 };
862
863 chk_bridgeline(
864 "38.229.33.83:80 $0bac39417268b96b9f514e7f63fa6fba1a788955 ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE",
865 &[r#"{
866 "addrs": ["38.229.33.83:80"],
867 "ids": ["ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE",
868 "$0bac39417268b96b9f514e7f63fa6fba1a788955"]
869 }"#],
870 &|bcb| {
871 bcb.addrs().push("38.229.33.83:80".parse().unwrap());
872 bcb.ids().push(
873 "ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE"
874 .parse()
875 .unwrap(),
876 );
877 bcb.ids()
878 .push("$0bac39417268b96b9f514e7f63fa6fba1a788955".parse().unwrap());
879 },
880 );
881
882 #[cfg(feature = "pt-client")]
883 chk_bridgeline(
884 "obfs4 some-host:80 $0bac39417268b96b9f514e7f63fa6fba1a788955 iat-mode=1",
885 &[r#"{
886 "transport": "obfs4",
887 "addrs": ["some-host:80"],
888 "ids": ["$0bac39417268b96b9f514e7f63fa6fba1a788955"],
889 "settings": [["iat-mode", "1"]]
890 }"#],
891 &|bcb| {
892 bcb.transport("obfs4");
893 bcb.addrs().push("some-host:80".parse().unwrap());
894 bcb.ids()
895 .push("$0bac39417268b96b9f514e7f63fa6fba1a788955".parse().unwrap());
896 bcb.push_setting("iat-mode", "1");
897 },
898 );
899
900 let chk_broken = |emsg: &str, jsons: &[&str], f: &dyn Fn(&mut BridgeConfigBuilder)| {
901 eprintln!(" ---- chk_bridgeline ----\n{:?}", emsg);
902
903 let mut bcb = BridgeConfigBuilder::default();
904 f(&mut bcb);
905
906 for json in jsons {
907 let from_dict: BridgeConfigBuilder = serde_json::from_str(json).unwrap();
908 assert_eq!(&from_dict, &bcb);
909 }
910
911 let err = bcb.build().expect_err("succeeded?!");
912 let got_emsg = err.to_string();
913 assert!(
914 got_emsg.contains(emsg),
915 "wrong error message: got_emsg={:?} err={:?} expected={:?}",
916 &got_emsg,
917 &err,
918 emsg,
919 );
920
921 let toml_got = toml::to_string(&bcb).unwrap();
928 let json_got: serde_json::Value = toml::from_str(&toml_got).unwrap();
929 let json_exp: serde_json::Value = serde_json::from_str(jsons[0]).unwrap();
930 assert_eq!(&json_got, &json_exp);
931 };
932
933 chk_broken(
934 "Specified `settings` for a direct bridge connection",
935 &[r#"{
936 "settings": [["hi","there"]]
937 }"#],
938 &|bcb| {
939 bcb.settings().push(("hi".into(), "there".into()));
940 },
941 );
942
943 #[cfg(not(feature = "pt-client"))]
944 chk_broken(
945 "Not compiled with pluggable transport support",
946 &[r#"{
947 "transport": "obfs4"
948 }"#],
949 &|bcb| {
950 bcb.transport("obfs4");
951 },
952 );
953
954 #[cfg(feature = "pt-client")]
955 chk_broken(
956 "only numeric addresses are supported for a direct bridge connection",
957 &[r#"{
958 "transport": "bridge",
959 "addrs": ["some-host:80"]
960 }"#],
961 &|bcb| {
962 bcb.transport("bridge");
963 bcb.addrs().push("some-host:80".parse().unwrap());
964 },
965 );
966
967 chk_broken(
968 "Missing `addrs` for a direct bridge connection",
969 &[r#"{
970 "transport": "-"
971 }"#],
972 &|bcb| {
973 bcb.transport("-");
974 },
975 );
976
977 #[cfg(feature = "pt-client")]
978 chk_broken(
979 "only supports a single nominal address",
980 &[r#"{
981 "transport": "obfs4",
982 "addrs": ["some-host:80", "38.229.33.83:80"]
983 }"#],
984 &|bcb| {
985 bcb.transport("obfs4");
986 bcb.addrs().push("some-host:80".parse().unwrap());
987 bcb.addrs().push("38.229.33.83:80".parse().unwrap());
988 },
989 );
990
991 chk_broken(
992 "multiple different ids of the same type (ed25519)",
993 &[r#"{
994 "addrs": ["38.229.33.83:80"],
995 "ids": ["ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE",
996 "ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISA"]
997 }"#],
998 &|bcb| {
999 bcb.addrs().push("38.229.33.83:80".parse().unwrap());
1000 bcb.ids().push(
1001 "ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE"
1002 .parse()
1003 .unwrap(),
1004 );
1005 bcb.ids().push(
1006 "ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISA"
1007 .parse()
1008 .unwrap(),
1009 );
1010 },
1011 );
1012
1013 chk_broken(
1014 "need an RSA identity",
1015 &[r#"{
1016 "addrs": ["38.229.33.83:80"],
1017 "ids": ["ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE"]
1018 }"#],
1019 &|bcb| {
1020 bcb.addrs().push("38.229.33.83:80".parse().unwrap());
1021 bcb.ids().push(
1022 "ed25519:dGhpcyBpcyBpbmNyZWRpYmx5IHNpbGx5ISEhISEhISE"
1023 .parse()
1024 .unwrap(),
1025 );
1026 },
1027 );
1028 }
1029}