Skip to main content

tor_netdoc/types/
misc.rs

1//! Types used to parse arguments of entries in a directory document.
2//!
3//! There are some types that are pretty common, like "ISOTime",
4//! "base64-encoded data", and so on.
5//!
6//! These types shouldn't be exposed outside of the netdoc crate.
7
8pub 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    // `E` for "encode`; different from `parse2::MultiplicitySelector`
37    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        // `P2` for "parse2`; different from `encode::MultiplicitySelector`
46        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
66/// Describes a value that van be decoded from a bunch of bytes.
67///
68/// Used for decoding the objects between BEGIN and END tags.
69pub(crate) trait FromBytes: Sized {
70    /// Try to parse a value of this type from a byte slice
71    fn from_bytes(b: &[u8], p: crate::Pos) -> crate::Result<Self>;
72    /// Try to parse a value of this type from a vector of bytes,
73    /// and consume that value
74    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    /// Implement conversion traits for a transparent newtype around bytes - shared code
81    ///
82    /// This is precisely `#[derive_deftly(Transparent)]`, but in the form of a deftly module,
83    /// so that other derives (eg `BytesTransparent`) can re-use it.
84    Transparent beta_deftly:
85
86    // Expands to bullet points for "generated code", except omitting
87    // `AsRef` & `AsMut` because some uses sites have additional impls of those,
88    // which are best presented together in the docs.
89  ${define TRANSPARENT_DOCS_IMPLS {
90    ///  * impls of `Deref`, `DerefMut`
91    ///  * impls of `From<field>` and "`Into`" (technically, `From<Self> for field`)
92  }}
93
94    // Expands to the implementations
95  ${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    /// Implement conversion traits for an arbitrary transparent newtype
144    ///
145    /// # Requirements
146    ///
147    ///  * Self should be a single-field struct
148    ///  * Self should have no runtime invariants
149    ///
150    /// # Generated code
151    ///
152    $TRANSPARENT_DOCS_IMPLS
153    ///  * impls of `AsMut<field>`, `AsRef<field>`
154    ///
155    /// # Guidelines
156    ///
157    ///  * the field should be `pub`, with `#[allow(clippy::exhaustive_structs)]`
158    ///  * derive `Hash`, `Debug` and (usually) `Clone`
159    ///  * consider deriving `PartialEq` and `Eq`
160    ///    but for types containing bytes, use [`ConstantTimeEq`],
161    ///    eg with [`#[derive_deftly(BytesTransparent)]`](derive_deftly_template_BytesTransparent)
162    ///    (instead of `Transparent`).
163    ///  * implement `FromStr`, `Display`, `NormalItemArgument`, as required
164    Transparent for struct, beta_deftly:
165
166    $TRANSPARENT_IMPLS
167}
168
169define_derive_deftly! {
170    use Transparent;
171
172    /// Implement `ConstantTimeEq`, `.as_bytes()`, etc., for a transparent newtype around bytes
173    ///
174    /// # Requirements
175    ///
176    ///  * Self should be a single-field struct
177    ///  * Self should deref to `&[u8]` (and to `&mut [u8]`).
178    ///  * (so Self should have no runtime invariants)
179    ///
180    /// # Generated code
181    ///
182    ///  * impls of `ConstantTimeEq`, `Eq`, `PartialEq`
183    ///  * `as_bytes()` method
184    ${TRANSPARENT_DOCS_IMPLS}
185    ///  * impls of `AsMut<field>`, `AsRef<field>`, `AsRef<[u8]>`, `AsMut<[u8]>`
186    ///
187    // We could derive Debug here but then we have to deal with the Fixed's N
188    // which gets quite fiddly.
189    //
190    /// # Guidelines
191    ///
192    ///  * derive `Hash` and write `#[allow(clippy::derived_hash_with_manual_eq)]`
193    ///  * impl `FromStr` and `Display` (if required, which they usually will be)
194    ///  * derive `derive_more::Debug` eg with `#[debug(r#"B64("{self}")"#)]`
195    ///  * `impl NormalItemArgument` if appropriate (ie the representation has no spaces)
196    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    $/// `$tname` is `Eq` via its constant-time implementation.
208    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        /// Return the byte array from this object.
217        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
241/// Types for decoding base64-encoded values.
242mod 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    /// A byte array, encoded in base64 with optional padding.
249    ///
250    /// On output (`Display`), output is unpadded.
251    #[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        /// Return this object if its length is within the provided bounds
283        /// object, or an error otherwise.
284        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        /// Try to convert this object into an array of N bytes.
293        ///
294        /// Return an error if the length is wrong.
295        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    /// A byte array encoded in a hexadecimal with a fixed length.
311    #[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
339// ============================================================
340
341/// Types for decoding hex-encoded values.
342mod b16impl {
343    use super::*;
344    use crate::{Error, NetdocErrorKind as EK, Pos, Result};
345
346    /// A byte array encoded in hexadecimal; prints in lowercase
347    ///
348    /// Both uppercase and lowercase are tolerated when parsing.
349    #[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    /// A byte array encoded in hexadecimal; prints in uppercase
358    ///
359    /// Both uppercase and lowercase are tolerated when parsing.
360    #[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    /// A fixed-length version of [`B16U`].
369    #[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            // `hex` has `hex::encode` but that allocates a `String`, which this approach doesn't
410            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            // `hex` has `hex::encode_upper` but that allocates a `String`
420            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            // TODO DIRAUTH combine this with the same code in `Display for B16U`
430            // `hex` has `hex::encode_upper` but that allocates a `String`
431            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
443// ============================================================
444
445/// Types for decoding curve25519 keys
446mod curve25519impl {
447    use super::*;
448
449    use crate::{Error, NormalItemArgument, Result, types::misc::FixedB64};
450    use tor_llcrypto::pk::curve25519::PublicKey;
451
452    /// A Curve25519 public key, encoded in base64 with optional padding
453    #[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
476// ============================================================
477
478/// Types for decoding ed25519 keys
479mod 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    /// An alleged ed25519 public key, encoded in base64 with optional
487    /// padding.
488    #[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    /// Helper that checks for the presence of `ed25519`.
517    #[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 encoded as `ed25519`.
523        Ed25519,
524    }
525
526    impl NormalItemArgument for Ed25519AlgorithmString {}
527
528    /// An Ed25519 public key found in a micro descriptor `id` line.
529    #[derive(Debug, Clone, PartialEq, Eq, Deftly)]
530    #[derive_deftly(ItemValueParseable)]
531    #[non_exhaustive]
532    pub struct Ed25519IdentityLine {
533        /// Fixed magic identifier (`ed25519`) for this line.
534        pub alg: Ed25519AlgorithmString,
535
536        /// The actual Ed25519 identity.
537        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
562// ============================================================
563
564/// Dummy types like [`Ignored`]
565mod ignored_impl {
566    use super::*;
567
568    use crate::parse2::ErrorProblem as EP;
569
570    /// Part of a network document, that isn't actually there.
571    ///
572    /// Used as a standin in `ns_type!` calls in various netstatus `each_variety.rs`.
573    /// The effect is as if the field were omitted from the containing type.
574    ///
575    ///  * When used as item(s) (ie, a field type when deriving `NetdocParseable\[Fields\]`):
576    ///    **ignores any number** of items with that field's keyword during parsing,
577    ///    and emits none during encoding.
578    ///
579    ///    (To *reject* documents containing this item, use `Option<Void>`,
580    ///    but note that the spec says unknown items should be ignored,
581    ///    which would normally include items which are merely missing from one variety.)
582    ///
583    ///  * When used as an argument (ie, a field type when deriving `ItemValueParseable`,
584    ///    or with `netdoc(single_arg)`  when deriving `NetdocParseable\[Fields\]`):
585    ///    consumes **no arguments** during parsing, and emits none during encoding.
586    ///
587    ///  * When used as an object field (ie, `netdoc(object)` when deriving `ItemValueParseable`):
588    ///    **rejects** an object - failing the parse if one is present.
589    ///    (Functions similarly to `Option<Void>`, but prefer `NotPresent` as it's clearer.)
590    ///
591    /// There are bespoke impls of the multiplicity traits
592    /// `ItemSetMethods` and `ObjectSetMethods`:
593    /// don't wrap this type in `Option` or `Vec`.
594    //
595    // TODO we'll need to implement ItemArgument etc., for encoding, too.
596    #[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    /// Ignored part of a network document.
603    ///
604    /// With `parse2`, can be used as an item, object, or even flattened-fields.
605    ///
606    /// When deriving `parse2` traits, and a field is absent in a particular netstatus variety,
607    /// use `ns_type!` with [`NotPresent`], rather than `Ignored`.
608    ///
609    /// During encoding as an Items or Objects, will be entirely omitted,
610    /// via the multiplicity arrangements.
611    ///
612    /// Cannot be encoded as an Argument: if this is not the last
613    /// Argument, we need something to put into the output document to avoid generating
614    /// a document with the arguments out of step.  If it *is* the last argument,
615    /// it could simply be omitted, since additional arguments are in any case ignored.
616    #[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    /// An Item or Object that would be ignored during parsing and is omitted during encoding
622    ///
623    /// This is the "single" item type for encoding multiplicity for Items or Objects,
624    /// for [`Ignored`].
625    ///
626    /// This type is uninhabited.
627    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            // allow any label
695            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// ============================================================
746
747/// Information about unknown values, which may have been retained as a `T`
748///
749/// Won't grow additional variants - but, `Retained` is only included conditionally.
750///
751/// Also used in the form `Unknown<()>` to indicate whether unknown values *should* be retained.
752///
753/// ### Example
754///
755/// ```
756/// # {
757/// #![cfg(feature = "retain-unknown")]
758///
759/// use tor_netdoc::types::Unknown;
760///
761/// let mut unk: Unknown<Vec<String>> = Unknown::new_retained_default();
762/// unk.with_mut_unknown(|u| u.push("something-we-found".into()));
763/// assert_eq!(unk.into_retained().unwrap(), ["something-we-found"]);
764/// # }
765/// ```
766///
767/// ### Equality comparison, semantics
768///
769/// Two `Unknown` are consider equal if both have the same record of unknown values,
770/// or if neither records unknown values at all.
771///
772/// `Unknown` is not `Eq` or `Ord` because we won't want to relate a `Discarded`
773/// to a `Retained`.  That would be a logic error.  `partial_cmp` gives `None` for this.
774#[derive(Debug, PartialEq, Clone, Copy, Hash)]
775#[allow(clippy::exhaustive_enums)] // this isn't going to change
776pub enum Unknown<T> {
777    /// The parsing discarded unknown values and they are no longer available.
778    Discarded(PhantomData<T>),
779
780    /// The document parsing retained (or should retain) unknown values.
781    #[cfg(feature = "retain-unknown")]
782    Retained(T),
783}
784
785impl<T> Unknown<T> {
786    /// Create an `Unknown` which specifies that values were discarded (or should be)
787    pub fn new_discard() -> Self {
788        Unknown::Discarded(PhantomData)
789    }
790
791    /// Map the `Retained`, if there is one
792    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    /// Map the `Retained`, fallibly
797    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    /// Obtain an `Unknown` containing (maybe) a reference
806    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    /// Return the retained unknown data, giving `None` if none was saved
815    ///
816    /// This is the function for disregarding the possible previously existence
817    /// of now-discarded unknown (unrecognised) information.
818    ///
819    /// Use [`into_retained`](Self::into_retained) if it would be a bug
820    /// if unrecognised information had been previously discarded.
821    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    /// Obtain the `Retained` data
830    ///
831    /// Treats lack of retention as an internal error.
832    #[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    /// Start recording unknown information, with a default value for `T`
841    #[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    /// Update the `Retained`, if there is one
850    ///
851    /// Intended for use in parsing, when we encounter an unknown value.
852    ///
853    /// Not provided in `try_` form.  If you think you need this, instead, unconditionally
854    /// parse and verify the unknown value, and then conditionally insert it with this function.
855    /// Don't parse it conditionally - that would skip some validation.
856    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// ============================================================
879
880/// Known keyword (enum) value, or arbitrary string
881///
882/// `T` should be a `Copy` enum with unit variants.
883/// It should have appropriate `FromStr` and `Display`,
884/// as well as [`NormalItemArgument`], impls.
885///
886/// Then `KeywordOrString` will implement the same traits.
887///
888/// Unlike [`Unknown`], unknown values are always retained as strings.
889//
890// `RelayFlags` has machinery for parsing flags and retaining unknown values,
891// but it uses `Unknown` to maybe discard unknown flags,
892// and it is generally quite a lot more complicated.
893#[derive(Debug, PartialEq, Clone, Hash)]
894#[allow(clippy::exhaustive_enums)] // this isn't going to change
895pub enum KeywordOrString<T: Copy> {
896    /// Known and recognised `T`
897    Known(T),
898
899    /// Unknown value in arbitrary syntax
900    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// ============================================================
925
926/// A sequence of `T` items, with their order retained
927///
928/// Normally when a `Vec<T>` appears in a network document,
929/// we expect the items to be sortable - they must impl [`EncodeOrd`](encode::EncodeOrd).
930/// When encoding, the output is always sorted.
931///
932/// *This* type retains the ordering.
933///
934/// Implements the [`encode`] and [`parse2`] item multiplicity traits.
935#[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
941// ============================================================
942
943/// Types for decoding times and dates
944mod 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    /// A wall-clock time, encoded in Iso8601 format with an intervening
954    /// space between the date and time.
955    ///
956    /// (Example: "2020-10-09 17:38:12")
957    #[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    /// Formatting object for parsing the space-separated Iso8601 format.
963    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    /// Formats a SystemTime according to the given format description
979    ///
980    /// Also converts any time::error::format to fmt::Error
981    /// so that it can be unwrapped in the Display trait impl
982    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    /// A wall-clock time, encoded in ISO8601 format without an intervening
998    /// space.
999    ///
1000    /// This represents a specific UTC instant (ie an instant in global civil time).
1001    /// But it may not be able to represent leap seconds.
1002    ///
1003    /// The timezone is not included in the string representation; `+0000` is implicit.
1004    ///
1005    /// (Example: "2020-10-09T17:38:12")
1006    #[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    /// Formatting object for parsing the space-separated Iso8601 format.
1012    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
1036/// Types for decoding RSA keys
1037mod 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    /// The fixed exponent which we require when parsing any RSA key in a netdoc
1045    //
1046    // TODO this value is duplicated a lot in the v1 parser
1047    pub(crate) const RSA_FIXED_EXPONENT: u32 = 65537;
1048
1049    /// The fixed exponent which we require when parsing any RSA key in a netdoc
1050    //
1051    // TODO this value is duplicated a lot in the v1 parser
1052    pub(crate) const RSA_MIN_BITS: usize = 1024;
1053
1054    /// RSA public key, partially processed by `crate::paarse`.
1055    ///
1056    /// As parsed from a base64-encoded object.
1057    /// They key's properties (exponent and size) haven't been checked.
1058    #[allow(non_camel_case_types)]
1059    #[derive(Clone, Debug)]
1060    pub(crate) struct RsaPublicParse1Helper(PublicKey, Pos);
1061
1062    /// RSA signature using SHA-1 as per "Signing documents" in dir-spec
1063    ///
1064    /// <https://spec.torproject.org/dir-spec/netdoc.html#signing>
1065    ///
1066    /// Used for
1067    /// [`AuthCert::dir-key-certification`](crate::doc::authcert::AuthCert::dir-key-certification),
1068    /// for example.
1069    ///
1070    /// # Caveats
1071    ///
1072    /// This type MUST NOT be used for anomalous signatures
1073    /// such as
1074    /// [`AuthCert::dir_key_crosscert`](crate::doc::authcert::AuthCert::dir_key_crosscert);
1075    /// in that case because `dir_key_crosscert`'s
1076    /// set of allowed object labels includes `ID SIGNATURE` whereas this type
1077    /// is always `SIGNATURE`
1078    #[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        /// The bytes of the signature (base64-decoded).
1084        #[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        /// Give an error if the exponent of this key is not 'e'
1102        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        /// Give an error if the length of this key's modulus, in
1112        /// bits, is not contained in 'bounds'
1113        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        /// Give an error if the length of this key's modulus, in
1123        /// bits, is not exactly `n`.
1124        pub(crate) fn check_len_eq(self, n: usize) -> Result<Self> {
1125            self.check_len(n..=n)
1126        }
1127    }
1128
1129    impl RsaSha1Signature {
1130        /// Make a signature according to "Signing documents" in the netdoc spec
1131        ///
1132        /// <https://spec.torproject.org/dir-spec/netdoc.html#signing>
1133        ///
1134        /// `NetdocEncoder` should have had the body of the document
1135        /// (everything except the signatures) already encoded.
1136        ///
1137        /// `item_keyword` is the keyword for the signature item.
1138        /// This is needed because different documents use different keywords,
1139        /// and the keyword is covered by the signature (an annoying is a layering violation).
1140        /// See <https://gitlab.torproject.org/tpo/core/torspec/-/issues/322>.
1141        ///
1142        /// # Example
1143        ///
1144        /// ```
1145        /// use derive_deftly::Deftly;
1146        /// use tor_error::Bug;
1147        /// use tor_llcrypto::pk::rsa;
1148        /// use tor_netdoc::derive_deftly_template_NetdocEncodable;
1149        /// use tor_netdoc::encode::{NetdocEncodable, NetdocEncoder};
1150        /// use tor_netdoc::types::RsaSha1Signature;
1151        ///
1152        /// #[derive(Deftly, Default)]
1153        /// #[derive_deftly(NetdocEncodable)]
1154        /// pub struct Document {
1155        ///     pub document_intro_keyword: (),
1156        /// }
1157        /// #[derive(Deftly)]
1158        /// #[derive_deftly(NetdocEncodable)]
1159        /// pub struct DocumentSignatures {
1160        ///     pub document_signature: RsaSha1Signature,
1161        /// }
1162        /// impl Document {
1163        ///     pub fn encode_sign(&self, k: &rsa::KeyPair) -> Result<String, Bug> {
1164        ///         let mut encoder = NetdocEncoder::new();
1165        ///         self.encode_unsigned(&mut encoder)?;
1166        ///         let document_signature =
1167        ///             RsaSha1Signature::new_sign_netdoc(k, &encoder, "document-signature")?;
1168        ///         let sigs = DocumentSignatures { document_signature };
1169        ///         sigs.encode_unsigned(&mut encoder)?;
1170        ///         let encoded = encoder.finish()?;
1171        ///         Ok(encoded)
1172        ///     }
1173        /// }
1174        ///
1175        /// # fn main() -> Result<(), anyhow::Error> {
1176        /// let k = rsa::KeyPair::generate(&mut tor_basic_utils::test_rng::testing_rng())?;
1177        /// let doc = Document::default();
1178        /// let encoded = doc.encode_sign(&k)?;
1179        /// assert!(encoded.starts_with(concat!(
1180        ///     "document-intro-keyword\n",
1181        ///     "document-signature\n",
1182        ///     "-----BEGIN SIGNATURE-----\n",
1183        /// )));
1184        /// # Ok(())
1185        /// # }
1186        /// ```
1187        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
1205/// Types for decoding Ed25519 certificates
1206mod edcert {
1207    use crate::{NetdocErrorKind as EK, Pos, Result};
1208    use tor_cert::{CertType, Ed25519Cert, KeyUnknownCert};
1209    use tor_llcrypto::pk::ed25519;
1210
1211    /// An ed25519 certificate as parsed from a directory object, with
1212    /// signature not validated.
1213    #[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        /// Give an error if this certificate's type is not `desired_type`.
1233        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        /// Give an error if this certificate's subject_key is not `pk`
1244        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        /// Consume this object and return the inner Ed25519 certificate.
1253        pub(crate) fn into_unchecked(self) -> KeyUnknownCert {
1254            self.0
1255        }
1256    }
1257}
1258
1259/// Digest identifiers, and digests in the form `ALGORITHM=BASE64U`
1260///
1261/// As found in a vote's `m` line.
1262// TODO Use FixedB64 here.
1263mod identified_digest {
1264    use super::*;
1265
1266    define_derive_deftly! {
1267        /// impl `FromStr` and `Display` for an enum with unit variants but also "unknown"
1268        ///
1269        /// Expected input: an enum whose variants are either
1270        ///  * unit variants, perhaps with `#[deftly(string_repr = "string")]`
1271        ///  * singleton tuple variant, containing `String` (or near equivalent)
1272        ///
1273        /// If `#[deftly(string_repro)]` is not specified,
1274        /// the default is snake case of the variant name.
1275        //
1276        // This macro may seem overkill, but open-coding these impls gives opportunities
1277        // for mismatches between FromStr, Display, and the variant name.
1278        //
1279        // TODO consider putting this in tor-basic-utils (maybe with a better name),
1280        // or possibly asking if derive_more want their FromStr to have this.
1281        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)} // anything else had better be Unknown
1301                    // not using `return ..;` makes this a syntax error if there are several.
1302                    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    /// The name of a digest algorithm.
1324    ///
1325    /// Can represent an unrecognised algorithm, so it's parsed and reproduced.
1326    #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deftly)]
1327    #[derive_deftly(StringReprUnitsOrUnknown)]
1328    #[non_exhaustive]
1329    pub enum DigestName {
1330        /// SHA-256
1331        Sha256,
1332        /// Unknown
1333        Unknown(String),
1334    }
1335
1336    /// A single digest made with a nominated digest algorithm, `ALGORITHM=DIGEST`
1337    #[derive(Debug, Clone, Eq, PartialEq, Hash, derive_more::Display)]
1338    #[display("{alg}={value}")]
1339    #[non_exhaustive]
1340    pub struct IdentifiedDigest {
1341        /// The algorithm name.
1342        alg: DigestName,
1343
1344        /// The digest value.
1345        ///
1346        /// Invariant: length is correct for `alg`, assuming `alg` is known.
1347        value: B64,
1348    }
1349
1350    impl NormalItemArgument for DigestName {}
1351    impl NormalItemArgument for IdentifiedDigest {}
1352
1353    /// Invalid syntax parsing an `IdentifiedDigest`
1354    #[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
1392/// Types for decoding RSA fingerprints
1393mod 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    /// A hex-encoded RSA key identity (fingerprint) with spaces in it.
1401    ///
1402    /// <https://spec.torproject.org/dir-spec/server-descriptor-format.html?highlight=fingerprint#item:fingerprint>
1403    ///
1404    /// Netdoc parsing adapter for [`RsaIdentity`]
1405    #[derive(Debug, Clone, Eq, PartialEq, Hash, Deftly)]
1406    #[derive_deftly(Transparent)]
1407    #[allow(clippy::exhaustive_structs)]
1408    pub struct SpFingerprint(pub RsaIdentity);
1409
1410    /// A hex-encoded fingerprint with no spaces.
1411    ///
1412    /// Netdoc parsing adapter for [`RsaIdentity`]
1413    #[derive(Debug, Clone, Eq, PartialEq, Hash, Deftly)]
1414    #[derive_deftly(Transparent)]
1415    #[allow(clippy::exhaustive_structs)]
1416    pub struct Fingerprint(pub RsaIdentity);
1417
1418    /// A base64-encoded fingerprint (unpadded)
1419    ///
1420    /// Netdoc parsing adapter for [`RsaIdentity`]
1421    #[derive(Debug, Clone, Eq, PartialEq, Hash, Deftly)]
1422    #[derive_deftly(Transparent)]
1423    #[allow(clippy::exhaustive_structs)]
1424    pub struct Base64Fingerprint(pub RsaIdentity);
1425
1426    /// A "long identity" in the format used for Family members.
1427    ///
1428    /// Netdoc parsing adapter for [`RsaIdentity`]
1429    #[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    /// Helper: parse an identity from a hexadecimal string
1435    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            // Take the first 10 arguments because an SpFingerprint consists of
1456            // 10 x 4 = 40 characters.
1457            let fp = args.take(10).collect::<Vec<_>>();
1458
1459            // Less than 10 means missing arguments.
1460            if fp.len() < 10 {
1461                return Err(ArgumentError::Missing);
1462            }
1463
1464            // More than 10 should be impossible due to .take(10).
1465            debug_assert_eq!(fp.len(), 10);
1466
1467            // All arguments must be 4 characters long.
1468            if fp.iter().any(|arg| arg.len() != 4) {
1469                return Err(ArgumentError::Invalid);
1470            }
1471
1472            // Convert it to a string without spaces, RsaIdentity::from_hex will
1473            // verify the rest.
1474            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
1538/// A type for relay nicknames
1539mod nickname {
1540    use super::*;
1541    use tinystr::TinyAsciiStr;
1542
1543    /// This is a strange limit, but it comes from Tor.
1544    const MAX_NICKNAME_LEN: usize = 19;
1545
1546    /// The nickname for a Tor relay.
1547    ///
1548    /// These nicknames are legacy mechanism that's occasionally useful in
1549    /// debugging. They should *never* be used to uniquely identify relays;
1550    /// nothing prevents two relays from having the same nickname.
1551    ///
1552    /// Nicknames are required to be ASCII, alphanumeric, and between 1 and 19
1553    /// characters inclusive.
1554    #[derive(Clone, Debug, PartialEq, Eq)]
1555    pub struct Nickname(tinystr::TinyAsciiStr<MAX_NICKNAME_LEN>);
1556
1557    /// Invalid nickname
1558    #[derive(Clone, Debug, thiserror::Error)]
1559    #[error("invalid nickname")]
1560    #[non_exhaustive]
1561    pub struct InvalidNickname {}
1562
1563    impl Nickname {
1564        /// Return a view of this nickname as a string slice.
1565        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
1593/// Hostnames etc.
1594//
1595// TODO maybe move all this to tor-basic-utils
1596mod hostname {
1597    use super::*;
1598    use std::net::IpAddr;
1599
1600    /// Internet hostname
1601    ///
1602    /// Valid according to Internet RFC1123,
1603    /// with the additional restriction that there must be at least one letter.
1604    /// (That means that anything accepted as a `Hostname`
1605    /// won't be accepted as an address even by very relaxed IPv4 address parsers.
1606    /// We presume that no TLD will ever exist that is entirely decimal digits.)
1607    ///
1608    /// Preserves case.
1609    ///
1610    /// Reserved hostname such as `example.come`, `tor.invalid` and `localhost`
1611    /// are accepted.
1612    ///
1613    /// # Comparisons; `PartialEq`, `Eq`
1614    ///
1615    /// The `PartialEq` and `Eq` implementations are case sensitive,
1616    /// even though internet hostnames are not case-sensitive.
1617    ///
1618    /// Comparing hostnames for identical apparent meaning is complicated.
1619    /// If you need to do that, you (may) need to engage with punycode (IDN),
1620    /// as well as arranging for a case-insensitive comparison.
1621    ///
1622    /// And of course, hostnames reference to the DNS.
1623    /// A single host may have multiple names and it may change its address.
1624    #[derive(Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] //
1625    #[derive(derive_more::Into, derive_more::Deref, derive_more::AsRef, derive_more::Display)]
1626    pub struct Hostname(String);
1627
1628    /// Hostname, or IP address (v4 or v6)
1629    ///
1630    /// Preserves hostname case.  See [`Hostname`].
1631    ///
1632    /// Reserved hostnames and addresses (eg `0.0.0.0` or `tor.invalid`) are accepted.
1633    ///
1634    /// IPv6 addresses are represented *without* surrounding `[ ]`.
1635    ///
1636    /// Therefore, you cannot make this into a host-and-port by appending `:port`.
1637    /// To process name-and-port is complex.  `SRV` (or `MX`) records might be involved.
1638    //
1639    // This type is called `InternetHost` to emphasise that it is primarily for
1640    // hosts on the public internet and, unlike arti-client's `Host`,
1641    // has special handling of `.onion` addresses.
1642    #[derive(Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] //
1643    #[derive(derive_more::Display)]
1644    #[allow(clippy::exhaustive_enums)]
1645    // TODO derive .as_hostname(), .as_ip_addr(), From<Hostname>, From<IpAddr>
1646    pub enum InternetHost {
1647        /// Hostname
1648        #[display("{_0}")]
1649        Name(Hostname),
1650        /// IP address (v4 or v6)
1651        #[display("{_0}")]
1652        IpAddr(IpAddr),
1653    }
1654
1655    /// Invalid hostname
1656    #[derive(Clone, Debug, thiserror::Error)]
1657    #[error("invalid hostname")]
1658    #[non_exhaustive]
1659    pub struct InvalidHostname {}
1660
1661    /// Invalid Internet hostname/address
1662    #[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        /// Obtain this hostname as a `str`
1669        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                // Reject hostnames that consist only of decimal digits and full stops.
1685                // Some of those are accepted by some old IPv4 address parsers.
1686                // If any fool makes a TLD that is only digits, they deserve everything they get.
1687                !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                // For simplicity, we  discard the errors from parsing the options
1712                // rather than trying to reproduce them.  Why something isn't a valid
1713                // address or hostname ought to be fairly obvious.
1714                Err(InvalidInternetHost {})
1715            }
1716        }
1717    }
1718
1719    impl NormalItemArgument for Hostname {}
1720    impl NormalItemArgument for InternetHost {}
1721}
1722
1723/// Contact information of the relay operator.
1724mod contact_info {
1725    use super::*;
1726
1727    /// `contact` item: contact information (eg of a relay dirauth operator)
1728    ///
1729    /// <https://spec.torproject.org/dir-spec/server-descriptor-format.html#item:contact>
1730    ///
1731    /// Also used for authority entries in netstatus documents.
1732    #[derive(Clone, Debug, PartialEq, Eq, Deftly)] //
1733    #[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    /// Contact information (`contact` item value) has invalid syntax
1739    #[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            // TODO torspec#396 we should probably impose more restrictions
1749            // For now we forbid `\n` and initial whitespace, which is enough to ensure
1750            // that all values will roundtrip unchanged through netdoc encoding and parsing.
1751            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
1770/// Types for boolean-like types.
1771mod 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    /// A boolean that is represented by a `0` (false) or `1` (true).
1779    // TODO DIRMIRROR: Derive Transparent
1780    #[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
1807/// Types for router descriptors.
1808pub mod routerdesc {
1809    use super::*;
1810    use parse2::ErrorProblem as EP;
1811    use tor_llcrypto::pk::ed25519;
1812
1813    /// Version argument found in an `overload-general` item.
1814    ///
1815    /// <https://spec.torproject.org/dir-spec/server-descriptor-format.html#item:overload-general>
1816    #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, strum::EnumString, strum::Display)]
1817    #[non_exhaustive]
1818    pub enum OverloadGeneralVersion {
1819        /// Version 1, currently the only supported and specified one.
1820        #[strum(serialize = "1")]
1821        V1,
1822    }
1823
1824    impl NormalItemArgument for OverloadGeneralVersion {}
1825
1826    /// The overload general type found in router descriptors.
1827    ///
1828    /// <https://spec.torproject.org/dir-spec/server-descriptor-format.html#item:overload-general>
1829    #[derive(Debug, Clone, Copy, PartialEq, Eq, Deftly)]
1830    #[derive_deftly(ItemValueParseable)]
1831    #[non_exhaustive]
1832    pub struct OverloadGeneral {
1833        /// The version of the item.
1834        pub version: OverloadGeneralVersion,
1835        /// The timestamp since when the relay is overloaded.
1836        pub since: Iso8601TimeSp,
1837    }
1838
1839    /// Introduction line of a router descriptor.
1840    ///
1841    /// <https://spec.torproject.org/dir-spec/server-descriptor-format.html#item:router>
1842    #[derive(Clone, Debug, PartialEq, Eq, Deftly)]
1843    #[derive_deftly(ItemValueParseable)]
1844    #[non_exhaustive]
1845    pub struct RouterDescIntroItem {
1846        /// A valid router [`Nickname`].
1847        pub nickname: Nickname,
1848
1849        /// An IPv4 address in dotted-squad format.
1850        pub address: std::net::Ipv4Addr,
1851
1852        /// The TCP port of the onion router.
1853        pub orport: u16,
1854
1855        /// Legacy.
1856        pub socksport: u16,
1857
1858        /// Legacy.
1859        pub dirport: u16,
1860    }
1861
1862    /// Digest identifying the extra-info document.
1863    ///
1864    /// <https://spec.torproject.org/dir-spec/server-descriptor-format.html#item:extra-info-digest>
1865    #[derive(Clone, Debug, PartialEq, Eq, Deftly)]
1866    #[derive_deftly(ItemValueParseable)]
1867    #[non_exhaustive]
1868    pub struct ExtraInfoDigests {
1869        /// Mandatory SHA-1 of the signed data in base 16.
1870        pub sha1: FixedB16U<20>,
1871
1872        /// Optional SHA-256 of the entire extra-info in base 64.
1873        pub sha2: Option<FixedB64<32>>,
1874    }
1875
1876    /// Accumulator for router descriptor hash signatures.
1877    #[derive(Debug, Clone, Default, Deftly)]
1878    #[derive_deftly(AsMutSelf)]
1879    #[allow(clippy::exhaustive_structs)]
1880    pub struct RouterHashAccu {
1881        /// Potentially the SHA-1 for the signature.
1882        pub sha1: Option<[u8; 20]>,
1883        /// Potentially the SHA-256 for the signature.
1884        pub sha256: Option<[u8; 32]>,
1885    }
1886
1887    /// SHA-256 router descriptor signature including magic and the keyword.
1888    #[derive(Debug, Clone, PartialEq, Eq, Deftly)]
1889    #[derive_deftly(ItemValueEncodable)]
1890    #[allow(clippy::exhaustive_structs)]
1891    // TODO SPEC is RouterSigEd25519 not a standard-ish kind of signature?
1892    // TODO DIRAUTH is RouterSigEd25519 not a standard-ish kind of signature?
1893    pub struct RouterSigEd25519(pub ed25519::Signature);
1894
1895    impl RouterSigEd25519 {
1896        /// The magic prefix for hashing this type of signature.
1897        const HASH_PREFIX_MAGIC: &str = "Tor router descriptor signature v1";
1898
1899        /// Calculate the hash for signature
1900        ///
1901        /// `signature_item_kw_spc` is the keyword *with a trailing space*.
1902        /// It's `&[&str]` for the convenience of the two call sites.
1903        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        /// Make a signature during document encoding
1920        ///
1921        /// `item_keyword` is the keyword for the signature item.
1922        ///
1923        /// # Example
1924        ///
1925        /// ```
1926        /// use derive_deftly::Deftly;
1927        /// use tor_error::Bug;
1928        /// use tor_llcrypto::pk::ed25519;
1929        /// use tor_netdoc::derive_deftly_template_NetdocEncodable;
1930        /// use tor_netdoc::encode::{NetdocEncodable, NetdocEncoder};
1931        /// use tor_netdoc::types::routerdesc::RouterSigEd25519;
1932        ///
1933        /// #[derive(Deftly, Default)]
1934        /// #[derive_deftly(NetdocEncodable)]
1935        /// pub struct Document {
1936        ///     pub document_intro_keyword: (),
1937        /// }
1938        /// #[derive(Deftly)]
1939        /// #[derive_deftly(NetdocEncodable)]
1940        /// pub struct DocumentSignatures {
1941        ///     pub document_signature: RouterSigEd25519,
1942        /// }
1943        /// impl Document {
1944        ///     pub fn encode_sign(&self, k: &ed25519::Keypair) -> Result<String, Bug> {
1945        ///         let mut encoder = NetdocEncoder::new();
1946        ///         self.encode_unsigned(&mut encoder)?;
1947        ///         let document_signature =
1948        ///             RouterSigEd25519::new_sign_netdoc(k, &encoder, "document-signature")?;
1949        ///         let sigs = DocumentSignatures { document_signature };
1950        ///         sigs.encode_unsigned(&mut encoder)?;
1951        ///         let encoded = encoder.finish()?;
1952        ///         Ok(encoded)
1953        ///     }
1954        /// }
1955        ///
1956        /// # fn main() -> Result<(), anyhow::Error> {
1957        /// let k = ed25519::Keypair::generate(&mut tor_basic_utils::test_rng::testing_rng());
1958        /// let doc = Document::default();
1959        /// let encoded = doc.encode_sign(&k)?;
1960        /// assert!(encoded.starts_with(concat!(
1961        ///     "document-intro-keyword\n",
1962        ///     "document-signature ",
1963        /// )));
1964        /// # Ok(())
1965        /// # }
1966        /// ```
1967        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            // TODO DIRMIRROR break this out into impl ItemArgumentParseable for Signature
1989            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    /// SHA-1 router descriptor signature over `router-sig-ed25519`.
2003    // TODO DIRMIRROR Is this not the same as RsaSha1Signature ?
2004    #[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            // There must be no additonal arguments.
2017            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    // @@ begin test lint list maintained by maint/add_warning @@
2039    #![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    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
2051    use itertools::Itertools;
2052
2053    use base64ct::Encoding;
2054
2055    use super::*;
2056    use crate::{Pos, Result};
2057
2058    /// Decode s as a multi-line base64 string, ignoring ascii whitespace.
2059    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        // Test parsing success:
2068        // Unpadded:
2069        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        // Padded:
2092        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        // Test parsing failures:
2098        // Invalid character.
2099        assert!("Mi43!!!!!!".parse::<B64>().is_err());
2100        // Invalid last character.
2101        assert!("Mi".parse::<B64>().is_err());
2102        assert!(
2103            "ppwthHXW8kXD0f9fE7UPYsOAAu4uj5ORwSomCMxaaaa"
2104                .parse::<B64>()
2105                .is_err()
2106        );
2107        // Invalid length.
2108        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        // Check that strings that we accept are precisely ones which
2137        // can be generated by either Base64 or Base64Unpadded
2138        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        // right length, bad key:
2252        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        // Taken from a chutney network.
2301        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        // tests on a valid key
2319        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        // A string of bytes that is not an RSA key.
2327        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        // From a chutney network.
2336        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        // From the cert above.
2343        let right_subject_key: Ed25519Identity = "HqbeSrmHyLfDBpz5OLb3eimg4/xEoY/0xPRYPgFvPtg"
2344            .parse::<Ed25519Public>()
2345            .unwrap()
2346            .into();
2347        // From `ed25519()` test above.
2348        let wrong_subject_key: Ed25519Identity = "WVIPQ8oArAqLY4XzkcpIOI6U8KsUJHBQhG8SC57qru0"
2349            .parse::<Ed25519Public>()
2350            .unwrap()
2351            .into();
2352
2353        // decode and check correct type and key
2354        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        // check wrong type.
2361        assert!(
2362            cert.clone()
2363                .check_cert_type(tor_cert::CertType::RSA_ID_X509)
2364                .is_err()
2365        );
2366        // check wrong key.
2367        assert!(cert.check_subject_key_is(&wrong_subject_key).is_err());
2368
2369        // Try an invalid object that isn't a certificate.
2370        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 for both `Hostname` and `InternetHost`
2436    #[test]
2437    fn hostname() {
2438        use std::net::IpAddr;
2439
2440        // Test a string that we should treat as a valid hostname.
2441        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        // Test a string that looks like it could be an address or a hostname.
2449        // We parse those as addresses.
2450        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        // Test a string that's an address, and isn't a valid hostname.
2458        let chk_addr = |s: &str| {
2459            let _: InvalidHostname = s.parse::<Hostname>().expect_err(s);
2460            chk_either(s);
2461        };
2462
2463        // Test a string that we should reject.
2464        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        // Unarguably invalid.
2475        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        // Strings that some IP address parsers accept as addresses,
2483        // but which are also valid hostnames according to RFC1123.
2484        //
2485        // We reject them rather than processing of them as hostnames,
2486        // exposing downstream software to possible strangeness.
2487        chk_bad("1");
2488        chk_bad("127.0.0.023");
2489
2490        // No-one thinks this is a valid IP address but we reject it as a hostname too,
2491        // even though it's syntactically legal per RFC1123, because it's quite bad.
2492        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"); // IPv6-mapped IPv4 address
2500    }
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")?; // TODO torspec#396
2542
2543        Ok(())
2544    }
2545
2546    /// Round trip test for [`NumericBoolean`] ensuring that `0` is false,
2547    /// `1` is true, and other things fail.
2548    #[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        // Testing this because it is not a u8.
2556        assert!(NumericBoolean::from_str("10000").is_err());
2557    }
2558
2559    /// Test that ensures SpFingerprint matches the 10x4 requirement.
2560    #[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        /// Small helper to parse an [`SpFingerprint`].
2575        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        // Test a valid one.
2585        assert_eq!(
2586            parse2(&vec!["ABAB"; 10].join(" ")).unwrap(),
2587            SpFingerprint(RsaIdentity::from_bytes(&[0xAB; 20]).unwrap())
2588        );
2589
2590        // Test a valid one with tail.
2591        assert_eq!(
2592            parse2(&vec!["ABAB"; 11].join(" ")).unwrap(),
2593            SpFingerprint(RsaIdentity::from_bytes(&[0xAB; 20]).unwrap())
2594        );
2595
2596        // Missing argument
2597        assert!(matches!(
2598            parse2(&vec!["ABAB"; 9].join(" ")).unwrap_err(),
2599            ErrorProblem::MissingArgument { .. }
2600        ));
2601
2602        // Invalid argument.
2603        // In this case, we have string with a total length of 40 but with
2604        // one pair having 6 characters and another one having 2 as a proof
2605        // of that.
2606        assert!(matches!(
2607            parse2("0000 000000 00 0000 0000 0000 0000 0000 0000 0000").unwrap_err(),
2608            ErrorProblem::InvalidArgument { .. }
2609        ));
2610
2611        // And of course invalid hex should fail too.
2612        assert!(matches!(
2613            parse2(&vec!["ZZZZ"; 10].join(" ")).unwrap_err(),
2614            ErrorProblem::InvalidArgument { .. }
2615        ));
2616    }
2617}