1pub use b16impl::*;
9pub use b64impl::*;
10pub use contact_info::*;
11pub use curve25519impl::*;
12pub use ed25519impl::*;
13pub(crate) use edcert::*;
14pub use fingerprint::*;
15pub use hostname::*;
16pub use rsa::*;
17pub use timeimpl::*;
18
19pub use nickname::{InvalidNickname, Nickname};
20
21pub use boolean::NumericBoolean;
22
23pub use fingerprint::{Base64Fingerprint, Fingerprint};
24
25pub use identified_digest::{DigestName, IdentifiedDigest};
26
27pub use ignored_impl::{Ignored, IgnoredItemOrObjectValue, NotPresent};
28
29use crate::NormalItemArgument;
30use crate::encode::{
31 self,
32 ItemArgument,
33 ItemEncoder,
34 ItemObjectEncodable,
35 ItemValueEncodable,
36 MultiplicitySelector as EMultiplicitySelector,
38 NetdocEncoder,
39};
40use crate::parse2::{
41 self, ArgumentError, ArgumentStream, ItemArgumentParseable, ItemObjectParseable,
42 ItemValueParseable, SignatureHashInputs, SignatureItemParseable, UnparsedItem,
43 multiplicity::{
44 ItemSetMethods,
45 MultiplicitySelector as P2MultiplicitySelector,
47 ObjectSetMethods,
48 },
49 sig_hashes::Sha1WholeKeywordLine,
50};
51
52use derive_deftly::{Deftly, define_derive_deftly, define_derive_deftly_module};
53use digest::Digest as _;
54use educe::Educe;
55use std::cmp::{self, PartialOrd};
56use std::fmt::{self, Display};
57use std::iter;
58use std::marker::PhantomData;
59use std::ops::{Deref, DerefMut};
60use std::result::Result as StdResult;
61use std::str::FromStr;
62use subtle::{Choice, ConstantTimeEq};
63use tor_error::{Bug, ErrorReport as _, internal, into_internal};
64use void::{ResultVoidExt as _, Void};
65
66pub(crate) trait FromBytes: Sized {
70 fn from_bytes(b: &[u8], p: crate::Pos) -> crate::Result<Self>;
72 fn from_vec(v: Vec<u8>, p: crate::Pos) -> crate::Result<Self> {
75 Self::from_bytes(&v[..], p)
76 }
77}
78
79define_derive_deftly_module! {
80 Transparent beta_deftly:
85
86 ${define TRANSPARENT_DOCS_IMPLS {
90 }}
93
94 ${define TRANSPARENT_IMPLS {
96
97 ${for fields {
98 ${loop_exactly_1 "must be applied to a single-field struct"}
99
100 impl<$tgens> From<$ftype> for $ttype {
101 fn from($fpatname: $ftype) -> $ttype {
102 $vpat
103 }
104 }
105
106 impl<$tgens> From<$ttype> for $ftype {
107 fn from(self_: $ttype) -> $ftype {
108 self_.$fname
109 }
110 }
111
112 impl<$tgens> Deref for $ttype {
113 type Target = $ftype;
114 fn deref(&self) -> &$ftype {
115 &self.$fname
116 }
117 }
118
119 impl<$tgens> DerefMut for $ttype {
120 fn deref_mut(&mut self) -> &mut $ftype {
121 &mut self.$fname
122 }
123 }
124
125 impl<$tgens> AsRef<$ftype> for $ttype {
126 fn as_ref(&self) -> &$ftype {
127 &self.$fname
128 }
129 }
130
131 impl<$tgens> AsMut<$ftype> for $ttype {
132 fn as_mut(&mut self) -> &mut $ftype {
133 &mut self.$fname
134 }
135 }
136 }}
137 }}
138}
139
140define_derive_deftly! {
141 use Transparent;
142
143 $TRANSPARENT_DOCS_IMPLS
153 Transparent for struct, beta_deftly:
165
166 $TRANSPARENT_IMPLS
167}
168
169define_derive_deftly! {
170 use Transparent;
171
172 ${TRANSPARENT_DOCS_IMPLS}
185 BytesTransparent for struct, beta_deftly:
197
198 $TRANSPARENT_IMPLS
199
200 impl<$tgens> ConstantTimeEq for $ttype {
201 fn ct_eq(&self, other: &$ttype) -> Choice {
202 $(
203 self.$fname.ct_eq(&other.$fname)
204 )
205 }
206 }
207 $impl<$tgens> PartialEq for $ttype {
209 fn eq(&self, other: &$ttype) -> bool {
210 self.ct_eq(other).into()
211 }
212 }
213 impl<$tgens> Eq for $ttype {}
214
215 impl<$tgens> $ttype {
216 pub fn as_bytes(&self) -> &[u8] {
218 $(
219 &self.$fname[..]
220 )
221 }
222 }
223
224 impl<$tgens> AsRef<[u8]> for $ttype {
225 fn as_ref(&self) -> &[u8] {
226 $(
227 self.$fname.as_ref()
228 )
229 }
230 }
231
232 impl<$tgens> AsMut<[u8]> for $ttype {
233 fn as_mut(&mut self) -> &mut [u8] {
234 $(
235 self.$fname.as_mut()
236 )
237 }
238 }
239}
240
241mod b64impl {
243 use super::*;
244 use crate::{Error, NetdocErrorKind as EK, Pos, Result};
245 use base64ct::{Base64, Base64Unpadded, Encoding};
246 use std::ops::RangeBounds;
247
248 #[derive(Clone, Hash, Deftly)]
252 #[derive_deftly(BytesTransparent)]
253 #[allow(clippy::derived_hash_with_manual_eq)]
254 #[derive(derive_more::Debug)]
255 #[debug(r#"B64("{self}")"#)]
256 #[allow(clippy::exhaustive_structs)]
257 pub struct B64(pub Vec<u8>);
258
259 impl FromStr for B64 {
260 type Err = Error;
261 fn from_str(s: &str) -> Result<Self> {
262 let v: core::result::Result<Vec<u8>, base64ct::Error> = match s.len() % 4 {
263 0 => Base64::decode_vec(s),
264 _ => Base64Unpadded::decode_vec(s),
265 };
266 let v = v.map_err(|_| {
267 EK::BadArgument
268 .with_msg("Invalid base64")
269 .at_pos(Pos::at(s))
270 })?;
271 Ok(B64(v))
272 }
273 }
274
275 impl Display for B64 {
276 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
277 Display::fmt(&Base64Unpadded::encode_string(&self.0), f)
278 }
279 }
280
281 impl B64 {
282 pub(crate) fn check_len<B: RangeBounds<usize>>(self, bounds: B) -> Result<Self> {
285 if bounds.contains(&self.0.len()) {
286 Ok(self)
287 } else {
288 Err(EK::BadObjectVal.with_msg("Invalid length on base64 data"))
289 }
290 }
291
292 pub(crate) fn into_array<const N: usize>(self) -> Result<[u8; N]> {
296 self.0
297 .try_into()
298 .map_err(|_| EK::BadObjectVal.with_msg("Invalid length on base64 data"))
299 }
300 }
301
302 impl FromIterator<u8> for B64 {
303 fn from_iter<T: IntoIterator<Item = u8>>(iter: T) -> Self {
304 Self(iter.into_iter().collect())
305 }
306 }
307
308 impl NormalItemArgument for B64 {}
309
310 #[derive(Clone, Hash, Deftly)]
312 #[derive_deftly(BytesTransparent)]
313 #[allow(clippy::derived_hash_with_manual_eq)]
314 #[derive(derive_more::Debug)]
315 #[debug(r#"FixedB64::<{N}>("{self}")"#)]
316 #[allow(clippy::exhaustive_structs)]
317 pub struct FixedB64<const N: usize>(pub [u8; N]);
318
319 impl<const N: usize> Display for FixedB64<N> {
320 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
321 Display::fmt(&B64(self.0.to_vec()), f)
322 }
323 }
324
325 impl<const N: usize> FromStr for FixedB64<N> {
326 type Err = Error;
327 fn from_str(s: &str) -> Result<Self> {
328 Ok(Self(B64::from_str(s)?.0.try_into().map_err(|_| {
329 EK::BadArgument
330 .at_pos(Pos::at(s))
331 .with_msg("invalid length")
332 })?))
333 }
334 }
335
336 impl<const N: usize> NormalItemArgument for FixedB64<N> {}
337}
338
339mod b16impl {
343 use super::*;
344 use crate::{Error, NetdocErrorKind as EK, Pos, Result};
345
346 #[derive(Clone, Hash, Deftly)]
350 #[derive_deftly(BytesTransparent)]
351 #[allow(clippy::derived_hash_with_manual_eq)]
352 #[derive(derive_more::Debug)]
353 #[debug(r#"B16("{self}")"#)]
354 #[allow(clippy::exhaustive_structs)]
355 pub struct B16(pub Vec<u8>);
356
357 #[derive(Clone, Hash, Deftly)]
361 #[derive_deftly(BytesTransparent)]
362 #[allow(clippy::derived_hash_with_manual_eq)]
363 #[derive(derive_more::Debug)]
364 #[debug(r#"B16U("{self}")"#)]
365 #[allow(clippy::exhaustive_structs)]
366 pub struct B16U(pub Vec<u8>);
367
368 #[derive(Clone, Hash, Deftly)]
370 #[derive_deftly(BytesTransparent)]
371 #[allow(clippy::derived_hash_with_manual_eq)]
372 #[derive(derive_more::Debug)]
373 #[debug(r#"FixedB16U("{self}")"#)]
374 #[allow(clippy::exhaustive_structs)]
375 pub struct FixedB16U<const N: usize>(pub [u8; N]);
376
377 impl FromStr for B16 {
378 type Err = Error;
379 fn from_str(s: &str) -> Result<Self> {
380 let bytes = hex::decode(s).map_err(|_| {
381 EK::BadArgument
382 .at_pos(Pos::at(s))
383 .with_msg("invalid hexadecimal")
384 })?;
385 Ok(B16(bytes))
386 }
387 }
388
389 impl FromStr for B16U {
390 type Err = Error;
391 fn from_str(s: &str) -> Result<Self> {
392 Ok(B16U(B16::from_str(s)?.0))
393 }
394 }
395
396 impl<const N: usize> FromStr for FixedB16U<N> {
397 type Err = Error;
398 fn from_str(s: &str) -> Result<Self> {
399 Ok(Self(B16U::from_str(s)?.0.try_into().map_err(|_| {
400 EK::BadArgument
401 .at_pos(Pos::at(s))
402 .with_msg("invalid length")
403 })?))
404 }
405 }
406
407 impl Display for B16 {
408 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
409 for c in self.as_bytes() {
411 write!(f, "{c:02x}")?;
412 }
413 Ok(())
414 }
415 }
416
417 impl Display for B16U {
418 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
419 for c in self.as_bytes() {
421 write!(f, "{c:02X}")?;
422 }
423 Ok(())
424 }
425 }
426
427 impl<const N: usize> Display for FixedB16U<N> {
428 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
429 for c in self.as_bytes() {
432 write!(f, "{c:02X}")?;
433 }
434 Ok(())
435 }
436 }
437
438 impl NormalItemArgument for B16 {}
439 impl NormalItemArgument for B16U {}
440 impl<const N: usize> NormalItemArgument for FixedB16U<N> {}
441}
442
443mod curve25519impl {
447 use super::*;
448
449 use crate::{Error, NormalItemArgument, Result, types::misc::FixedB64};
450 use tor_llcrypto::pk::curve25519::PublicKey;
451
452 #[derive(Debug, Clone, PartialEq, Eq, Deftly)]
454 #[derive_deftly(Transparent)]
455 #[allow(clippy::exhaustive_structs)]
456 pub struct Curve25519Public(pub PublicKey);
457
458 impl FromStr for Curve25519Public {
459 type Err = Error;
460 fn from_str(s: &str) -> Result<Self> {
461 let pk: FixedB64<32> = s.parse()?;
462 let pk: [u8; 32] = pk.into();
463 Ok(Curve25519Public(pk.into()))
464 }
465 }
466
467 impl Display for Curve25519Public {
468 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
469 FixedB64::from(self.0.to_bytes()).fmt(f)
470 }
471 }
472
473 impl NormalItemArgument for Curve25519Public {}
474}
475
476mod ed25519impl {
480 use super::*;
481
482 use crate::{Error, NormalItemArgument, Result, types::misc::FixedB64};
483 use derive_deftly::Deftly;
484 use tor_llcrypto::pk::ed25519::{Ed25519Identity, Signature};
485
486 #[derive(Debug, Clone, PartialEq, Eq)]
489 #[allow(clippy::exhaustive_structs)]
490 pub struct Ed25519Public(pub Ed25519Identity);
491
492 impl FromStr for Ed25519Public {
493 type Err = Error;
494 fn from_str(s: &str) -> Result<Self> {
495 let pk: FixedB64<32> = s.parse()?;
496 Ok(Ed25519Public(Ed25519Identity::new(pk.into())))
497 }
498 }
499
500 impl Display for Ed25519Public {
501 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
502 let pk: [u8; 32] = self.0.into();
503 let pk = FixedB64::from(pk);
504 pk.fmt(f)
505 }
506 }
507
508 impl NormalItemArgument for Ed25519Public {}
509
510 impl From<Ed25519Public> for Ed25519Identity {
511 fn from(pk: Ed25519Public) -> Ed25519Identity {
512 pk.0
513 }
514 }
515
516 #[derive(Debug, Clone, PartialEq, Eq, derive_more::Display, derive_more::FromStr)]
518 #[display(rename_all = "lowercase")]
519 #[from_str(rename_all = "lowercase")]
520 #[allow(clippy::exhaustive_enums)]
521 pub enum Ed25519AlgorithmString {
522 Ed25519,
524 }
525
526 impl NormalItemArgument for Ed25519AlgorithmString {}
527
528 #[derive(Debug, Clone, PartialEq, Eq, Deftly)]
530 #[derive_deftly(ItemValueParseable)]
531 #[non_exhaustive]
532 pub struct Ed25519IdentityLine {
533 pub alg: Ed25519AlgorithmString,
535
536 pub pk: Ed25519Public,
538 }
539
540 impl From<Ed25519Public> for Ed25519IdentityLine {
541 fn from(pk: Ed25519Public) -> Self {
542 Self {
543 alg: Ed25519AlgorithmString::Ed25519,
544 pk,
545 }
546 }
547 }
548
549 impl From<Ed25519Identity> for Ed25519IdentityLine {
550 fn from(pk: Ed25519Identity) -> Self {
551 Ed25519Public(pk).into()
552 }
553 }
554
555 impl ItemArgument for Signature {
556 fn write_arg_onto(&self, out: &mut ItemEncoder) -> StdResult<(), Bug> {
557 FixedB64::from(self.to_bytes()).write_arg_onto(out)
558 }
559 }
560}
561
562mod ignored_impl {
566 use super::*;
567
568 use crate::parse2::ErrorProblem as EP;
569
570 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Default)]
597 #[allow(clippy::exhaustive_structs)]
598 #[derive(Deftly)]
599 #[derive_deftly(NetdocParseableFields)]
600 pub struct NotPresent;
601
602 #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Default, Deftly)]
617 #[derive_deftly(ItemValueParseable, NetdocParseableFields)]
618 #[allow(clippy::exhaustive_structs)]
619 pub struct Ignored;
620
621 pub struct IgnoredItemOrObjectValue(Void);
628
629 impl ItemSetMethods for P2MultiplicitySelector<NotPresent> {
630 type Each = Ignored;
631 type Field = NotPresent;
632 fn can_accumulate(self, _acc: &Option<NotPresent>) -> Result<(), EP> {
633 Ok(())
634 }
635 fn accumulate(self, _acc: &mut Option<NotPresent>, _item: Ignored) -> Result<(), EP> {
636 Ok(())
637 }
638 fn finish(self, _acc: Option<NotPresent>, _: &'static str) -> Result<NotPresent, EP> {
639 Ok(NotPresent)
640 }
641 fn debug_core(self) -> &'static str {
642 "Ignored"
643 }
644 }
645
646 impl ItemArgumentParseable for NotPresent {
647 fn from_args(_: &mut ArgumentStream) -> Result<NotPresent, ArgumentError> {
648 Ok(NotPresent)
649 }
650 }
651
652 impl ObjectSetMethods for P2MultiplicitySelector<NotPresent> {
653 type Field = NotPresent;
654 type Each = Void;
655 fn resolve_option(self, _found: Option<Void>) -> Result<NotPresent, EP> {
656 Ok(NotPresent)
657 }
658 fn debug_core(self) -> &'static str {
659 "NotPresent"
660 }
661 }
662
663 impl<'f> encode::MultiplicityMethods<'f> for EMultiplicitySelector<NotPresent> {
664 type Field = NotPresent;
665 type Each = Void;
666 fn iter_ordered(self, _: &'f Self::Field) -> impl Iterator<Item = &'f Self::Each> {
667 iter::empty()
668 }
669 }
670
671 impl encode::OptionalityMethods for EMultiplicitySelector<NotPresent> {
672 type Field = NotPresent;
673 type Each = Void;
674 fn as_option<'f>(self, _: &'f Self::Field) -> Option<&'f Self::Each> {
675 None
676 }
677 }
678
679 impl FromStr for Ignored {
680 type Err = Void;
681 fn from_str(_s: &str) -> Result<Ignored, Void> {
682 Ok(Ignored)
683 }
684 }
685
686 impl ItemArgumentParseable for Ignored {
687 fn from_args(_: &mut ArgumentStream) -> Result<Ignored, ArgumentError> {
688 Ok(Ignored)
689 }
690 }
691
692 impl ItemObjectParseable for Ignored {
693 fn check_label(_label: &str) -> Result<(), EP> {
694 Ok(())
696 }
697 fn from_bytes(_input: &[u8]) -> Result<Self, EP> {
698 Ok(Ignored)
699 }
700 }
701
702 impl ObjectSetMethods for P2MultiplicitySelector<Ignored> {
703 type Field = Ignored;
704 type Each = Ignored;
705 fn resolve_option(self, _found: Option<Ignored>) -> Result<Ignored, EP> {
706 Ok(Ignored)
707 }
708 fn debug_core(self) -> &'static str {
709 "Ignored"
710 }
711 }
712
713 impl<'f> encode::MultiplicityMethods<'f> for EMultiplicitySelector<Ignored> {
714 type Field = Ignored;
715 type Each = IgnoredItemOrObjectValue;
716 fn iter_ordered(self, _: &'f Self::Field) -> impl Iterator<Item = &'f Self::Each> {
717 iter::empty()
718 }
719 }
720
721 impl encode::OptionalityMethods for EMultiplicitySelector<Ignored> {
722 type Field = Ignored;
723 type Each = IgnoredItemOrObjectValue;
724 fn as_option<'f>(self, _: &'f Self::Field) -> Option<&'f Self::Each> {
725 None
726 }
727 }
728
729 impl ItemValueEncodable for IgnoredItemOrObjectValue {
730 fn write_item_value_onto(&self, _: ItemEncoder) -> Result<(), Bug> {
731 void::unreachable(self.0)
732 }
733 }
734
735 impl ItemObjectEncodable for IgnoredItemOrObjectValue {
736 fn label(&self) -> &str {
737 void::unreachable(self.0)
738 }
739 fn write_object_onto(&self, _: &mut Vec<u8>) -> Result<(), Bug> {
740 void::unreachable(self.0)
741 }
742 }
743}
744
745#[derive(Debug, PartialEq, Clone, Copy, Hash)]
775#[allow(clippy::exhaustive_enums)] pub enum Unknown<T> {
777 Discarded(PhantomData<T>),
779
780 #[cfg(feature = "retain-unknown")]
782 Retained(T),
783}
784
785impl<T> Unknown<T> {
786 pub fn new_discard() -> Self {
788 Unknown::Discarded(PhantomData)
789 }
790
791 pub fn map<U>(self, f: impl FnOnce(T) -> U) -> Unknown<U> {
793 self.try_map(move |t| Ok::<_, Void>(f(t))).void_unwrap()
794 }
795
796 pub fn try_map<U, E>(self, f: impl FnOnce(T) -> Result<U, E>) -> Result<Unknown<U>, E> {
798 Ok(match self {
799 Unknown::Discarded(_) => Unknown::Discarded(PhantomData),
800 #[cfg(feature = "retain-unknown")]
801 Unknown::Retained(t) => Unknown::Retained(f(t)?),
802 })
803 }
804
805 pub fn as_ref(&self) -> Unknown<&T> {
807 match self {
808 Unknown::Discarded(_) => Unknown::Discarded(PhantomData),
809 #[cfg(feature = "retain-unknown")]
810 Unknown::Retained(t) => Unknown::Retained(t),
811 }
812 }
813
814 pub fn only_known(self) -> Option<T> {
822 match self {
823 Unknown::Discarded(_) => None,
824 #[cfg(feature = "retain-unknown")]
825 Unknown::Retained(t) => Some(t),
826 }
827 }
828
829 #[cfg(feature = "retain-unknown")]
833 pub fn into_retained(self) -> Result<T, Bug> {
834 match self {
835 Unknown::Discarded(_) => Err(internal!("Unknown::retained but data not collected")),
836 Unknown::Retained(t) => Ok(t),
837 }
838 }
839
840 #[cfg(feature = "retain-unknown")]
842 pub fn new_retained_default() -> Self
843 where
844 T: Default,
845 {
846 Unknown::Retained(T::default())
847 }
848
849 pub fn with_mut_unknown(&mut self, f: impl FnOnce(&mut T)) {
857 match self {
858 Unknown::Discarded(_) => {}
859 #[cfg(feature = "retain-unknown")]
860 Unknown::Retained(t) => f(t),
861 }
862 }
863}
864
865impl<T: PartialOrd> PartialOrd for Unknown<T> {
866 fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
867 use Unknown::*;
868 match (self, other) {
869 (Discarded(_), Discarded(_)) => Some(cmp::Ordering::Equal),
870 #[cfg(feature = "retain-unknown")]
871 (Discarded(_), Retained(_)) | (Retained(_), Discarded(_)) => None,
872 #[cfg(feature = "retain-unknown")]
873 (Retained(a), Retained(b)) => a.partial_cmp(b),
874 }
875 }
876}
877
878#[derive(Debug, PartialEq, Clone, Hash)]
894#[allow(clippy::exhaustive_enums)] pub enum KeywordOrString<T: Copy> {
896 Known(T),
898
899 Unknown(String),
901}
902
903impl<T: Copy + NormalItemArgument> NormalItemArgument for KeywordOrString<T> {}
904
905impl<T: Copy + Display> Display for KeywordOrString<T> {
906 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
907 match self {
908 KeywordOrString::Known(t) => Display::fmt(t, f),
909 KeywordOrString::Unknown(s) => Display::fmt(s, f),
910 }
911 }
912}
913
914impl<T: Copy + FromStr> FromStr for KeywordOrString<T> {
915 type Err = Void;
916 fn from_str(s: &str) -> Result<Self, Void> {
917 Ok(match s.parse() {
918 Ok(y) => KeywordOrString::Known(y),
919 Err(_) => KeywordOrString::Unknown(s.to_owned()),
920 })
921 }
922}
923
924#[derive(Debug, Clone, Hash, Deftly, Eq, PartialEq, Educe)]
936#[educe(Default)]
937#[derive_deftly(Transparent)]
938#[allow(clippy::exhaustive_structs)]
939pub struct RetainedOrderVec<T>(pub Vec<T>);
940
941mod timeimpl {
945 use super::*;
946 use crate::{Error, NetdocErrorKind as EK, Pos, Result};
947 use std::time::SystemTime;
948 use time::{
949 OffsetDateTime, PrimitiveDateTime, format_description::FormatItem,
950 macros::format_description,
951 };
952
953 #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Deftly)]
958 #[derive_deftly(Transparent)]
959 #[allow(clippy::exhaustive_structs)]
960 pub struct Iso8601TimeSp(pub SystemTime);
961
962 const ISO_8601SP_FMT: &[FormatItem] =
964 format_description!("[year]-[month]-[day] [hour]:[minute]:[second]");
965
966 impl FromStr for Iso8601TimeSp {
967 type Err = Error;
968 fn from_str(s: &str) -> Result<Iso8601TimeSp> {
969 let d = PrimitiveDateTime::parse(s, &ISO_8601SP_FMT).map_err(|e| {
970 EK::BadArgument
971 .at_pos(Pos::at(s))
972 .with_msg(format!("invalid time: {}", e))
973 })?;
974 Ok(Iso8601TimeSp(d.assume_utc().into()))
975 }
976 }
977
978 fn fmt_with(
983 t: SystemTime,
984 format_desc: &[FormatItem],
985 ) -> core::result::Result<String, fmt::Error> {
986 OffsetDateTime::from(t)
987 .format(format_desc)
988 .map_err(|_| fmt::Error)
989 }
990
991 impl Display for Iso8601TimeSp {
992 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
993 write!(f, "{}", fmt_with(self.0, ISO_8601SP_FMT)?)
994 }
995 }
996
997 #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Deftly)]
1007 #[derive_deftly(Transparent)]
1008 #[allow(clippy::exhaustive_structs)]
1009 pub struct Iso8601TimeNoSp(pub SystemTime);
1010
1011 const ISO_8601NOSP_FMT: &[FormatItem] =
1013 format_description!("[year]-[month]-[day]T[hour]:[minute]:[second]");
1014
1015 impl FromStr for Iso8601TimeNoSp {
1016 type Err = Error;
1017 fn from_str(s: &str) -> Result<Iso8601TimeNoSp> {
1018 let d = PrimitiveDateTime::parse(s, &ISO_8601NOSP_FMT).map_err(|e| {
1019 EK::BadArgument
1020 .at_pos(Pos::at(s))
1021 .with_msg(format!("invalid time: {}", e))
1022 })?;
1023 Ok(Iso8601TimeNoSp(d.assume_utc().into()))
1024 }
1025 }
1026
1027 impl Display for Iso8601TimeNoSp {
1028 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1029 write!(f, "{}", fmt_with(self.0, ISO_8601NOSP_FMT)?)
1030 }
1031 }
1032
1033 impl crate::NormalItemArgument for Iso8601TimeNoSp {}
1034}
1035
1036mod rsa {
1038 use super::*;
1039 use crate::{NetdocErrorKind as EK, Pos, Result};
1040 use std::ops::RangeBounds;
1041 use tor_llcrypto::pk::rsa::PublicKey;
1042 use tor_llcrypto::{d::Sha1, pk::rsa::KeyPair};
1043
1044 pub(crate) const RSA_FIXED_EXPONENT: u32 = 65537;
1048
1049 pub(crate) const RSA_MIN_BITS: usize = 1024;
1053
1054 #[allow(non_camel_case_types)]
1059 #[derive(Clone, Debug)]
1060 pub(crate) struct RsaPublicParse1Helper(PublicKey, Pos);
1061
1062 #[derive(Debug, Clone, PartialEq, Eq, Deftly)]
1079 #[derive_deftly(ItemValueParseable, ItemValueEncodable)]
1080 #[deftly(netdoc(no_extra_args, signature(hash_accu = Sha1WholeKeywordLine)))]
1081 #[non_exhaustive]
1082 pub struct RsaSha1Signature {
1083 #[deftly(netdoc(object(label = "SIGNATURE"), with = crate::types::raw_data_object))]
1085 pub signature: Vec<u8>,
1086 }
1087
1088 impl From<RsaPublicParse1Helper> for PublicKey {
1089 fn from(k: RsaPublicParse1Helper) -> PublicKey {
1090 k.0
1091 }
1092 }
1093 impl super::FromBytes for RsaPublicParse1Helper {
1094 fn from_bytes(b: &[u8], pos: Pos) -> Result<Self> {
1095 let key = PublicKey::from_der(b)
1096 .ok_or_else(|| EK::BadObjectVal.with_msg("unable to decode RSA public key"))?;
1097 Ok(RsaPublicParse1Helper(key, pos))
1098 }
1099 }
1100 impl RsaPublicParse1Helper {
1101 pub(crate) fn check_exponent(self, e: u32) -> Result<Self> {
1103 if self.0.exponent_is(e) {
1104 Ok(self)
1105 } else {
1106 Err(EK::BadObjectVal
1107 .at_pos(self.1)
1108 .with_msg("invalid RSA exponent"))
1109 }
1110 }
1111 pub(crate) fn check_len<B: RangeBounds<usize>>(self, bounds: B) -> Result<Self> {
1114 if bounds.contains(&self.0.bits()) {
1115 Ok(self)
1116 } else {
1117 Err(EK::BadObjectVal
1118 .at_pos(self.1)
1119 .with_msg("invalid RSA length"))
1120 }
1121 }
1122 pub(crate) fn check_len_eq(self, n: usize) -> Result<Self> {
1125 self.check_len(n..=n)
1126 }
1127 }
1128
1129 impl RsaSha1Signature {
1130 pub fn new_sign_netdoc(
1188 private_key: &KeyPair,
1189 encoder: &NetdocEncoder,
1190 item_keyword: &str,
1191 ) -> StdResult<Self, Bug> {
1192 let mut h = Sha1::new();
1193 h.update(encoder.text_sofar()?);
1194 h.update(item_keyword);
1195 h.update("\n");
1196 let h = h.finalize();
1197 let signature = private_key
1198 .sign(&h)
1199 .map_err(into_internal!("RSA signing failed"))?;
1200 Ok(RsaSha1Signature { signature })
1201 }
1202 }
1203}
1204
1205mod edcert {
1207 use crate::{NetdocErrorKind as EK, Pos, Result};
1208 use tor_cert::{CertType, Ed25519Cert, KeyUnknownCert};
1209 use tor_llcrypto::pk::ed25519;
1210
1211 #[derive(Debug, Clone)]
1214 pub(crate) struct UnvalidatedEdCert(KeyUnknownCert, Pos);
1215
1216 impl super::FromBytes for UnvalidatedEdCert {
1217 fn from_bytes(b: &[u8], p: Pos) -> Result<Self> {
1218 let cert = Ed25519Cert::decode(b).map_err(|e| {
1219 EK::BadObjectVal
1220 .at_pos(p)
1221 .with_msg("Bad certificate")
1222 .with_source(e)
1223 })?;
1224
1225 Ok(Self(cert, p))
1226 }
1227 fn from_vec(v: Vec<u8>, p: Pos) -> Result<Self> {
1228 Self::from_bytes(&v[..], p)
1229 }
1230 }
1231 impl UnvalidatedEdCert {
1232 pub(crate) fn check_cert_type(self, desired_type: CertType) -> Result<Self> {
1234 if self.0.peek_cert_type() != desired_type {
1235 return Err(EK::BadObjectVal.at_pos(self.1).with_msg(format!(
1236 "bad certificate type {} (wanted {})",
1237 self.0.peek_cert_type(),
1238 desired_type
1239 )));
1240 }
1241 Ok(self)
1242 }
1243 pub(crate) fn check_subject_key_is(self, pk: &ed25519::Ed25519Identity) -> Result<Self> {
1245 if self.0.peek_subject_key().as_ed25519() != Some(pk) {
1246 return Err(EK::BadObjectVal
1247 .at_pos(self.1)
1248 .with_msg("incorrect subject key"));
1249 }
1250 Ok(self)
1251 }
1252 pub(crate) fn into_unchecked(self) -> KeyUnknownCert {
1254 self.0
1255 }
1256 }
1257}
1258
1259mod identified_digest {
1264 use super::*;
1265
1266 define_derive_deftly! {
1267 StringReprUnitsOrUnknown for enum, expect items, beta_deftly:
1282
1283 ${define STRING_REPR {
1284 ${vmeta(string_repr)
1285 as str,
1286 default { ${concat ${snake_case $vname}} }
1287 }
1288 }}
1289
1290 impl FromStr for $ttype {
1291 type Err = Void;
1292 fn from_str(s: &str) -> Result<Self, Void> {
1293 $(
1294 ${when v_is_unit}
1295 if s == $STRING_REPR {
1296 return Ok($vtype)
1297 }
1298 )
1299 $(
1300 ${when not(v_is_unit)} Ok($vtype { 0: s.into() })
1303 )
1304 }
1305 }
1306 impl Display for $ttype {
1307 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1308 let s: &str = match self {
1309 $(
1310 ${when v_is_unit}
1311 $vtype => $STRING_REPR,
1312 )
1313 $(
1314 ${when not(v_is_unit)}
1315 $vpat => f_0,
1316 )
1317 };
1318 Display::fmt(s, f)
1319 }
1320 }
1321 }
1322
1323 #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deftly)]
1327 #[derive_deftly(StringReprUnitsOrUnknown)]
1328 #[non_exhaustive]
1329 pub enum DigestName {
1330 Sha256,
1332 Unknown(String),
1334 }
1335
1336 #[derive(Debug, Clone, Eq, PartialEq, Hash, derive_more::Display)]
1338 #[display("{alg}={value}")]
1339 #[non_exhaustive]
1340 pub struct IdentifiedDigest {
1341 alg: DigestName,
1343
1344 value: B64,
1348 }
1349
1350 impl NormalItemArgument for DigestName {}
1351 impl NormalItemArgument for IdentifiedDigest {}
1352
1353 #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, thiserror::Error)]
1355 #[error("invalid syntax, expected ALGORITHM=DIGEST: {0}")]
1356 pub struct IdentifiedDigestParseError(String);
1357
1358 impl FromStr for IdentifiedDigest {
1359 type Err = IdentifiedDigestParseError;
1360
1361 fn from_str(s: &str) -> Result<Self, Self::Err> {
1362 (|| {
1363 let (alg, value) = s.split_once('=').ok_or("missing equals sign")?;
1364
1365 let alg = alg.parse().void_unwrap();
1366 let value = value
1367 .parse::<B64>()
1368 .map_err(|e| format!("bad value: {}", e.report()))?;
1369
1370 if let Some(exp_len) = (|| {
1371 Some({
1372 use DigestName::*;
1373 match alg {
1374 Sha256 => 32,
1375 Unknown(_) => None?,
1376 }
1377 })
1378 })() {
1379 let val_len = value.as_bytes().len();
1380 if val_len != exp_len {
1381 return Err(format!("got {val_len} bytes, expected {exp_len}"));
1382 }
1383 }
1384
1385 Ok(IdentifiedDigest { alg, value })
1386 })()
1387 .map_err(IdentifiedDigestParseError)
1388 }
1389 }
1390}
1391
1392mod fingerprint {
1394 use super::*;
1395 use crate::parse2::{ArgumentError, ArgumentStream, ItemArgumentParseable};
1396 use crate::{Error, NetdocErrorKind as EK, Pos, Result};
1397 use base64ct::{Base64Unpadded, Encoding as _};
1398 use tor_llcrypto::pk::rsa::RsaIdentity;
1399
1400 #[derive(Debug, Clone, Eq, PartialEq, Hash, Deftly)]
1406 #[derive_deftly(Transparent)]
1407 #[allow(clippy::exhaustive_structs)]
1408 pub struct SpFingerprint(pub RsaIdentity);
1409
1410 #[derive(Debug, Clone, Eq, PartialEq, Hash, Deftly)]
1414 #[derive_deftly(Transparent)]
1415 #[allow(clippy::exhaustive_structs)]
1416 pub struct Fingerprint(pub RsaIdentity);
1417
1418 #[derive(Debug, Clone, Eq, PartialEq, Hash, Deftly)]
1422 #[derive_deftly(Transparent)]
1423 #[allow(clippy::exhaustive_structs)]
1424 pub struct Base64Fingerprint(pub RsaIdentity);
1425
1426 #[derive(Debug, Clone, Eq, PartialEq, Hash, Deftly)]
1430 #[derive_deftly(Transparent)]
1431 #[allow(clippy::exhaustive_structs)]
1432 pub(crate) struct LongIdent(pub RsaIdentity);
1433
1434 fn parse_hex_ident(s: &str) -> Result<RsaIdentity> {
1436 RsaIdentity::from_hex(s).ok_or_else(|| {
1437 EK::BadArgument
1438 .at_pos(Pos::at(s))
1439 .with_msg("wrong length on fingerprint")
1440 })
1441 }
1442
1443 impl FromStr for SpFingerprint {
1444 type Err = Error;
1445 fn from_str(s: &str) -> Result<SpFingerprint> {
1446 let ident = parse_hex_ident(&s.replace(' ', "")).map_err(|e| e.at_pos(Pos::at(s)))?;
1447 Ok(SpFingerprint(ident))
1448 }
1449 }
1450
1451 impl ItemArgumentParseable for SpFingerprint {
1452 fn from_args<'s>(
1453 args: &mut ArgumentStream<'s>,
1454 ) -> std::result::Result<Self, ArgumentError> {
1455 let fp = args.take(10).collect::<Vec<_>>();
1458
1459 if fp.len() < 10 {
1461 return Err(ArgumentError::Missing);
1462 }
1463
1464 debug_assert_eq!(fp.len(), 10);
1466
1467 if fp.iter().any(|arg| arg.len() != 4) {
1469 return Err(ArgumentError::Invalid);
1470 }
1471
1472 Ok(Self(
1475 RsaIdentity::from_hex(fp.join("").as_str()).ok_or(ArgumentError::Invalid)?,
1476 ))
1477 }
1478 }
1479
1480 impl FromStr for Base64Fingerprint {
1481 type Err = Error;
1482 fn from_str(s: &str) -> Result<Base64Fingerprint> {
1483 let b = s.parse::<super::B64>()?;
1484 let ident = RsaIdentity::from_bytes(b.as_bytes()).ok_or_else(|| {
1485 EK::BadArgument
1486 .at_pos(Pos::at(s))
1487 .with_msg("Wrong identity length")
1488 })?;
1489 Ok(Base64Fingerprint(ident))
1490 }
1491 }
1492
1493 impl Display for Base64Fingerprint {
1494 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1495 Display::fmt(&Base64Unpadded::encode_string(self.as_bytes()), f)
1496 }
1497 }
1498
1499 impl FromStr for Fingerprint {
1500 type Err = Error;
1501 fn from_str(s: &str) -> Result<Fingerprint> {
1502 let ident = parse_hex_ident(s).map_err(|e| e.at_pos(Pos::at(s)))?;
1503 Ok(Fingerprint(ident))
1504 }
1505 }
1506
1507 impl Display for Fingerprint {
1508 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
1509 Display::fmt(&hex::encode_upper(self.as_bytes()), f)
1510 }
1511 }
1512
1513 impl FromStr for LongIdent {
1514 type Err = Error;
1515 fn from_str(mut s: &str) -> Result<LongIdent> {
1516 if s.starts_with('$') {
1517 s = &s[1..];
1518 }
1519 if let Some(idx) = s.find(['=', '~']) {
1520 s = &s[..idx];
1521 }
1522 let ident = parse_hex_ident(s)?;
1523 Ok(LongIdent(ident))
1524 }
1525 }
1526
1527 impl Display for LongIdent {
1528 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1529 write!(f, "${}", self.0.as_hex_upper())
1530 }
1531 }
1532
1533 impl crate::NormalItemArgument for Fingerprint {}
1534 impl crate::NormalItemArgument for Base64Fingerprint {}
1535 impl crate::NormalItemArgument for LongIdent {}
1536}
1537
1538mod nickname {
1540 use super::*;
1541 use tinystr::TinyAsciiStr;
1542
1543 const MAX_NICKNAME_LEN: usize = 19;
1545
1546 #[derive(Clone, Debug, PartialEq, Eq)]
1555 pub struct Nickname(tinystr::TinyAsciiStr<MAX_NICKNAME_LEN>);
1556
1557 #[derive(Clone, Debug, thiserror::Error)]
1559 #[error("invalid nickname")]
1560 #[non_exhaustive]
1561 pub struct InvalidNickname {}
1562
1563 impl Nickname {
1564 pub(crate) fn as_str(&self) -> &str {
1566 self.0.as_str()
1567 }
1568 }
1569
1570 impl Display for Nickname {
1571 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1572 self.as_str().fmt(f)
1573 }
1574 }
1575
1576 impl FromStr for Nickname {
1577 type Err = InvalidNickname;
1578
1579 fn from_str(s: &str) -> Result<Self, InvalidNickname> {
1580 let tiny = TinyAsciiStr::from_str(s).map_err(|_| InvalidNickname {})?;
1581
1582 if tiny.is_ascii_alphanumeric() && !tiny.is_empty() {
1583 Ok(Nickname(tiny))
1584 } else {
1585 Err(InvalidNickname {})
1586 }
1587 }
1588 }
1589
1590 impl crate::NormalItemArgument for Nickname {}
1591}
1592
1593mod hostname {
1597 use super::*;
1598 use std::net::IpAddr;
1599
1600 #[derive(Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] #[derive(derive_more::Into, derive_more::Deref, derive_more::AsRef, derive_more::Display)]
1626 pub struct Hostname(String);
1627
1628 #[derive(Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] #[derive(derive_more::Display)]
1644 #[allow(clippy::exhaustive_enums)]
1645 pub enum InternetHost {
1647 #[display("{_0}")]
1649 Name(Hostname),
1650 #[display("{_0}")]
1652 IpAddr(IpAddr),
1653 }
1654
1655 #[derive(Clone, Debug, thiserror::Error)]
1657 #[error("invalid hostname")]
1658 #[non_exhaustive]
1659 pub struct InvalidHostname {}
1660
1661 #[derive(Clone, Debug, thiserror::Error)]
1663 #[error("invalid: not a valid hostname, nor a valid IPv4 or IPv6 address")]
1664 #[non_exhaustive]
1665 pub struct InvalidInternetHost {}
1666
1667 impl Hostname {
1668 pub fn as_str(&self) -> &str {
1670 &self.0
1671 }
1672 }
1673
1674 impl AsRef<str> for Hostname {
1675 fn as_ref(&self) -> &str {
1676 self.as_str()
1677 }
1678 }
1679
1680 impl TryFrom<String> for Hostname {
1681 type Error = InvalidHostname;
1682 fn try_from(s: String) -> Result<Self, InvalidHostname> {
1683 if hostname_validator::is_valid(&s) &&
1684 !s.chars().all(|c| c.is_ascii_digit() || c == '.')
1688 {
1689 Ok(Hostname(s))
1690 } else {
1691 Err(InvalidHostname {})
1692 }
1693 }
1694 }
1695
1696 impl FromStr for Hostname {
1697 type Err = InvalidHostname;
1698 fn from_str(s: &str) -> Result<Self, InvalidHostname> {
1699 s.to_owned().try_into()
1700 }
1701 }
1702
1703 impl FromStr for InternetHost {
1704 type Err = InvalidInternetHost;
1705 fn from_str(s: &str) -> Result<Self, InvalidInternetHost> {
1706 if let Ok(y) = s.parse() {
1707 Ok(InternetHost::IpAddr(y))
1708 } else if let Ok(y) = s.parse() {
1709 Ok(InternetHost::Name(y))
1710 } else {
1711 Err(InvalidInternetHost {})
1715 }
1716 }
1717 }
1718
1719 impl NormalItemArgument for Hostname {}
1720 impl NormalItemArgument for InternetHost {}
1721}
1722
1723mod contact_info {
1725 use super::*;
1726
1727 #[derive(Clone, Debug, PartialEq, Eq, Deftly)] #[derive(derive_more::Into, derive_more::AsRef, derive_more::Deref, derive_more::Display)]
1734 #[derive_deftly(ItemValueEncodable)]
1735 #[non_exhaustive]
1736 pub struct ContactInfo(#[deftly(netdoc(rest))] String);
1737
1738 #[derive(Clone, Debug, thiserror::Error)]
1740 #[error("contact information (`contact` item value) has invalid syntax")]
1741 #[non_exhaustive]
1742 pub struct InvalidContactInfo {}
1743
1744 impl FromStr for ContactInfo {
1745 type Err = InvalidContactInfo;
1746
1747 fn from_str(s: &str) -> Result<Self, InvalidContactInfo> {
1748 if s.contains('\n') || s.starts_with(char::is_whitespace) {
1752 Err(InvalidContactInfo {})
1753 } else {
1754 Ok(ContactInfo(s.to_owned()))
1755 }
1756 }
1757 }
1758
1759 impl ItemValueParseable for ContactInfo {
1760 fn from_unparsed(mut item: UnparsedItem<'_>) -> Result<Self, parse2::ErrorProblem> {
1761 item.check_no_object()?;
1762 item.args_mut()
1763 .into_remaining()
1764 .parse()
1765 .map_err(|_e| item.args().handle_error("info", ArgumentError::Invalid))
1766 }
1767 }
1768}
1769
1770mod boolean {
1772 use std::{fmt::Display, str::FromStr};
1773
1774 use derive_more::{From, Into};
1775
1776 use crate::{Error, NetdocErrorKind as EK, NormalItemArgument, Pos};
1777
1778 #[derive(Clone, Copy, Debug, Default, From, Into)]
1781 #[allow(clippy::exhaustive_structs)]
1782 pub struct NumericBoolean(pub bool);
1783
1784 impl FromStr for NumericBoolean {
1785 type Err = Error;
1786
1787 fn from_str(s: &str) -> Result<Self, Self::Err> {
1788 match s {
1789 "0" => Ok(Self(false)),
1790 "1" => Ok(Self(true)),
1791 _ => Err(EK::BadArgument
1792 .at_pos(Pos::at(s))
1793 .with_msg("Invalid numeric boolean")),
1794 }
1795 }
1796 }
1797
1798 impl Display for NumericBoolean {
1799 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1800 write!(f, "{}", u8::from(self.0))
1801 }
1802 }
1803
1804 impl NormalItemArgument for NumericBoolean {}
1805}
1806
1807pub mod routerdesc {
1809 use super::*;
1810 use parse2::ErrorProblem as EP;
1811 use tor_llcrypto::pk::ed25519;
1812
1813 #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, strum::EnumString, strum::Display)]
1817 #[non_exhaustive]
1818 pub enum OverloadGeneralVersion {
1819 #[strum(serialize = "1")]
1821 V1,
1822 }
1823
1824 impl NormalItemArgument for OverloadGeneralVersion {}
1825
1826 #[derive(Debug, Clone, Copy, PartialEq, Eq, Deftly)]
1830 #[derive_deftly(ItemValueParseable)]
1831 #[non_exhaustive]
1832 pub struct OverloadGeneral {
1833 pub version: OverloadGeneralVersion,
1835 pub since: Iso8601TimeSp,
1837 }
1838
1839 #[derive(Clone, Debug, PartialEq, Eq, Deftly)]
1843 #[derive_deftly(ItemValueParseable)]
1844 #[non_exhaustive]
1845 pub struct RouterDescIntroItem {
1846 pub nickname: Nickname,
1848
1849 pub address: std::net::Ipv4Addr,
1851
1852 pub orport: u16,
1854
1855 pub socksport: u16,
1857
1858 pub dirport: u16,
1860 }
1861
1862 #[derive(Clone, Debug, PartialEq, Eq, Deftly)]
1866 #[derive_deftly(ItemValueParseable)]
1867 #[non_exhaustive]
1868 pub struct ExtraInfoDigests {
1869 pub sha1: FixedB16U<20>,
1871
1872 pub sha2: Option<FixedB64<32>>,
1874 }
1875
1876 #[derive(Debug, Clone, Default, Deftly)]
1878 #[derive_deftly(AsMutSelf)]
1879 #[allow(clippy::exhaustive_structs)]
1880 pub struct RouterHashAccu {
1881 pub sha1: Option<[u8; 20]>,
1883 pub sha256: Option<[u8; 32]>,
1885 }
1886
1887 #[derive(Debug, Clone, PartialEq, Eq, Deftly)]
1889 #[derive_deftly(ItemValueEncodable)]
1890 #[allow(clippy::exhaustive_structs)]
1891 pub struct RouterSigEd25519(pub ed25519::Signature);
1894
1895 impl RouterSigEd25519 {
1896 const HASH_PREFIX_MAGIC: &str = "Tor router descriptor signature v1";
1898
1899 fn hash(document_sofar: &str, signature_item_kw_spc: &[&str]) -> [u8; 32] {
1904 debug_assert!(
1905 signature_item_kw_spc
1906 .last()
1907 .expect("signature_item_kw_spc")
1908 .ends_with(" ")
1909 );
1910 let mut h = tor_llcrypto::d::Sha256::new();
1911 h.update(Self::HASH_PREFIX_MAGIC);
1912 h.update(document_sofar);
1913 for b in signature_item_kw_spc {
1914 h.update(b);
1915 }
1916 h.finalize().into()
1917 }
1918
1919 pub fn new_sign_netdoc(
1968 private_key: &ed25519::Keypair,
1969 encoder: &NetdocEncoder,
1970 item_keyword: &str,
1971 ) -> StdResult<Self, Bug> {
1972 let signature = private_key
1973 .sign(&Self::hash(encoder.text_sofar()?, &[item_keyword, " "]))
1974 .to_bytes()
1975 .into();
1976 Ok(RouterSigEd25519(signature))
1977 }
1978 }
1979
1980 impl SignatureItemParseable for RouterSigEd25519 {
1981 type HashAccu = RouterHashAccu;
1982
1983 fn from_unparsed_and_body(
1984 mut item: UnparsedItem<'_>,
1985 hash_inputs: &SignatureHashInputs<'_>,
1986 hash: &mut Self::HashAccu,
1987 ) -> Result<Self, EP> {
1988 let args = item.args_mut();
1990 let sig = FixedB64::<64>::from_args(args)
1991 .map_err(|e| args.handle_error("router-sig-ed25519", e))?
1992 .0;
1993 let sig = ed25519::Signature::from(sig);
1994 hash.sha256 = Some(Self::hash(
1995 hash_inputs.document_sofar,
1996 &[hash_inputs.signature_item_kw_spc],
1997 ));
1998 Ok(Self(sig))
1999 }
2000 }
2001
2002 #[derive(Debug, Clone, PartialEq, Eq)]
2005 #[allow(clippy::exhaustive_structs)]
2006 pub struct RouterSignature(pub Vec<u8>);
2007
2008 impl SignatureItemParseable for RouterSignature {
2009 type HashAccu = RouterHashAccu;
2010
2011 fn from_unparsed_and_body(
2012 mut item: UnparsedItem<'_>,
2013 hash_inputs: &SignatureHashInputs<'_>,
2014 hash: &mut Self::HashAccu,
2015 ) -> Result<Self, EP> {
2016 let args = item.args_mut();
2018 if args.next().is_some() {
2019 return Err(EP::UnexpectedArgument {
2020 column: args.prev_arg_column(),
2021 });
2022 }
2023 let obj = item.object().ok_or(EP::MissingObject)?.decode_data()?;
2024
2025 let mut h = tor_llcrypto::d::Sha1::new();
2026 h.update(hash_inputs.document_sofar);
2027 h.update(hash_inputs.signature_item_line);
2028 h.update("\n");
2029 hash.sha1 = Some(h.finalize().into());
2030
2031 Ok(Self(obj))
2032 }
2033 }
2034}
2035
2036#[cfg(test)]
2037mod test {
2038 #![allow(clippy::bool_assert_comparison)]
2040 #![allow(clippy::clone_on_copy)]
2041 #![allow(clippy::dbg_macro)]
2042 #![allow(clippy::mixed_attributes_style)]
2043 #![allow(clippy::print_stderr)]
2044 #![allow(clippy::print_stdout)]
2045 #![allow(clippy::single_char_pattern)]
2046 #![allow(clippy::unwrap_used)]
2047 #![allow(clippy::unchecked_time_subtraction)]
2048 #![allow(clippy::useless_vec)]
2049 #![allow(clippy::needless_pass_by_value)]
2050 use itertools::Itertools;
2052
2053 use base64ct::Encoding;
2054
2055 use super::*;
2056 use crate::{Pos, Result};
2057
2058 fn base64_decode_ignore_ws(s: &str) -> std::result::Result<Vec<u8>, base64ct::Error> {
2060 let mut s = s.to_string();
2061 s.retain(|c| !c.is_ascii_whitespace());
2062 base64ct::Base64::decode_vec(s.as_str())
2063 }
2064
2065 #[test]
2066 fn base64() -> Result<()> {
2067 assert_eq!("Mi43MTgyOA".parse::<B64>()?.as_bytes(), &b"2.71828"[..]);
2070 assert!("Mi43MTgyOA".parse::<B64>()?.check_len(7..8).is_ok());
2071 assert_eq!("Mg".parse::<B64>()?.as_bytes(), &b"2"[..]);
2072 assert!("Mg".parse::<B64>()?.check_len(1..2).is_ok());
2073 assert_eq!(
2074 "8J+NkvCfjZLwn42S8J+NkvCfjZLwn42S"
2075 .parse::<B64>()?
2076 .as_bytes(),
2077 "๐๐๐๐๐๐".as_bytes()
2078 );
2079 assert!(
2080 "8J+NkvCfjZLwn42S8J+NkvCfjZLwn42S"
2081 .parse::<B64>()?
2082 .check_len(24..25)
2083 .is_ok()
2084 );
2085 assert!(
2086 "ppwthHXW8kXD0f9fE7UPYsOAAu4uj5ORwSomCMxKkz8="
2087 .parse::<B64>()?
2088 .check_len(32..33)
2089 .is_ok()
2090 );
2091 assert_eq!("Mi43MTgyOA==".parse::<B64>()?.as_bytes(), &b"2.71828"[..]);
2093 assert!("Mi43MTgyOA==".parse::<B64>()?.check_len(7..8).is_ok());
2094 assert_eq!("Mg==".parse::<B64>()?.as_bytes(), &b"2"[..]);
2095 assert!("Mg==".parse::<B64>()?.check_len(1..2).is_ok());
2096
2097 assert!("Mi43!!!!!!".parse::<B64>().is_err());
2100 assert!("Mi".parse::<B64>().is_err());
2102 assert!(
2103 "ppwthHXW8kXD0f9fE7UPYsOAAu4uj5ORwSomCMxaaaa"
2104 .parse::<B64>()
2105 .is_err()
2106 );
2107 assert!("Mi43MTgyOA".parse::<B64>()?.check_len(8..).is_err());
2109 Ok(())
2110 }
2111
2112 #[test]
2113 fn base64_lengths() -> Result<()> {
2114 assert_eq!("".parse::<B64>()?.as_bytes(), b"");
2115 assert!("=".parse::<B64>().is_err());
2116 assert!("==".parse::<B64>().is_err());
2117 assert!("B".parse::<B64>().is_err());
2118 assert!("B=".parse::<B64>().is_err());
2119 assert!("B==".parse::<B64>().is_err());
2120 assert!("Bg=".parse::<B64>().is_err());
2121 assert_eq!("Bg".parse::<B64>()?.as_bytes(), b"\x06");
2122 assert_eq!("Bg==".parse::<B64>()?.as_bytes(), b"\x06");
2123 assert_eq!("BCg".parse::<B64>()?.as_bytes(), b"\x04\x28");
2124 assert_eq!("BCg=".parse::<B64>()?.as_bytes(), b"\x04\x28");
2125 assert!("BCg==".parse::<B64>().is_err());
2126 assert_eq!("BCDE".parse::<B64>()?.as_bytes(), b"\x04\x20\xc4");
2127 assert!("BCDE=".parse::<B64>().is_err());
2128 assert!("BCDE==".parse::<B64>().is_err());
2129 Ok(())
2130 }
2131
2132 #[test]
2133 fn base64_rev() {
2134 use base64ct::{Base64, Base64Unpadded};
2135
2136 for n in 0..=5 {
2139 for c_vec in std::iter::repeat_n("ACEQg/=".chars(), n).multi_cartesian_product() {
2140 let s: String = c_vec.into_iter().collect();
2141 #[allow(clippy::print_stderr)]
2142 let b = match s.parse::<B64>() {
2143 Ok(b) => {
2144 eprintln!("{:10} {:?}", &s, b.as_bytes());
2145 b
2146 }
2147 Err(_) => {
2148 eprintln!("{:10} Err", &s);
2149 continue;
2150 }
2151 };
2152 let b = b.as_bytes();
2153
2154 let ep = Base64::encode_string(b);
2155 let eu = Base64Unpadded::encode_string(b);
2156
2157 assert!(
2158 s == ep || s == eu,
2159 "{:?} decoded to {:?} giving neither {:?} nor {:?}",
2160 s,
2161 b,
2162 ep,
2163 eu
2164 );
2165 }
2166 }
2167 }
2168
2169 #[test]
2170 fn base16() -> anyhow::Result<()> {
2171 let chk = |s: &str, b: &[u8]| -> anyhow::Result<()> {
2172 let parsed = s.parse::<B16>()?;
2173 assert_eq!(parsed.as_bytes(), b, "{s:?}");
2174 assert_eq!(parsed.to_string(), s.to_ascii_lowercase());
2175
2176 let parsed = s.parse::<B16U>()?;
2177 assert_eq!(parsed.as_bytes(), b, "{s:?}");
2178 assert_eq!(parsed.to_string(), s.to_ascii_uppercase());
2179 Ok(())
2180 };
2181
2182 chk("332e313432", b"3.142")?;
2183 chk("332E313432", b"3.142")?;
2184 chk("332E3134", b"3.14")?;
2185
2186 assert!("332E313".parse::<B16>().is_err());
2187 assert!("332G3134".parse::<B16>().is_err());
2188 Ok(())
2189 }
2190
2191 #[test]
2192 fn curve25519() -> Result<()> {
2193 use tor_llcrypto::pk::curve25519::PublicKey;
2194 let k1 = "ppwthHXW8kXD0f9fE7UPYsOAAu4uj5ORwSomCMxKkz8=";
2195 let k2 = hex::decode("a69c2d8475d6f245c3d1ff5f13b50f62c38002ee2e8f9391c12a2608cc4a933f")
2196 .unwrap();
2197 let k2: &[u8; 32] = &k2[..].try_into().unwrap();
2198
2199 let k1: PublicKey = k1.parse::<Curve25519Public>()?.into();
2200 assert_eq!(k1, (*k2).into());
2201
2202 assert!(
2203 "ppwthHXW8kXD0f9fE7UPYsOAAu4uj5ORwSomCMxKkz"
2204 .parse::<Curve25519Public>()
2205 .is_err()
2206 );
2207 assert!(
2208 "ppwthHXW8kXD0f9fE7UPYsOAAu4uj5ORSomCMxKkz"
2209 .parse::<Curve25519Public>()
2210 .is_err()
2211 );
2212 assert!(
2213 "ppwthHXW8kXD0f9fE7UPYsOAAu4uj5wSomCMxKkz"
2214 .parse::<Curve25519Public>()
2215 .is_err()
2216 );
2217 assert!(
2218 "ppwthHXW8kXD0f9fE7UPYsOAAu4ORwSomCMxKkz"
2219 .parse::<Curve25519Public>()
2220 .is_err()
2221 );
2222
2223 Ok(())
2224 }
2225
2226 #[test]
2227 fn ed25519() -> Result<()> {
2228 use tor_llcrypto::pk::ed25519::Ed25519Identity;
2229 let k1 = "WVIPQ8oArAqLY4XzkcpIOI6U8KsUJHBQhG8SC57qru0";
2230 let k2 = hex::decode("59520f43ca00ac0a8b6385f391ca48388e94f0ab14247050846f120b9eeaaeed")
2231 .unwrap();
2232
2233 let k1: Ed25519Identity = k1.parse::<Ed25519Public>()?.into();
2234 assert_eq!(k1, Ed25519Identity::from_bytes(&k2).unwrap());
2235
2236 assert!(
2237 "WVIPQ8oArAqLY4Xzk0!!!!8KsUJHBQhG8SC57qru"
2238 .parse::<Ed25519Public>()
2239 .is_err()
2240 );
2241 assert!(
2242 "WVIPQ8oArAqLY4XzkcpIU8KsUJHBQhG8SC57qru"
2243 .parse::<Ed25519Public>()
2244 .is_err()
2245 );
2246 assert!(
2247 "WVIPQ8oArAqLY4XzkcpIU8KsUJHBQhG8SC57qr"
2248 .parse::<Ed25519Public>()
2249 .is_err()
2250 );
2251 assert!(
2253 "ppwthHXW8kXD0f9fE7UPYsOAAu4uj5ORwSomCMxaaaa"
2254 .parse::<Curve25519Public>()
2255 .is_err()
2256 );
2257 Ok(())
2258 }
2259
2260 #[test]
2261 fn time() -> Result<()> {
2262 use humantime::parse_rfc3339;
2263 use std::time::SystemTime;
2264
2265 let t = "2020-09-29 13:36:33".parse::<Iso8601TimeSp>()?;
2266 let t: SystemTime = t.into();
2267 assert_eq!(t, parse_rfc3339("2020-09-29T13:36:33Z").unwrap());
2268
2269 assert!("2020-FF-29 13:36:33".parse::<Iso8601TimeSp>().is_err());
2270 assert!("2020-09-29Q13:99:33".parse::<Iso8601TimeSp>().is_err());
2271 assert!("2020-09-29".parse::<Iso8601TimeSp>().is_err());
2272 assert!("too bad, waluigi time".parse::<Iso8601TimeSp>().is_err());
2273
2274 assert_eq!(
2275 "2020-09-29 13:36:33",
2276 "2020-09-29 13:36:33".parse::<Iso8601TimeSp>()?.to_string()
2277 );
2278
2279 let t = "2020-09-29T13:36:33".parse::<Iso8601TimeNoSp>()?;
2280 let t: SystemTime = t.into();
2281 assert_eq!(t, parse_rfc3339("2020-09-29T13:36:33Z").unwrap());
2282
2283 assert!("2020-09-29 13:36:33".parse::<Iso8601TimeNoSp>().is_err());
2284 assert!("2020-09-29Q13:99:33".parse::<Iso8601TimeNoSp>().is_err());
2285 assert!("2020-09-29".parse::<Iso8601TimeNoSp>().is_err());
2286 assert!("too bad, waluigi time".parse::<Iso8601TimeNoSp>().is_err());
2287
2288 assert_eq!(
2289 "2020-09-29T13:36:33",
2290 "2020-09-29T13:36:33"
2291 .parse::<Iso8601TimeNoSp>()?
2292 .to_string()
2293 );
2294
2295 Ok(())
2296 }
2297
2298 #[test]
2299 fn rsa_public_key() {
2300 let key_b64 = r#"
2302 MIIBigKCAYEAsDkzTcKS4kAF56R2ijb9qCek53tKC1EwMdpWMk58bB28fY6kHc55
2303 E7n1hB+LC5neZlx88GKuZ9k8P3g0MlO5ejalcfBdIIm28Nz86JXf/L23YnEpxnG/
2304 IpxZEcmx/EYN+vwp72W3DGuzyntaoaut6lGJk+O/aRCLLcTm4MNznvN1ackK2H6b
2305 Xm2ejRwtVRLoPKODJiPGl43snCfXXWsMH3IALFOgm0szPLv2fAJzBI8VWrUN81M/
2306 lgwJhG6+xbr1CkrXI5fKs/TNr0B0ydC9BIZplmPrnXaeNklnw1cqUJ1oxDSgBrvx
2307 rpDo7paObjSPV26opa68QKGa7Gu2MZQC3RzViNCbawka/108g6hSUkoM+Om2oivr
2308 DvtMOs10MjsfibEBVnwEhqnlb/gj3hJkYoGRsCwAyMIaMObHcmAevMJRWAjGCc8T
2309 GMS9dSmg1IZst+U+V2OCcIHXT6wZ1zPsBM0pYKVLCwtewaq1306k0n+ekriEo7eI
2310 FS3Dd/Dx/a6jAgMBAAE=
2311 "#;
2312 let key_bytes = base64_decode_ignore_ws(key_b64).unwrap();
2313 let rsa = RsaPublicParse1Helper::from_vec(key_bytes, Pos::None).unwrap();
2314
2315 let bits = tor_llcrypto::pk::rsa::PublicKey::from(rsa.clone()).bits();
2316 assert_eq!(bits, 3072);
2317
2318 assert!(rsa.clone().check_exponent(65537).is_ok());
2320 assert!(rsa.clone().check_exponent(1337).is_err());
2321 assert!(rsa.clone().check_len_eq(3072).is_ok());
2322 assert!(rsa.clone().check_len(1024..=4096).is_ok());
2323 assert!(rsa.clone().check_len(1024..=1024).is_err());
2324 assert!(rsa.check_len(4096..).is_err());
2325
2326 let failure = RsaPublicParse1Helper::from_vec(vec![1, 2, 3], Pos::None);
2328 assert!(failure.is_err());
2329 }
2330
2331 #[test]
2332 fn ed_cert() {
2333 use tor_llcrypto::pk::ed25519::Ed25519Identity;
2334
2335 let cert_b64 = r#"
2337 AQQABwRNAR6m3kq5h8i3wwac+Ti293opoOP8RKGP9MT0WD4Bbz7YAQAgBACGCdys
2338 G7AwsoYMIKenDN6In6ReiGF8jaYoGqmWKDVBdGGMDIZyNIq+VdhgtAB1EyNFHJU1
2339 jGM0ir9dackL+PIsHbzJH8s/P/8RfUsKIL6/ZHbn3nKMxLH/8kjtxp5ScAA=
2340 "#;
2341 let cert_bytes = base64_decode_ignore_ws(cert_b64).unwrap();
2342 let right_subject_key: Ed25519Identity = "HqbeSrmHyLfDBpz5OLb3eimg4/xEoY/0xPRYPgFvPtg"
2344 .parse::<Ed25519Public>()
2345 .unwrap()
2346 .into();
2347 let wrong_subject_key: Ed25519Identity = "WVIPQ8oArAqLY4XzkcpIOI6U8KsUJHBQhG8SC57qru0"
2349 .parse::<Ed25519Public>()
2350 .unwrap()
2351 .into();
2352
2353 let cert = UnvalidatedEdCert::from_vec(cert_bytes, Pos::None)
2355 .unwrap()
2356 .check_cert_type(tor_cert::CertType::IDENTITY_V_SIGNING)
2357 .unwrap()
2358 .check_subject_key_is(&right_subject_key)
2359 .unwrap();
2360 assert!(
2362 cert.clone()
2363 .check_cert_type(tor_cert::CertType::RSA_ID_X509)
2364 .is_err()
2365 );
2366 assert!(cert.check_subject_key_is(&wrong_subject_key).is_err());
2368
2369 let failure = UnvalidatedEdCert::from_vec(vec![1, 2, 3], Pos::None);
2371 assert!(failure.is_err());
2372 }
2373
2374 #[test]
2375 fn fingerprint() -> Result<()> {
2376 use tor_llcrypto::pk::rsa::RsaIdentity;
2377 let fp1 = "7467 A97D 19CD 2B4F 2BC0 388A A99C 5E67 710F 847E";
2378 let fp2 = "7467A97D19CD2B4F2BC0388AA99C5E67710F847E";
2379 let fp3 = "$7467A97D19CD2B4F2BC0388AA99C5E67710F847E";
2380 let fp4 = "$7467A97D19CD2B4F2BC0388AA99C5E67710F847E=fred";
2381
2382 let k = hex::decode(fp2).unwrap();
2383 let k = RsaIdentity::from_bytes(&k[..]).unwrap();
2384
2385 assert_eq!(RsaIdentity::from(fp1.parse::<SpFingerprint>()?), k);
2386 assert_eq!(RsaIdentity::from(fp2.parse::<SpFingerprint>()?), k);
2387 assert!(fp3.parse::<SpFingerprint>().is_err());
2388 assert!(fp4.parse::<SpFingerprint>().is_err());
2389
2390 assert!(fp1.parse::<Fingerprint>().is_err());
2391 assert_eq!(RsaIdentity::from(fp2.parse::<Fingerprint>()?), k);
2392 assert!(fp3.parse::<Fingerprint>().is_err());
2393 assert!(fp4.parse::<Fingerprint>().is_err());
2394 assert_eq!(Fingerprint(k).to_string(), fp2);
2395
2396 assert!(fp1.parse::<LongIdent>().is_err());
2397 assert_eq!(RsaIdentity::from(fp2.parse::<LongIdent>()?), k);
2398 assert_eq!(RsaIdentity::from(fp3.parse::<LongIdent>()?), k);
2399 assert_eq!(RsaIdentity::from(fp4.parse::<LongIdent>()?), k);
2400
2401 assert!("xxxx".parse::<Fingerprint>().is_err());
2402 assert!("ffffffffff".parse::<Fingerprint>().is_err());
2403
2404 let fp_b64 = "dGepfRnNK08rwDiKqZxeZ3EPhH4";
2405 assert_eq!(RsaIdentity::from(fp_b64.parse::<Base64Fingerprint>()?), k);
2406 assert_eq!(Base64Fingerprint(k).to_string(), fp_b64);
2407
2408 Ok(())
2409 }
2410
2411 #[test]
2412 fn nickname() -> anyhow::Result<()> {
2413 let n: Nickname = "Foo".parse()?;
2414 assert_eq!(n.as_str(), "Foo");
2415 assert_eq!(n.to_string(), "Foo");
2416
2417 let word = "Untr1gonometr1cally";
2418 assert_eq!(word.len(), 19);
2419 let long: Nickname = word.parse()?;
2420 assert_eq!(long.as_str(), word);
2421
2422 let too_long = "abcdefghijklmnopqrstuvwxyz";
2423 let not_ascii = "Eyjafjallajรถkull";
2424 let too_short = "";
2425 let other_invalid = "contains space";
2426 assert!(not_ascii.len() <= 19);
2427 assert!(too_long.parse::<Nickname>().is_err());
2428 assert!(not_ascii.parse::<Nickname>().is_err());
2429 assert!(too_short.parse::<Nickname>().is_err());
2430 assert!(other_invalid.parse::<Nickname>().is_err());
2431
2432 Ok(())
2433 }
2434
2435 #[test]
2437 fn hostname() {
2438 use std::net::IpAddr;
2439
2440 let chk_name = |s: &str| {
2442 let n: Hostname = s.parse().expect(s);
2443 assert_eq!(n.as_str(), s);
2444 assert_eq!(n.to_string(), s);
2445 assert_eq!(s.parse::<InternetHost>().expect(s), InternetHost::Name(n));
2446 };
2447
2448 let chk_either = |s: &str| {
2451 let h: InternetHost = s.parse().expect(s);
2452 let a: IpAddr = s.parse().expect(s);
2453 assert_eq!(h, InternetHost::IpAddr(a), "{s:?}");
2454 assert_eq!(h.to_string(), a.to_string(), "{s:?}");
2455 };
2456
2457 let chk_addr = |s: &str| {
2459 let _: InvalidHostname = s.parse::<Hostname>().expect_err(s);
2460 chk_either(s);
2461 };
2462
2463 let chk_bad = |s: &str| {
2465 let _: InvalidHostname = s.parse::<Hostname>().expect_err(s);
2466 let _: InvalidInternetHost = s.parse::<InternetHost>().expect_err(s);
2467 };
2468
2469 chk_name("foo.bar");
2470 chk_name("localhost");
2471 chk_name("tor.invalid");
2472 chk_name("example.com");
2473
2474 chk_bad("");
2476 chk_bad("foo bar");
2477 chk_bad("foo..bar");
2478 chk_bad("foo.-bar");
2479 chk_bad(" foo.bar ");
2480 chk_bad("[::1]");
2481
2482 chk_bad("1");
2488 chk_bad("127.0.0.023");
2489
2490 chk_bad("1.2.3.4.5");
2493
2494 chk_either("0.0.0.0");
2495 chk_either("127.0.0.1");
2496 chk_addr("::");
2497 chk_addr("::1");
2498 chk_addr("2001:0db8:85a3:0000:0000:8a2e:0370:7334");
2499 chk_addr("::ffff:192.0.2.3"); }
2501
2502 #[test]
2503 fn contact_info() -> anyhow::Result<()> {
2504 use encode::NetdocEncodable;
2505 use parse2::{ParseInput, parse_netdoc};
2506
2507 const S: &str = "some relay operator";
2508 let n: ContactInfo = S.parse()?;
2509 assert_eq!(n.as_str(), S);
2510 assert_eq!(n.to_string(), S);
2511
2512 let bad = |s: &str| {
2513 let _: InvalidContactInfo = s.parse::<ContactInfo>().unwrap_err();
2514 };
2515
2516 bad(" starts with space");
2517 bad("contains\nnewline");
2518
2519 #[derive(PartialEq, Debug, Deftly)]
2520 #[derive_deftly(NetdocParseable, NetdocEncodable)]
2521 struct TestDoc {
2522 pub intro: (),
2523 pub contact: ContactInfo,
2524 }
2525
2526 let roundtrip = |s: &str| -> anyhow::Result<()> {
2527 let doc = TestDoc {
2528 intro: (),
2529 contact: s.parse()?,
2530 };
2531 let mut enc = NetdocEncoder::new();
2532 doc.encode_unsigned(&mut enc)?;
2533 let enc = enc.finish()?;
2534 let reparsed = parse_netdoc::<TestDoc>(&ParseInput::new(&enc, "<test>"))?;
2535 assert_eq!(doc, reparsed);
2536 Ok(())
2537 };
2538
2539 roundtrip("normal")?;
2540 roundtrip("trailing white space ")?;
2541 roundtrip("wtf is this allowed in \x03 netdocs\r")?; Ok(())
2544 }
2545
2546 #[test]
2549 fn numeric_boolean() {
2550 let chk = |s: &str| {
2551 assert_eq!(NumericBoolean::from_str(s).expect(s).to_string(), s);
2552 };
2553 chk("0");
2554 chk("1");
2555 assert!(NumericBoolean::from_str("10000").is_err());
2557 }
2558
2559 #[test]
2561 fn sp_fingerprint() {
2562 use derive_deftly::Deftly;
2563 use tor_llcrypto::pk::rsa::RsaIdentity;
2564
2565 use crate::parse2::ErrorProblem;
2566
2567 #[derive(Deftly)]
2568 #[derive_deftly(NetdocParseable)]
2569 struct Wrapper {
2570 #[deftly(netdoc(single_arg))]
2571 fingerprint: SpFingerprint,
2572 }
2573
2574 fn parse2(s: &str) -> std::result::Result<SpFingerprint, ErrorProblem> {
2576 use crate::parse2::{self, ParseInput};
2577
2578 let s = format!("fingerprint {s}\n");
2579 parse2::parse_netdoc::<Wrapper>(&ParseInput::new(&s, ""))
2580 .map(|x| x.fingerprint)
2581 .map_err(|x| x.problem)
2582 }
2583
2584 assert_eq!(
2586 parse2(&vec!["ABAB"; 10].join(" ")).unwrap(),
2587 SpFingerprint(RsaIdentity::from_bytes(&[0xAB; 20]).unwrap())
2588 );
2589
2590 assert_eq!(
2592 parse2(&vec!["ABAB"; 11].join(" ")).unwrap(),
2593 SpFingerprint(RsaIdentity::from_bytes(&[0xAB; 20]).unwrap())
2594 );
2595
2596 assert!(matches!(
2598 parse2(&vec!["ABAB"; 9].join(" ")).unwrap_err(),
2599 ErrorProblem::MissingArgument { .. }
2600 ));
2601
2602 assert!(matches!(
2607 parse2("0000 000000 00 0000 0000 0000 0000 0000 0000 0000").unwrap_err(),
2608 ErrorProblem::InvalidArgument { .. }
2609 ));
2610
2611 assert!(matches!(
2613 parse2(&vec!["ZZZZ"; 10].join(" ")).unwrap_err(),
2614 ErrorProblem::InvalidArgument { .. }
2615 ));
2616 }
2617}