1use std::collections::BTreeMap;
4use std::fmt::{self, Debug, Display};
5use std::ops::Range;
6use std::result::Result as StdResult;
7use std::str::FromStr;
8
9use derive_more::From;
10use safelog::DisplayRedacted as _;
11use thiserror::Error;
12use tor_error::{Bug, internal, into_internal};
13use tor_hscrypto::pk::{HSID_ONION_SUFFIX, HsId, HsIdParseError};
14use tor_hscrypto::time::TimePeriod;
15use tor_persist::hsnickname::HsNickname;
16use tor_persist::slug::Slug;
17
18use crate::{ArtiPath, ArtiPathSyntaxError};
19
20#[macro_use]
22pub mod derive;
23
24#[derive(Clone, Debug, PartialEq, Eq, Hash, From, derive_more::Display)]
26#[non_exhaustive]
27pub enum KeyPath {
28 Arti(ArtiPath),
30 CTor(CTorPath),
32}
33
34#[derive(Clone, Debug, PartialEq, Eq, Hash, From)]
36pub struct ArtiPathRange(pub(crate) Range<usize>);
37
38impl ArtiPath {
39 pub fn matches(&self, pat: &KeyPathPattern) -> Option<Vec<ArtiPathRange>> {
60 use KeyPathPattern::*;
61
62 let pattern: &str = match pat {
63 Arti(pat) => pat.as_ref(),
64 _ => return None,
65 };
66
67 glob_match::glob_match_with_captures(pattern, self.as_ref())
68 .map(|res| res.into_iter().map(|r| r.into()).collect())
69 }
70}
71
72impl KeyPath {
73 pub fn matches(&self, pat: &KeyPathPattern) -> bool {
90 use KeyPathPattern::*;
91
92 match (self, pat) {
93 (KeyPath::Arti(p), Arti(_)) => p.matches(pat).is_some(),
94 (KeyPath::CTor(p), CTor(pat)) if p == pat => true,
95 _ => false,
96 }
97 }
98
99 pub fn arti(&self) -> Option<&ArtiPath> {
103 match self {
104 KeyPath::Arti(arti) => Some(arti),
105 KeyPath::CTor(_) => None,
106 }
107 }
108
109 pub fn ctor(&self) -> Option<&CTorPath> {
111 match self {
112 KeyPath::Arti(_) => None,
113 KeyPath::CTor(ctor) => Some(ctor),
114 }
115 }
116}
117
118pub trait KeySpecifierPattern {
125 fn new_any() -> Self
127 where
128 Self: Sized;
129
130 fn arti_pattern(&self) -> Result<KeyPathPattern, Bug>;
133}
134
135#[cfg(feature = "experimental-api")]
141pub trait CertSpecifierPattern {
142 type SubjectKeySpecifierPattern: KeySpecifierPattern;
148
149 fn new_any() -> Self
155 where
156 Self: Sized;
157
158 fn arti_pattern(&self) -> Result<KeyPathPattern, Bug>;
161}
162
163#[derive(Debug, Clone, thiserror::Error)]
170#[non_exhaustive]
171pub enum KeyPathError {
172 #[error("{err}")]
174 Arti {
175 path: ArtiPath,
177 err: ArtiPathError,
179 },
180
181 #[error("{err}")]
183 CTor {
184 path: CTorPath,
186 err: CTorPathError,
188 },
189
190 #[error("Internal error")]
192 Bug(#[from] tor_error::Bug),
193}
194
195#[derive(Debug, Clone, thiserror::Error)]
197#[non_exhaustive]
198pub enum ArtiPathError {
199 #[error("Path does not match expected pattern")]
201 PatternNotMatched,
202
203 #[error("ArtiPath is invalid")]
205 InvalidArtiPath(ArtiPathSyntaxError),
206
207 #[error("invalid string value for element of key path")]
215 InvalidKeyPathComponentValue {
216 #[source]
218 error: InvalidKeyPathComponentValue,
219 key: String,
223 value: Slug,
225 },
226
227 #[error("Internal error")]
229 Bug(#[from] tor_error::Bug),
230}
231
232#[derive(Debug, Clone, PartialEq, thiserror::Error)]
235#[non_exhaustive]
236pub enum CTorPathError {
237 #[error("C Tor path cannot be converted to {0}")]
239 KeySpecifierMismatch(String),
240
241 #[error("Key specifier {0} does not have a C Tor path")]
244 MissingCTorPath(String),
245}
246
247#[derive(Error, Clone, Debug)]
253#[non_exhaustive]
254pub enum InvalidKeyPathComponentValue {
255 #[error("{0}")]
264 Slug(String),
265
266 #[error("Internal error")]
271 Bug(#[from] tor_error::Bug),
272}
273
274#[derive(Debug, Clone, PartialEq, derive_builder::Builder, amplify::Getters)]
281pub struct KeyPathInfo {
282 summary: String,
286 role: String,
292 #[builder(default, setter(custom))]
299 extra_info: BTreeMap<String, String>,
300}
301
302impl KeyPathInfo {
303 pub fn builder() -> KeyPathInfoBuilder {
305 KeyPathInfoBuilder::default()
306 }
307}
308
309impl KeyPathInfoBuilder {
310 pub fn set_all_extra_info(
314 &mut self,
315 all_extra_info: impl Iterator<Item = (String, String)>,
316 ) -> &mut Self {
317 self.extra_info = Some(all_extra_info.collect());
318 self
319 }
320
321 pub fn extra_info(&mut self, key: impl Into<String>, value: impl Into<String>) -> &mut Self {
325 let extra_info = self.extra_info.get_or_insert(Default::default());
326 extra_info.insert(key.into(), value.into());
327 self
328 }
329}
330
331pub trait KeyPathInfoExtractor: Send + Sync {
336 fn describe(&self, path: &KeyPath) -> StdResult<KeyPathInfo, KeyPathError>;
338}
339
340#[macro_export]
342macro_rules! register_key_info_extractor {
343 ($kv:expr) => {{
344 $crate::inventory::submit!(&$kv as &dyn $crate::KeyPathInfoExtractor);
345 }};
346}
347
348#[derive(Clone, Debug, PartialEq, Eq, Hash)]
368#[non_exhaustive]
369pub enum KeyPathPattern {
370 Arti(String),
372 CTor(CTorPath),
374}
375
376#[derive(Clone, Debug, PartialEq, Eq, Hash, derive_more::Display)] #[non_exhaustive]
379pub enum CTorPath {
380 #[display("HsClientDescEncKeypair({})", hs_id.display_unredacted())]
389 HsClientDescEncKeypair {
390 hs_id: HsId,
392 },
393 #[display("hs_ed25519_public_key")]
395 HsIdPublicKey {
396 nickname: HsNickname,
398 },
399 #[display("hs_ed25519_secret_key")]
401 HsIdKeypair {
402 nickname: HsNickname,
404 },
405}
406
407pub trait KeySpecifier {
411 fn arti_path(&self) -> StdResult<ArtiPath, ArtiPathUnavailableError>;
415
416 fn ctor_path(&self) -> Option<CTorPath>;
421
422 fn keypair_specifier(&self) -> Option<Box<dyn KeySpecifier>>;
425}
426
427pub trait KeySpecifierComponent {
440 fn to_slug(&self) -> Result<Slug, Bug>;
442 fn from_slug(s: &Slug) -> StdResult<Self, InvalidKeyPathComponentValue>
444 where
445 Self: Sized;
446 fn fmt_pretty(&self, f: &mut fmt::Formatter) -> fmt::Result;
450}
451
452#[derive(Error, Debug, Clone)]
457#[non_exhaustive]
458pub enum ArtiPathUnavailableError {
459 #[error("Internal error")]
461 Bug(#[from] tor_error::Bug),
462
463 #[error("ArtiPath unavailable")]
468 ArtiPathUnavailable,
469}
470
471impl KeySpecifier for ArtiPath {
472 fn arti_path(&self) -> StdResult<ArtiPath, ArtiPathUnavailableError> {
473 Ok(self.clone())
474 }
475
476 fn ctor_path(&self) -> Option<CTorPath> {
477 None
478 }
479
480 fn keypair_specifier(&self) -> Option<Box<dyn KeySpecifier>> {
481 None
482 }
483}
484
485impl KeySpecifier for CTorPath {
486 fn arti_path(&self) -> StdResult<ArtiPath, ArtiPathUnavailableError> {
487 Err(ArtiPathUnavailableError::ArtiPathUnavailable)
488 }
489
490 fn ctor_path(&self) -> Option<CTorPath> {
491 Some(self.clone())
492 }
493
494 fn keypair_specifier(&self) -> Option<Box<dyn KeySpecifier>> {
495 None
496 }
497}
498
499impl KeySpecifier for KeyPath {
500 fn arti_path(&self) -> StdResult<ArtiPath, ArtiPathUnavailableError> {
501 match self {
502 KeyPath::Arti(p) => p.arti_path(),
503 KeyPath::CTor(p) => p.arti_path(),
504 }
505 }
506
507 fn ctor_path(&self) -> Option<CTorPath> {
508 match self {
509 KeyPath::Arti(p) => p.ctor_path(),
510 KeyPath::CTor(p) => p.ctor_path(),
511 }
512 }
513
514 fn keypair_specifier(&self) -> Option<Box<dyn KeySpecifier>> {
515 None
516 }
517}
518
519impl KeySpecifierComponent for TimePeriod {
520 fn to_slug(&self) -> Result<Slug, Bug> {
521 Slug::new(format!(
522 "{}_{}_{}",
523 self.interval_num(),
524 self.length(),
525 self.epoch_offset_in_sec()
526 ))
527 .map_err(into_internal!("TP formatting went wrong"))
528 }
529
530 fn from_slug(s: &Slug) -> StdResult<Self, InvalidKeyPathComponentValue>
531 where
532 Self: Sized,
533 {
534 use itertools::Itertools;
535
536 let s = s.to_string();
537 #[allow(clippy::redundant_closure)] let err_ctx = |e: &str| InvalidKeyPathComponentValue::Slug(e.to_string());
539 let (interval, len, offset) = s
540 .split('_')
541 .collect_tuple()
542 .ok_or_else(|| err_ctx("invalid number of subcomponents"))?;
543
544 let length = len.parse().map_err(|_| err_ctx("invalid length"))?;
545 let interval_num = interval
546 .parse()
547 .map_err(|_| err_ctx("invalid interval_num"))?;
548 let offset_in_sec = offset
549 .parse()
550 .map_err(|_| err_ctx("invalid offset_in_sec"))?;
551
552 Ok(TimePeriod::from_parts(length, interval_num, offset_in_sec))
553 }
554
555 fn fmt_pretty(&self, f: &mut fmt::Formatter) -> fmt::Result {
556 Display::fmt(&self, f)
557 }
558}
559
560pub trait KeySpecifierComponentViaDisplayFromStr: Display + FromStr {}
568impl<T: KeySpecifierComponentViaDisplayFromStr> KeySpecifierComponent for T {
569 fn to_slug(&self) -> Result<Slug, Bug> {
570 self.to_string()
571 .try_into()
572 .map_err(into_internal!("Display generated bad Slug"))
573 }
574 fn from_slug(s: &Slug) -> Result<Self, InvalidKeyPathComponentValue>
575 where
576 Self: Sized,
577 {
578 s.as_str()
579 .parse()
580 .map_err(|_| internal!("slug cannot be parsed as component").into())
581 }
582 fn fmt_pretty(&self, f: &mut fmt::Formatter) -> fmt::Result {
583 Display::fmt(self, f)
584 }
585}
586
587impl KeySpecifierComponentViaDisplayFromStr for HsNickname {}
588
589impl KeySpecifierComponent for HsId {
590 fn to_slug(&self) -> StdResult<Slug, Bug> {
591 let hsid = self.display_unredacted().to_string();
595 let hsid_slug = hsid
596 .strip_suffix(HSID_ONION_SUFFIX)
597 .ok_or_else(|| internal!("HsId Display impl missing .onion suffix?!"))?;
598 hsid_slug
599 .to_owned()
600 .try_into()
601 .map_err(into_internal!("Display generated bad Slug"))
602 }
603
604 fn from_slug(s: &Slug) -> StdResult<Self, InvalidKeyPathComponentValue>
605 where
606 Self: Sized,
607 {
608 let onion = format!("{}{HSID_ONION_SUFFIX}", s.as_str());
617
618 onion
619 .parse()
620 .map_err(|e: HsIdParseError| InvalidKeyPathComponentValue::Slug(e.to_string()))
621 }
622
623 fn fmt_pretty(&self, f: &mut fmt::Formatter) -> fmt::Result {
624 Display::fmt(&self.display_redacted(), f)
625 }
626}
627
628struct KeySpecifierComponentPrettyHelper<'c>(&'c dyn KeySpecifierComponent);
630
631impl Display for KeySpecifierComponentPrettyHelper<'_> {
632 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
633 KeySpecifierComponent::fmt_pretty(self.0, f)
634 }
635}
636
637pub trait KeyCertificateSpecifier {
656 fn cert_denotators(&self) -> Vec<&dyn KeySpecifierComponent>;
665 fn subject_key_specifier(&self) -> &dyn KeySpecifier;
667}
668
669impl<T: KeyCertificateSpecifier + ?Sized> KeySpecifier for T {
670 fn arti_path(&self) -> StdResult<ArtiPath, ArtiPathUnavailableError> {
671 let subject_key_arti_path = self
672 .subject_key_specifier()
673 .arti_path()
674 .map_err(|_| internal!("subject key does not have an ArtiPath?!"))?;
675
676 let path =
677 ArtiPath::from_path_and_denotators(subject_key_arti_path, &self.cert_denotators())
678 .map_err(into_internal!("invalid certificate specifier"))?;
679
680 Ok(path)
681 }
682
683 fn ctor_path(&self) -> Option<CTorPath> {
684 None
686 }
687
688 fn keypair_specifier(&self) -> Option<Box<dyn KeySpecifier>> {
689 None
690 }
691}
692
693pub trait CTorKeySpecifier: KeySpecifier + Sized {
698 fn ctor_path(&self) -> Option<CTorPath>;
702
703 fn from_ctor_path(path: CTorPath) -> Result<Self, CTorPathError>;
708}
709
710#[cfg(test)]
711mod test {
712 #![allow(clippy::bool_assert_comparison)]
714 #![allow(clippy::clone_on_copy)]
715 #![allow(clippy::dbg_macro)]
716 #![allow(clippy::mixed_attributes_style)]
717 #![allow(clippy::print_stderr)]
718 #![allow(clippy::print_stdout)]
719 #![allow(clippy::single_char_pattern)]
720 #![allow(clippy::unwrap_used)]
721 #![allow(clippy::unchecked_time_subtraction)]
722 #![allow(clippy::useless_vec)]
723 #![allow(clippy::needless_pass_by_value)]
724 #![allow(clippy::string_slice)] use super::*;
727
728 use crate::test_utils::check_key_specifier;
729 use derive_deftly::Deftly;
730 use humantime::parse_rfc3339;
731 use itertools::Itertools;
732 use serde::{Deserialize, Serialize};
733 use std::fmt::Debug;
734 use std::time::Duration;
735
736 impl KeySpecifierComponentViaDisplayFromStr for usize {}
737 impl KeySpecifierComponentViaDisplayFromStr for String {}
738
739 impl KeySpecifierComponentViaDisplayFromStr for bool {}
742
743 fn test_time_period() -> TimePeriod {
744 TimePeriod::new(
745 Duration::from_secs(86400),
746 parse_rfc3339("2020-09-15T00:00:00Z").unwrap(),
747 Duration::from_secs(3600),
748 )
749 .unwrap()
750 }
751
752 #[test]
753 fn pretty_time_period() {
754 let tp = test_time_period();
755 assert_eq!(
756 KeySpecifierComponentPrettyHelper(&tp).to_string(),
757 "#18519 2020-09-14T01:00:00Z..+24:00",
758 );
759 }
760
761 #[test]
762 fn serde() {
763 #[derive(Serialize, Deserialize, Debug)]
767 struct T {
768 n: Slug,
769 }
770 let j = serde_json::from_str(r#"{ "n": "x" }"#).unwrap();
771 let t: T = serde_json::from_value(j).unwrap();
772 assert_eq!(&t.n.to_string(), "x");
773
774 assert_eq!(&serde_json::to_string(&t).unwrap(), r#"{"n":"x"}"#);
775
776 let j = serde_json::from_str(r#"{ "n": "!" }"#).unwrap();
777 let e = serde_json::from_value::<T>(j).unwrap_err();
778 assert!(
779 e.to_string()
780 .contains("character '!' (U+0021) is not allowed"),
781 "wrong msg {e:?}"
782 );
783 }
784
785 #[test]
786 fn define_key_specifier_with_fields_and_denotator() {
787 let tp = test_time_period();
788
789 #[derive(Deftly, Debug, PartialEq)]
790 #[derive_deftly(KeySpecifier)]
791 #[deftly(prefix = "encabulator")]
792 #[deftly(role = "marzlevane")]
793 #[deftly(summary = "test key")]
794 struct TestSpecifier {
795 kind: String,
797 base: String,
798 casing: String,
799 #[deftly(denotator)]
800 count: usize,
801 #[deftly(denotator)]
802 tp: TimePeriod,
803 }
804
805 let key_spec = TestSpecifier {
806 kind: "hydrocoptic".into(),
807 base: "waneshaft".into(),
808 casing: "logarithmic".into(),
809 count: 6,
810 tp,
811 };
812
813 check_key_specifier(
814 &key_spec,
815 "encabulator/hydrocoptic/waneshaft/logarithmic/marzlevane+6+18519_1440_3600",
816 );
817
818 let info = TestSpecifierInfoExtractor
819 .describe(&KeyPath::Arti(key_spec.arti_path().unwrap()))
820 .unwrap();
821
822 assert_eq!(
823 format!("{info:#?}"),
824 r##"
825KeyPathInfo {
826 summary: "test key",
827 role: "marzlevane",
828 extra_info: {
829 "base": "waneshaft",
830 "casing": "logarithmic",
831 "count": "6",
832 "kind": "hydrocoptic",
833 "tp": "#18519 2020-09-14T01:00:00Z..+24:00",
834 },
835}
836 "##
837 .trim()
838 );
839 }
840
841 #[test]
842 fn define_key_specifier_no_fields() {
843 #[derive(Deftly, Debug, PartialEq)]
844 #[derive_deftly(KeySpecifier)]
845 #[deftly(prefix = "encabulator")]
846 #[deftly(role = "marzlevane")]
847 #[deftly(summary = "test key")]
848 struct TestSpecifier {}
849
850 let key_spec = TestSpecifier {};
851
852 check_key_specifier(&key_spec, "encabulator/marzlevane");
853
854 assert_eq!(
855 TestSpecifierPattern {}.arti_pattern().unwrap(),
856 KeyPathPattern::Arti("encabulator/marzlevane".into())
857 );
858 }
859
860 #[test]
861 fn define_key_specifier_with_denotator() {
862 #[derive(Deftly, Debug, PartialEq)]
863 #[derive_deftly(KeySpecifier)]
864 #[deftly(prefix = "encabulator")]
865 #[deftly(role = "marzlevane")]
866 #[deftly(summary = "test key")]
867 struct TestSpecifier {
868 #[deftly(denotator)]
869 count: usize,
870 }
871
872 let key_spec = TestSpecifier { count: 6 };
873
874 check_key_specifier(&key_spec, "encabulator/marzlevane+6");
875
876 assert_eq!(
877 TestSpecifierPattern { count: None }.arti_pattern().unwrap(),
878 KeyPathPattern::Arti("encabulator/marzlevane+*".into())
879 );
880 }
881
882 #[test]
883 fn define_key_specifier_with_fields() {
884 #[derive(Deftly, Debug, PartialEq)]
885 #[derive_deftly(KeySpecifier)]
886 #[deftly(prefix = "encabulator")]
887 #[deftly(role = "fan")]
888 #[deftly(summary = "test key")]
889 struct TestSpecifier {
890 casing: String,
891 bearings: String,
893 }
894
895 let key_spec = TestSpecifier {
896 casing: "logarithmic".into(),
897 bearings: "spurving".into(),
898 };
899
900 check_key_specifier(&key_spec, "encabulator/logarithmic/spurving/fan");
901
902 assert_eq!(
903 TestSpecifierPattern {
904 casing: Some("logarithmic".into()),
905 bearings: Some("prefabulating".into()),
906 }
907 .arti_pattern()
908 .unwrap(),
909 KeyPathPattern::Arti("encabulator/logarithmic/prefabulating/fan".into())
910 );
911
912 let ctor_path = CTorPath::HsIdPublicKey {
913 nickname: HsNickname::from_str("foo").unwrap(),
914 };
915
916 assert_eq!(
917 TestSpecifier::from_ctor_path(ctor_path).unwrap_err(),
918 CTorPathError::MissingCTorPath("TestSpecifier".into()),
919 );
920 }
921
922 #[test]
923 fn define_key_specifier_with_multiple_denotators() {
924 #[derive(Deftly, Debug, PartialEq)]
925 #[derive_deftly(KeySpecifier)]
926 #[deftly(prefix = "encabulator")]
927 #[deftly(role = "fan")]
928 #[deftly(summary = "test key")]
929 struct TestSpecifier {
930 casing: String,
931 bearings: String,
933
934 #[deftly(denotator)]
935 count: usize,
936
937 #[deftly(denotator)]
938 length: usize,
939
940 #[deftly(denotator)]
941 kind: String,
942 }
943
944 let key_spec = TestSpecifier {
945 casing: "logarithmic".into(),
946 bearings: "spurving".into(),
947 count: 8,
948 length: 2000,
949 kind: "lunar".into(),
950 };
951
952 check_key_specifier(
953 &key_spec,
954 "encabulator/logarithmic/spurving/fan+8+2000+lunar",
955 );
956
957 assert_eq!(
958 TestSpecifierPattern {
959 casing: Some("logarithmic".into()),
960 bearings: Some("prefabulating".into()),
961 ..TestSpecifierPattern::new_any()
962 }
963 .arti_pattern()
964 .unwrap(),
965 KeyPathPattern::Arti("encabulator/logarithmic/prefabulating/fan+*+*+*".into())
966 );
967 }
968
969 #[test]
970 fn define_key_specifier_role_field() {
971 #[derive(Deftly, Debug, Eq, PartialEq)]
972 #[derive_deftly(KeySpecifier)]
973 #[deftly(prefix = "prefix")]
974 #[deftly(summary = "test key")]
975 struct TestSpecifier {
976 #[deftly(role)]
977 role: String,
978 i: usize,
979 #[deftly(denotator)]
980 den: bool,
981 }
982
983 check_key_specifier(
984 &TestSpecifier {
985 i: 1,
986 role: "role".to_string(),
987 den: true,
988 },
989 "prefix/1/role+true",
990 );
991 }
992
993 #[test]
994 fn define_key_specifier_ctor_path() {
995 #[derive(Deftly, Debug, Eq, PartialEq)]
996 #[derive_deftly(KeySpecifier)]
997 #[deftly(prefix = "p")]
998 #[deftly(role = "r")]
999 #[deftly(ctor_path = HsIdPublicKey)]
1000 #[deftly(summary = "test key")]
1001 struct TestSpecifier {
1002 nickname: HsNickname,
1003 }
1004
1005 let spec = TestSpecifier {
1006 nickname: HsNickname::from_str("42").unwrap(),
1007 };
1008
1009 check_key_specifier(&spec, "p/42/r");
1010
1011 let ctor_path = KeySpecifier::ctor_path(&spec);
1012
1013 assert_eq!(
1014 ctor_path,
1015 Some(CTorPath::HsIdPublicKey {
1016 nickname: HsNickname::from_str("42").unwrap(),
1017 }),
1018 );
1019
1020 assert_eq!(
1021 TestSpecifier::from_ctor_path(ctor_path.unwrap()).unwrap(),
1022 spec,
1023 );
1024
1025 const HSID: &str = "yc6v7oeksrbech4ctv53di7rfjuikjagkyfrwu3yclzkfyv5haay6mqd.onion";
1027 let wrong_paths = &[
1028 CTorPath::HsClientDescEncKeypair {
1029 hs_id: HsId::from_str(HSID).unwrap(),
1030 },
1031 CTorPath::HsIdKeypair {
1032 nickname: HsNickname::from_str("42").unwrap(),
1033 },
1034 ];
1035
1036 for path in wrong_paths {
1037 assert_eq!(
1038 TestSpecifier::from_ctor_path(path.clone()).unwrap_err(),
1039 CTorPathError::KeySpecifierMismatch("TestSpecifier".into()),
1040 );
1041 }
1042 }
1043
1044 #[test]
1045 fn define_key_specifier_fixed_path_component() {
1046 #[derive(Deftly, Debug, Eq, PartialEq)]
1047 #[derive_deftly(KeySpecifier)]
1048 #[deftly(prefix = "prefix")]
1049 #[deftly(role = "role")]
1050 #[deftly(summary = "test key")]
1051 struct TestSpecifier {
1052 x: usize,
1053 #[deftly(fixed_path_component = "fixed")]
1054 z: bool,
1055 }
1056
1057 check_key_specifier(&TestSpecifier { x: 1, z: true }, "prefix/1/fixed/true/role");
1058 }
1059
1060 #[test]
1061 #[cfg(feature = "experimental-api")]
1062 fn define_cert_specifier_with_multiple_denotators() {
1063 #[derive(Deftly, Debug, PartialEq)]
1064 #[derive_deftly(KeySpecifier)]
1065 #[deftly(prefix = "encabulator")]
1066 #[deftly(role = "fan")]
1067 #[deftly(summary = "test key")]
1068 struct TestKeySpecifier {
1069 casing: String,
1070 bearings: String,
1071 #[deftly(denotator)]
1072 count: usize,
1073 }
1074
1075 #[derive(Deftly, Debug, PartialEq)]
1076 #[derive_deftly(CertSpecifier)]
1077 #[allow(dead_code)]
1078 struct TestCertSpecifier {
1079 #[deftly(subject)]
1080 subject: TestKeySpecifier,
1081 #[deftly(denotator)]
1082 length: usize,
1083 #[deftly(denotator)]
1084 width: usize,
1085 }
1086
1087 let cert_pat = TestCertSpecifierPattern::new_any();
1088 assert_eq!(
1089 cert_pat.arti_pattern().unwrap(),
1090 KeyPathPattern::Arti("encabulator/*/*/fan+*@*+*".into())
1091 );
1092 }
1093
1094 #[test]
1095 fn encode_time_period() {
1096 let period = TimePeriod::from_parts(1, 2, 3);
1097 let encoded_period = period.to_slug().unwrap();
1098
1099 assert_eq!(encoded_period.to_string(), "2_1_3");
1100 assert_eq!(period, TimePeriod::from_slug(&encoded_period).unwrap());
1101
1102 assert!(TimePeriod::from_slug(&Slug::new("invalid_tp".to_string()).unwrap()).is_err());
1103 assert!(TimePeriod::from_slug(&Slug::new("2_1_3_4".to_string()).unwrap()).is_err());
1104 }
1105
1106 #[test]
1107 fn encode_hsid() {
1108 let b32 = "eweiibe6tdjsdprb4px6rqrzzcsi22m4koia44kc5pcjr7nec2rlxyad";
1109 let onion = format!("{b32}.onion");
1110 let hsid = HsId::from_str(&onion).unwrap();
1111 let hsid_slug = hsid.to_slug().unwrap();
1112
1113 assert_eq!(hsid_slug.to_string(), b32);
1114 assert_eq!(hsid, HsId::from_slug(&hsid_slug).unwrap());
1115 }
1116
1117 #[test]
1118 fn key_info_builder() {
1119 macro_rules! assert_extra_info_eq {
1121 ($key_info:expr, [$(($k:expr, $v:expr),)*]) => {{
1122 assert_eq!(
1123 $key_info.extra_info.into_iter().collect_vec(),
1124 vec![
1125 $(($k.into(), $v.into()),)*
1126 ]
1127 );
1128 }}
1129 }
1130 let extra_info = vec![("nickname".into(), "bar".into())];
1131
1132 let key_info = KeyPathInfo::builder()
1133 .summary("test summary".into())
1134 .role("KS_vote".to_string())
1135 .set_all_extra_info(extra_info.clone().into_iter())
1136 .build()
1137 .unwrap();
1138
1139 assert_eq!(key_info.extra_info.into_iter().collect_vec(), extra_info);
1140
1141 let key_info = KeyPathInfo::builder()
1142 .summary("test summary".into())
1143 .role("KS_vote".to_string())
1144 .set_all_extra_info(extra_info.clone().into_iter())
1145 .extra_info("type", "service")
1146 .extra_info("time period", "100")
1147 .build()
1148 .unwrap();
1149
1150 assert_extra_info_eq!(
1151 key_info,
1152 [
1153 ("nickname", "bar"),
1154 ("time period", "100"),
1155 ("type", "service"),
1156 ]
1157 );
1158
1159 let key_info = KeyPathInfo::builder()
1160 .summary("test summary".into())
1161 .role("KS_vote".to_string())
1162 .extra_info("type", "service")
1163 .extra_info("time period", "100")
1164 .set_all_extra_info(extra_info.clone().into_iter())
1165 .build()
1166 .unwrap();
1167
1168 assert_extra_info_eq!(key_info, [("nickname", "bar"),]);
1169
1170 let key_info = KeyPathInfo::builder()
1171 .summary("test summary".into())
1172 .role("KS_vote".to_string())
1173 .extra_info("type", "service")
1174 .extra_info("time period", "100")
1175 .build()
1176 .unwrap();
1177
1178 assert_extra_info_eq!(key_info, [("time period", "100"), ("type", "service"),]);
1179 }
1180}