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

            
8
pub use b16impl::*;
9
pub use b64impl::*;
10
pub use contact_info::*;
11
pub use curve25519impl::*;
12
pub use ed25519impl::*;
13
pub(crate) use edcert::*;
14
pub use fingerprint::*;
15
pub use hostname::*;
16
pub use rsa::*;
17
pub use timeimpl::*;
18

            
19
pub use nickname::{InvalidNickname, Nickname};
20

            
21
pub use boolean::NumericBoolean;
22

            
23
pub use fingerprint::{Base64Fingerprint, Fingerprint};
24

            
25
pub use identified_digest::{DigestName, IdentifiedDigest};
26

            
27
pub use ignored_impl::{Ignored, IgnoredItemOrObjectValue, NotPresent};
28

            
29
use crate::NormalItemArgument;
30
use 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
};
40
use 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

            
52
use derive_deftly::{Deftly, define_derive_deftly, define_derive_deftly_module};
53
use digest::Digest as _;
54
use educe::Educe;
55
use std::cmp::{self, Ordering, PartialOrd};
56
use std::fmt::{self, Display};
57
use std::iter;
58
use std::marker::PhantomData;
59
use std::ops::{Deref, DerefMut};
60
use std::result::Result as StdResult;
61
use std::str::FromStr;
62
use subtle::{Choice, ConstantTimeEq};
63
use tor_error::{Bug, ErrorReport as _, internal, into_internal};
64
use 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.
69
pub(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
5398
    fn from_vec(v: Vec<u8>, p: crate::Pos) -> crate::Result<Self> {
75
5398
        Self::from_bytes(&v[..], p)
76
5398
    }
77
}
78

            
79
define_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
532826
        fn from($fpatname: $ftype) -> $ttype {
102
            $vpat
103
        }
104
    }
105

            
106
    impl<$tgens> From<$ttype> for $ftype {
107
18192
        fn from(self_: $ttype) -> $ftype {
108
            self_.$fname
109
        }
110
    }
111

            
112
    impl<$tgens> Deref for $ttype {
113
        type Target = $ftype;
114
255761192
        fn deref(&self) -> &$ftype {
115
            &self.$fname
116
        }
117
    }
118

            
119
    impl<$tgens> DerefMut for $ttype {
120
2
        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

            
140
define_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

            
169
define_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`, `Ord`, `PartialOrd`
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
    impl<$tgens> PartialOrd for $ttype {
215
        fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
216
            Some(self.cmp(other))
217
        }
218
    }
219
    impl<$tgens> Ord for $ttype {
220
        fn cmp(&self, other: &Self) -> Ordering {
221
          $(
222
            self.$fname.cmp(&other.$fname)
223
          )
224
        }
225
    }
226

            
227
    impl<$tgens> $ttype {
228
        /// Return the byte array from this object.
229
13494
        pub fn as_bytes(&self) -> &[u8] {
230
          $(
231
13494
            &self.$fname[..]
232
          )
233
13494
        }
234
    }
235

            
236
    impl<$tgens> AsRef<[u8]> for $ttype {
237
        fn as_ref(&self) -> &[u8] {
238
          $(
239
            self.$fname.as_ref()
240
          )
241
        }
242
    }
243

            
244
    impl<$tgens> AsMut<[u8]> for $ttype {
245
        fn as_mut(&mut self) -> &mut [u8] {
246
          $(
247
            self.$fname.as_mut()
248
          )
249
        }
250
    }
251
}
252

            
253
/// Types for decoding base64-encoded values.
254
mod b64impl {
255
    use super::*;
256
    use crate::{Error, NetdocErrorKind as EK, Pos, Result};
257
    use base64ct::{Base64, Base64Unpadded, Encoding};
258
    use std::ops::RangeBounds;
259

            
260
    /// A byte array, encoded in base64 with optional padding.
261
    ///
262
    /// On output (`Display`), output is unpadded.
263
    #[derive(Clone, Hash, Deftly)]
264
    #[derive_deftly(BytesTransparent)]
265
    #[allow(clippy::derived_hash_with_manual_eq)]
266
    #[derive(derive_more::Debug)]
267
    #[debug(r#"B64("{self}")"#)]
268
    #[allow(clippy::exhaustive_structs)]
269
    pub struct B64(pub Vec<u8>);
270

            
271
    impl FromStr for B64 {
272
        type Err = Error;
273
94640
        fn from_str(s: &str) -> Result<Self> {
274
94640
            let v: core::result::Result<Vec<u8>, base64ct::Error> = match s.len() % 4 {
275
14566
                0 => Base64::decode_vec(s),
276
80074
                _ => Base64Unpadded::decode_vec(s),
277
            };
278
112647
            let v = v.map_err(|_| {
279
36014
                EK::BadArgument
280
36014
                    .with_msg("Invalid base64")
281
36014
                    .at_pos(Pos::at(s))
282
54021
            })?;
283
58626
            Ok(B64(v))
284
94640
        }
285
    }
286

            
287
    impl Display for B64 {
288
30
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
289
30
            Display::fmt(&Base64Unpadded::encode_string(&self.0), f)
290
30
        }
291
    }
292

            
293
    impl B64 {
294
        /// Return this object if its length is within the provided bounds
295
        /// object, or an error otherwise.
296
2124
        pub(crate) fn check_len<B: RangeBounds<usize>>(self, bounds: B) -> Result<Self> {
297
2124
            if bounds.contains(&self.0.len()) {
298
2122
                Ok(self)
299
            } else {
300
2
                Err(EK::BadObjectVal.with_msg("Invalid length on base64 data"))
301
            }
302
2124
        }
303

            
304
        /// Try to convert this object into an array of N bytes.
305
        ///
306
        /// Return an error if the length is wrong.
307
42862
        pub(crate) fn into_array<const N: usize>(self) -> Result<[u8; N]> {
308
42862
            self.0
309
42862
                .try_into()
310
42862
                .map_err(|_| EK::BadObjectVal.with_msg("Invalid length on base64 data"))
311
42862
        }
312
    }
313

            
314
    impl FromIterator<u8> for B64 {
315
        fn from_iter<T: IntoIterator<Item = u8>>(iter: T) -> Self {
316
            Self(iter.into_iter().collect())
317
        }
318
    }
319

            
320
    impl NormalItemArgument for B64 {}
321

            
322
    /// A byte array encoded in a hexadecimal with a fixed length.
323
    #[derive(Clone, Hash, Deftly)]
324
    #[derive_deftly(BytesTransparent)]
325
    #[allow(clippy::derived_hash_with_manual_eq)]
326
    #[derive(derive_more::Debug)]
327
    #[debug(r#"FixedB64::<{N}>("{self}")"#)]
328
    #[allow(clippy::exhaustive_structs)]
329
    pub struct FixedB64<const N: usize>(pub [u8; N]);
330

            
331
    impl<const N: usize> Display for FixedB64<N> {
332
10
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
333
10
            Display::fmt(&B64(self.0.to_vec()), f)
334
10
        }
335
    }
336

            
337
    impl<const N: usize> FromStr for FixedB64<N> {
338
        type Err = Error;
339
5552
        fn from_str(s: &str) -> Result<Self> {
340
5552
            Ok(Self(B64::from_str(s)?.0.try_into().map_err(|_| {
341
2
                EK::BadArgument
342
2
                    .at_pos(Pos::at(s))
343
2
                    .with_msg("invalid length")
344
2
            })?))
345
5552
        }
346
    }
347

            
348
    impl<const N: usize> NormalItemArgument for FixedB64<N> {}
349
}
350

            
351
// ============================================================
352

            
353
/// Types for decoding hex-encoded values.
354
mod b16impl {
355
    use super::*;
356
    use crate::{Error, NetdocErrorKind as EK, Pos, Result};
357

            
358
    /// A byte array encoded in hexadecimal; prints in lowercase
359
    ///
360
    /// Both uppercase and lowercase are tolerated when parsing.
361
    #[derive(Clone, Hash, Deftly)]
362
    #[derive_deftly(BytesTransparent)]
363
    #[allow(clippy::derived_hash_with_manual_eq)]
364
    #[derive(derive_more::Debug)]
365
    #[debug(r#"B16("{self}")"#)]
366
    #[allow(clippy::exhaustive_structs)]
367
    pub struct B16(pub Vec<u8>);
368

            
369
    /// A byte array encoded in hexadecimal; prints in uppercase
370
    ///
371
    /// Both uppercase and lowercase are tolerated when parsing.
372
    #[derive(Clone, Hash, Deftly)]
373
    #[derive_deftly(BytesTransparent)]
374
    #[allow(clippy::derived_hash_with_manual_eq)]
375
    #[derive(derive_more::Debug)]
376
    #[debug(r#"B16U("{self}")"#)]
377
    #[allow(clippy::exhaustive_structs)]
378
    pub struct B16U(pub Vec<u8>);
379

            
380
    /// A fixed-length version of [`B16U`].
381
    #[derive(Clone, Hash, Deftly)]
382
    #[derive_deftly(BytesTransparent)]
383
    #[allow(clippy::derived_hash_with_manual_eq)]
384
    #[derive(derive_more::Debug)]
385
    #[debug(r#"FixedB16U("{self}")"#)]
386
    #[allow(clippy::exhaustive_structs)]
387
    pub struct FixedB16U<const N: usize>(pub [u8; N]);
388

            
389
    impl FromStr for B16 {
390
        type Err = Error;
391
3154
        fn from_str(s: &str) -> Result<Self> {
392
3156
            let bytes = hex::decode(s).map_err(|_| {
393
4
                EK::BadArgument
394
4
                    .at_pos(Pos::at(s))
395
4
                    .with_msg("invalid hexadecimal")
396
6
            })?;
397
3150
            Ok(B16(bytes))
398
3154
        }
399
    }
400

            
401
    impl FromStr for B16U {
402
        type Err = Error;
403
3144
        fn from_str(s: &str) -> Result<Self> {
404
3144
            Ok(B16U(B16::from_str(s)?.0))
405
3144
        }
406
    }
407

            
408
    impl<const N: usize> FromStr for FixedB16U<N> {
409
        type Err = Error;
410
        fn from_str(s: &str) -> Result<Self> {
411
            Ok(Self(B16U::from_str(s)?.0.try_into().map_err(|_| {
412
                EK::BadArgument
413
                    .at_pos(Pos::at(s))
414
                    .with_msg("invalid length")
415
            })?))
416
        }
417
    }
418

            
419
    /// Write `b` to `f` in hex uppercase
420
    // `hex` has `hex::encode_upper` but that allocates a `String`
421
22
    fn write_b16u(b: &[u8], f: &mut fmt::Formatter) -> fmt::Result {
422
348
        for c in b {
423
348
            write!(f, "{c:02X}")?;
424
        }
425
22
        Ok(())
426
22
    }
427

            
428
    impl Display for B16 {
429
6
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
430
            // `hex` has `hex::encode` but that allocates a `String`, which this approach doesn't
431
28
            for c in self.as_bytes() {
432
28
                write!(f, "{c:02x}")?;
433
            }
434
6
            Ok(())
435
6
        }
436
    }
437

            
438
    impl Display for B16U {
439
22
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
440
22
            write_b16u(self.as_bytes(), f)
441
22
        }
442
    }
443

            
444
    impl<const N: usize> Display for FixedB16U<N> {
445
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
446
            write_b16u(self.as_bytes(), f)
447
        }
448
    }
449

            
450
    impl NormalItemArgument for B16 {}
451
    impl NormalItemArgument for B16U {}
452
    impl<const N: usize> NormalItemArgument for FixedB16U<N> {}
453
}
454

            
455
// ============================================================
456

            
457
/// Types for decoding curve25519 keys
458
mod curve25519impl {
459
    use super::*;
460

            
461
    use crate::{Error, NormalItemArgument, Result, types::misc::FixedB64};
462
    use tor_llcrypto::pk::curve25519::PublicKey;
463

            
464
    /// A Curve25519 public key, encoded in base64 with optional padding
465
    #[derive(Debug, Clone, PartialEq, Eq, Deftly)]
466
    #[derive_deftly(Transparent)]
467
    #[allow(clippy::exhaustive_structs)]
468
    pub struct Curve25519Public(pub PublicKey);
469

            
470
    impl FromStr for Curve25519Public {
471
        type Err = Error;
472
2770
        fn from_str(s: &str) -> Result<Self> {
473
2770
            let pk: FixedB64<32> = s.parse()?;
474
2760
            let pk: [u8; 32] = pk.into();
475
2760
            Ok(Curve25519Public(pk.into()))
476
2770
        }
477
    }
478

            
479
    impl Display for Curve25519Public {
480
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
481
            FixedB64::from(self.0.to_bytes()).fmt(f)
482
        }
483
    }
484

            
485
    impl NormalItemArgument for Curve25519Public {}
486
}
487

            
488
// ============================================================
489

            
490
/// Types for decoding ed25519 keys
491
mod ed25519impl {
492
    use super::*;
493

            
494
    use crate::{Error, NormalItemArgument, Result, types::misc::FixedB64};
495
    use derive_deftly::Deftly;
496
    use tor_llcrypto::pk::ed25519::{Ed25519Identity, Signature};
497

            
498
    /// An alleged ed25519 public key, encoded in base64 with optional
499
    /// padding.
500
    #[derive(Debug, Clone, PartialEq, Eq)]
501
    #[allow(clippy::exhaustive_structs)]
502
    pub struct Ed25519Public(pub Ed25519Identity);
503

            
504
    impl FromStr for Ed25519Public {
505
        type Err = Error;
506
2768
        fn from_str(s: &str) -> Result<Self> {
507
2768
            let pk: FixedB64<32> = s.parse()?;
508
2762
            Ok(Ed25519Public(Ed25519Identity::new(pk.into())))
509
2768
        }
510
    }
511

            
512
    impl Display for Ed25519Public {
513
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
514
            let pk: [u8; 32] = self.0.into();
515
            let pk = FixedB64::from(pk);
516
            pk.fmt(f)
517
        }
518
    }
519

            
520
    impl NormalItemArgument for Ed25519Public {}
521

            
522
    impl From<Ed25519Public> for Ed25519Identity {
523
2296
        fn from(pk: Ed25519Public) -> Ed25519Identity {
524
2296
            pk.0
525
2296
        }
526
    }
527

            
528
    /// Helper that checks for the presence of `ed25519`.
529
    #[derive(Debug, Clone, PartialEq, Eq, derive_more::Display, derive_more::FromStr)]
530
    #[display(rename_all = "lowercase")]
531
    #[from_str(rename_all = "lowercase")]
532
    #[allow(clippy::exhaustive_enums)]
533
    pub enum Ed25519AlgorithmString {
534
        /// Ed25519 encoded as `ed25519`.
535
        Ed25519,
536
    }
537

            
538
    impl NormalItemArgument for Ed25519AlgorithmString {}
539

            
540
    /// An Ed25519 public key found in a micro descriptor `id` line.
541
    #[derive(Debug, Clone, PartialEq, Eq, Deftly)]
542
    #[derive_deftly(ItemValueParseable)]
543
    #[non_exhaustive]
544
    pub struct Ed25519IdentityLine {
545
        /// Fixed magic identifier (`ed25519`) for this line.
546
        pub alg: Ed25519AlgorithmString,
547

            
548
        /// The actual Ed25519 identity.
549
        pub pk: Ed25519Public,
550
    }
551

            
552
    impl From<Ed25519Public> for Ed25519IdentityLine {
553
488166
        fn from(pk: Ed25519Public) -> Self {
554
488166
            Self {
555
488166
                alg: Ed25519AlgorithmString::Ed25519,
556
488166
                pk,
557
488166
            }
558
488166
        }
559
    }
560

            
561
    impl From<Ed25519Identity> for Ed25519IdentityLine {
562
488166
        fn from(pk: Ed25519Identity) -> Self {
563
488166
            Ed25519Public(pk).into()
564
488166
        }
565
    }
566

            
567
    impl ItemArgument for Signature {
568
2
        fn write_arg_onto(&self, out: &mut ItemEncoder) -> StdResult<(), Bug> {
569
2
            FixedB64::from(self.to_bytes()).write_arg_onto(out)
570
2
        }
571
    }
572
}
573

            
574
// ============================================================
575

            
576
/// Dummy types like [`Ignored`]
577
mod ignored_impl {
578
    use super::*;
579

            
580
    use crate::parse2::ErrorProblem as EP;
581

            
582
    /// Part of a network document, that isn't actually there.
583
    ///
584
    /// Used as a standin in `ns_type!` calls in various netstatus `each_variety.rs`.
585
    /// The effect is as if the field were omitted from the containing type.
586
    ///
587
    ///  * When used as item(s) (ie, a field type when deriving `NetdocParseable\[Fields\]`):
588
    ///    **ignores any number** of items with that field's keyword during parsing,
589
    ///    and emits none during encoding.
590
    ///
591
    ///    (To *reject* documents containing this item, use `Option<Void>`,
592
    ///    but note that the spec says unknown items should be ignored,
593
    ///    which would normally include items which are merely missing from one variety.)
594
    ///
595
    ///  * When used as an argument (ie, a field type when deriving `ItemValueParseable`,
596
    ///    or with `netdoc(single_arg)`  when deriving `NetdocParseable\[Fields\]`):
597
    ///    consumes **no arguments** during parsing, and emits none during encoding.
598
    ///
599
    ///  * When used as an object field (ie, `netdoc(object)` when deriving `ItemValueParseable`):
600
    ///    **rejects** an object - failing the parse if one is present.
601
    ///    (Functions similarly to `Option<Void>`, but prefer `NotPresent` as it's clearer.)
602
    ///
603
    ///  * When used as a sub-document (ie, `netdoc(flatten)` when deriving a document trait),
604
    ///    it recognises, and encodes as, no fields.
605
    ///
606
    /// There are bespoke impls of the multiplicity traits
607
    /// `ItemSetMethods` and `ObjectSetMethods`:
608
    /// don't wrap this type in `Option` or `Vec`.
609
    //
610
    // TODO we'll need to implement ItemArgument etc., for encoding, too.
611
    #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Default)]
612
    #[allow(clippy::exhaustive_structs)]
613
    #[derive(Deftly)]
614
    #[derive_deftly(NetdocEncodableFields, NetdocParseableFields)]
615
    pub struct NotPresent;
616

            
617
    /// Ignored part of a network document.
618
    ///
619
    /// With `parse2`, can be used as an item, object, or even flattened-fields.
620
    ///
621
    /// When deriving `parse2` traits, and a field is absent in a particular netstatus variety,
622
    /// use `ns_type!` with [`NotPresent`], rather than `Ignored`.
623
    ///
624
    /// During encoding as an Items or Objects, will be entirely omitted,
625
    /// via the multiplicity arrangements.
626
    ///
627
    /// Cannot be encoded as an Argument: if this is not the last
628
    /// Argument, we need something to put into the output document to avoid generating
629
    /// a document with the arguments out of step.  If it *is* the last argument,
630
    /// it could simply be omitted, since additional arguments are in any case ignored.
631
    #[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, Ord, PartialOrd, Default, Deftly)]
632
    #[derive_deftly(ItemValueParseable, NetdocParseableFields)]
633
    #[allow(clippy::exhaustive_structs)]
634
    pub struct Ignored;
635

            
636
    /// An Item or Object that would be ignored during parsing and is omitted during encoding
637
    ///
638
    /// This is the "single" item type for encoding multiplicity for Items or Objects,
639
    /// for [`Ignored`].
640
    ///
641
    /// This type is uninhabited.
642
    pub struct IgnoredItemOrObjectValue(Void);
643

            
644
    impl ItemSetMethods for P2MultiplicitySelector<NotPresent> {
645
        type Each = Ignored;
646
        type Field = NotPresent;
647
        fn can_accumulate(self, _acc: &Option<NotPresent>) -> Result<(), EP> {
648
            Ok(())
649
        }
650
12
        fn accumulate(self, _acc: &mut Option<NotPresent>, _item: Ignored) -> Result<(), EP> {
651
12
            Ok(())
652
12
        }
653
466
        fn finish(self, _acc: Option<NotPresent>, _: &'static str) -> Result<NotPresent, EP> {
654
466
            Ok(NotPresent)
655
466
        }
656
        fn debug_core(self) -> &'static str {
657
            "Ignored"
658
        }
659
    }
660

            
661
    impl ItemArgumentParseable for NotPresent {
662
14
        fn from_args(_: &mut ArgumentStream) -> Result<NotPresent, ArgumentError> {
663
14
            Ok(NotPresent)
664
14
        }
665
    }
666

            
667
    impl ObjectSetMethods for P2MultiplicitySelector<NotPresent> {
668
        type Field = NotPresent;
669
        type Each = Void;
670
6
        fn resolve_option(self, _found: Option<Void>) -> Result<NotPresent, EP> {
671
6
            Ok(NotPresent)
672
6
        }
673
        fn debug_core(self) -> &'static str {
674
            "NotPresent"
675
        }
676
    }
677

            
678
    impl<'f> encode::MultiplicityMethods<'f> for EMultiplicitySelector<NotPresent> {
679
        type Field = NotPresent;
680
        type Each = Void;
681
6
        fn iter_ordered(self, _: &'f Self::Field) -> impl Iterator<Item = &'f Self::Each> {
682
6
            iter::empty()
683
6
        }
684
    }
685

            
686
    impl encode::OptionalityMethods for EMultiplicitySelector<NotPresent> {
687
        type Field = NotPresent;
688
        type Each = Void;
689
2
        fn as_option<'f>(self, _: &'f Self::Field) -> Option<&'f Self::Each> {
690
2
            None
691
2
        }
692
    }
693

            
694
    impl FromStr for Ignored {
695
        type Err = Void;
696
        fn from_str(_s: &str) -> Result<Ignored, Void> {
697
            Ok(Ignored)
698
        }
699
    }
700

            
701
    impl ItemArgumentParseable for Ignored {
702
        fn from_args(_: &mut ArgumentStream) -> Result<Ignored, ArgumentError> {
703
            Ok(Ignored)
704
        }
705
    }
706

            
707
    impl ItemObjectParseable for Ignored {
708
6
        fn check_label(_label: &str) -> Result<(), EP> {
709
            // allow any label
710
6
            Ok(())
711
6
        }
712
6
        fn from_bytes(_input: &[u8]) -> Result<Self, EP> {
713
6
            Ok(Ignored)
714
6
        }
715
    }
716

            
717
    impl ObjectSetMethods for P2MultiplicitySelector<Ignored> {
718
        type Field = Ignored;
719
        type Each = Ignored;
720
6
        fn resolve_option(self, _found: Option<Ignored>) -> Result<Ignored, EP> {
721
6
            Ok(Ignored)
722
6
        }
723
        fn debug_core(self) -> &'static str {
724
            "Ignored"
725
        }
726
    }
727

            
728
    impl<'f> encode::MultiplicityMethods<'f> for EMultiplicitySelector<Ignored> {
729
        type Field = Ignored;
730
        type Each = IgnoredItemOrObjectValue;
731
        fn iter_ordered(self, _: &'f Self::Field) -> impl Iterator<Item = &'f Self::Each> {
732
            iter::empty()
733
        }
734
    }
735

            
736
    impl encode::OptionalityMethods for EMultiplicitySelector<Ignored> {
737
        type Field = Ignored;
738
        type Each = IgnoredItemOrObjectValue;
739
2
        fn as_option<'f>(self, _: &'f Self::Field) -> Option<&'f Self::Each> {
740
2
            None
741
2
        }
742
    }
743

            
744
    impl ItemValueEncodable for IgnoredItemOrObjectValue {
745
        fn write_item_value_onto(&self, _: ItemEncoder) -> Result<(), Bug> {
746
            void::unreachable(self.0)
747
        }
748
    }
749

            
750
    impl ItemObjectEncodable for IgnoredItemOrObjectValue {
751
        fn label(&self) -> &str {
752
            void::unreachable(self.0)
753
        }
754
        fn write_object_onto(&self, _: &mut Vec<u8>) -> Result<(), Bug> {
755
            void::unreachable(self.0)
756
        }
757
    }
758
}
759

            
760
// ============================================================
761

            
762
/// Information about unknown values, which may have been retained as a `T`
763
///
764
/// Won't grow additional variants - but, `Retained` is only included conditionally.
765
///
766
/// Also used in the form `Unknown<()>` to indicate whether unknown values *should* be retained.
767
///
768
/// ### Example
769
///
770
/// ```
771
/// # {
772
/// #![cfg(feature = "retain-unknown")]
773
///
774
/// use tor_netdoc::types::Unknown;
775
///
776
/// let mut unk: Unknown<Vec<String>> = Unknown::new_retained_default();
777
/// unk.with_mut_unknown(|u| u.push("something-we-found".into()));
778
/// assert_eq!(unk.into_retained().unwrap(), ["something-we-found"]);
779
/// # }
780
/// ```
781
///
782
/// ### Equality comparison, semantics
783
///
784
/// Two `Unknown` are consider equal if both have the same record of unknown values,
785
/// or if neither records unknown values at all.
786
///
787
/// `Unknown` is not `Eq` or `Ord` because we won't want to relate a `Discarded`
788
/// to a `Retained`.  That would be a logic error.  `partial_cmp` gives `None` for this.
789
#[derive(Debug, PartialEq, Clone, Copy, Hash)]
790
#[allow(clippy::exhaustive_enums)] // this isn't going to change
791
pub enum Unknown<T> {
792
    /// The parsing discarded unknown values and they are no longer available.
793
    Discarded(PhantomData<T>),
794

            
795
    /// The document parsing retained (or should retain) unknown values.
796
    #[cfg(feature = "retain-unknown")]
797
    Retained(T),
798
}
799

            
800
impl<T> Unknown<T> {
801
    /// Create an `Unknown` which specifies that values were discarded (or should be)
802
476532
    pub fn new_discard() -> Self {
803
476532
        Unknown::Discarded(PhantomData)
804
476532
    }
805

            
806
    /// Map the `Retained`, if there is one
807
2590
    pub fn map<U>(self, f: impl FnOnce(T) -> U) -> Unknown<U> {
808
2590
        self.try_map(move |t| Ok::<_, Void>(f(t))).void_unwrap()
809
2590
    }
810

            
811
    /// Map the `Retained`, fallibly
812
2590
    pub fn try_map<U, E>(self, f: impl FnOnce(T) -> Result<U, E>) -> Result<Unknown<U>, E> {
813
2590
        Ok(match self {
814
2590
            Unknown::Discarded(_) => Unknown::Discarded(PhantomData),
815
            #[cfg(feature = "retain-unknown")]
816
            Unknown::Retained(t) => Unknown::Retained(f(t)?),
817
        })
818
2590
    }
819

            
820
    /// Obtain an `Unknown` containing (maybe) a reference
821
    pub fn as_ref(&self) -> Unknown<&T> {
822
        match self {
823
            Unknown::Discarded(_) => Unknown::Discarded(PhantomData),
824
            #[cfg(feature = "retain-unknown")]
825
            Unknown::Retained(t) => Unknown::Retained(t),
826
        }
827
    }
828

            
829
    /// Return the retained unknown data, giving `None` if none was saved
830
    ///
831
    /// This is the function for disregarding the possible previously existence
832
    /// of now-discarded unknown (unrecognised) information.
833
    ///
834
    /// Use [`into_retained`](Self::into_retained) if it would be a bug
835
    /// if unrecognised information had been previously discarded.
836
    pub fn only_known(self) -> Option<T> {
837
        match self {
838
            Unknown::Discarded(_) => None,
839
            #[cfg(feature = "retain-unknown")]
840
            Unknown::Retained(t) => Some(t),
841
        }
842
    }
843

            
844
    /// Obtain the `Retained` data
845
    ///
846
    /// Treats lack of retention as an internal error.
847
    #[cfg(feature = "retain-unknown")]
848
    pub fn into_retained(self) -> Result<T, Bug> {
849
        match self {
850
            Unknown::Discarded(_) => Err(internal!("Unknown::retained but data not collected")),
851
            Unknown::Retained(t) => Ok(t),
852
        }
853
    }
854

            
855
    /// Start recording unknown information, with a default value for `T`
856
    #[cfg(feature = "retain-unknown")]
857
    pub fn new_retained_default() -> Self
858
    where
859
        T: Default,
860
    {
861
        Unknown::Retained(T::default())
862
    }
863

            
864
    /// Update the `Retained`, if there is one
865
    ///
866
    /// Intended for use in parsing, when we encounter an unknown value.
867
    ///
868
    /// Not provided in `try_` form.  If you think you need this, instead, unconditionally
869
    /// parse and verify the unknown value, and then conditionally insert it with this function.
870
    /// Don't parse it conditionally - that would skip some validation.
871
2
    pub fn with_mut_unknown(&mut self, f: impl FnOnce(&mut T)) {
872
2
        match self {
873
2
            Unknown::Discarded(_) => {}
874
            #[cfg(feature = "retain-unknown")]
875
            Unknown::Retained(t) => f(t),
876
        }
877
2
    }
878
}
879

            
880
impl<T: PartialOrd> PartialOrd for Unknown<T> {
881
    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
882
        use Unknown::*;
883
        match (self, other) {
884
            (Discarded(_), Discarded(_)) => Some(cmp::Ordering::Equal),
885
            #[cfg(feature = "retain-unknown")]
886
            (Discarded(_), Retained(_)) | (Retained(_), Discarded(_)) => None,
887
            #[cfg(feature = "retain-unknown")]
888
            (Retained(a), Retained(b)) => a.partial_cmp(b),
889
        }
890
    }
891
}
892

            
893
// ============================================================
894

            
895
/// Known keyword (enum) value, or arbitrary string
896
///
897
/// `T` should be a `Copy` enum with unit variants.
898
/// It should have appropriate `FromStr` and `Display`,
899
/// as well as [`NormalItemArgument`], impls.
900
///
901
/// Then `KeywordOrString` will implement the same traits.
902
///
903
/// Unlike [`Unknown`], unknown values are always retained as strings.
904
//
905
// `RelayFlags` has machinery for parsing flags and retaining unknown values,
906
// but it uses `Unknown` to maybe discard unknown flags,
907
// and it is generally quite a lot more complicated.
908
#[derive(Debug, PartialEq, Clone, Hash)]
909
#[allow(clippy::exhaustive_enums)] // this isn't going to change
910
pub enum KeywordOrString<T: Copy> {
911
    /// Known and recognised `T`
912
    Known(T),
913

            
914
    /// Unknown value in arbitrary syntax
915
    Unknown(String),
916
}
917

            
918
impl<T: Copy + NormalItemArgument> NormalItemArgument for KeywordOrString<T> {}
919

            
920
impl<T: Copy + Display> Display for KeywordOrString<T> {
921
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
922
        match self {
923
            KeywordOrString::Known(t) => Display::fmt(t, f),
924
            KeywordOrString::Unknown(s) => Display::fmt(s, f),
925
        }
926
    }
927
}
928

            
929
impl<T: Copy + FromStr> FromStr for KeywordOrString<T> {
930
    type Err = Void;
931
1156
    fn from_str(s: &str) -> Result<Self, Void> {
932
1156
        Ok(match s.parse() {
933
1156
            Ok(y) => KeywordOrString::Known(y),
934
            Err(_) => KeywordOrString::Unknown(s.to_owned()),
935
        })
936
1156
    }
937
}
938

            
939
// ============================================================
940

            
941
/// A sequence of `T` items, with their order retained
942
///
943
/// Normally when a `Vec<T>` appears in a network document,
944
/// we expect the items to be sortable - they must impl [`EncodeOrd`](encode::EncodeOrd).
945
/// When encoding, the output is always sorted.
946
///
947
/// *This* type retains the ordering.
948
///
949
/// Implements the [`encode`] and [`parse2`] item multiplicity traits.
950
#[derive(Debug, Clone, Hash, Deftly, Eq, PartialEq, Educe)]
951
#[educe(Default)]
952
#[derive_deftly(Transparent)]
953
#[allow(clippy::exhaustive_structs)]
954
pub struct RetainedOrderVec<T>(pub Vec<T>);
955

            
956
// ============================================================
957

            
958
/// Types for decoding times and dates
959
mod timeimpl {
960
    use super::*;
961
    use crate::{Error, NetdocErrorKind as EK, Pos, Result};
962
    use std::time::SystemTime;
963
    use time::{
964
        OffsetDateTime, PrimitiveDateTime, format_description::FormatItem,
965
        macros::format_description,
966
    };
967

            
968
    /// A wall-clock time, encoded in Iso8601 format with an intervening
969
    /// space between the date and time.
970
    ///
971
    /// (Example: "2020-10-09 17:38:12")
972
    #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Deftly)]
973
    #[derive_deftly(Transparent)]
974
    #[allow(clippy::exhaustive_structs)]
975
    pub struct Iso8601TimeSp(pub SystemTime);
976

            
977
    /// Formatting object for parsing the space-separated Iso8601 format.
978
    const ISO_8601SP_FMT: &[FormatItem] =
979
        format_description!("[year]-[month]-[day] [hour]:[minute]:[second]");
980

            
981
    impl FromStr for Iso8601TimeSp {
982
        type Err = Error;
983
8350
        fn from_str(s: &str) -> Result<Iso8601TimeSp> {
984
8354
            let d = PrimitiveDateTime::parse(s, &ISO_8601SP_FMT).map_err(|e| {
985
8
                EK::BadArgument
986
8
                    .at_pos(Pos::at(s))
987
8
                    .with_msg(format!("invalid time: {}", e))
988
12
            })?;
989
8342
            Ok(Iso8601TimeSp(d.assume_utc().into()))
990
8350
        }
991
    }
992

            
993
    /// Formats a SystemTime according to the given format description
994
    ///
995
    /// Also converts any time::error::format to fmt::Error
996
    /// so that it can be unwrapped in the Display trait impl
997
18
    fn fmt_with(
998
18
        t: SystemTime,
999
18
        format_desc: &[FormatItem],
18
    ) -> core::result::Result<String, fmt::Error> {
18
        OffsetDateTime::from(t)
18
            .format(format_desc)
18
            .map_err(|_| fmt::Error)
18
    }
    impl Display for Iso8601TimeSp {
12
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
12
            write!(f, "{}", fmt_with(self.0, ISO_8601SP_FMT)?)
12
        }
    }
    /// A wall-clock time, encoded in ISO8601 format without an intervening
    /// space.
    ///
    /// This represents a specific UTC instant (ie an instant in global civil time).
    /// But it may not be able to represent leap seconds.
    ///
    /// The timezone is not included in the string representation; `+0000` is implicit.
    ///
    /// (Example: "2020-10-09T17:38:12")
    #[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Deftly)]
    #[derive_deftly(Transparent)]
    #[allow(clippy::exhaustive_structs)]
    pub struct Iso8601TimeNoSp(pub SystemTime);
    /// Formatting object for parsing the space-separated Iso8601 format.
    const ISO_8601NOSP_FMT: &[FormatItem] =
        format_description!("[year]-[month]-[day]T[hour]:[minute]:[second]");
    impl FromStr for Iso8601TimeNoSp {
        type Err = Error;
24
        fn from_str(s: &str) -> Result<Iso8601TimeNoSp> {
28
            let d = PrimitiveDateTime::parse(s, &ISO_8601NOSP_FMT).map_err(|e| {
8
                EK::BadArgument
8
                    .at_pos(Pos::at(s))
8
                    .with_msg(format!("invalid time: {}", e))
12
            })?;
16
            Ok(Iso8601TimeNoSp(d.assume_utc().into()))
24
        }
    }
    impl Display for Iso8601TimeNoSp {
6
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
6
            write!(f, "{}", fmt_with(self.0, ISO_8601NOSP_FMT)?)
6
        }
    }
    impl crate::NormalItemArgument for Iso8601TimeNoSp {}
}
/// Types for decoding RSA keys
mod rsa {
    use super::*;
    use crate::{NetdocErrorKind as EK, Pos, Result};
    use std::ops::RangeBounds;
    use tor_llcrypto::pk::rsa::PublicKey;
    use tor_llcrypto::{d::Sha1, pk::rsa::KeyPair};
    /// The fixed exponent which we require when parsing any RSA key in a netdoc
    //
    // TODO this value is duplicated a lot in the v1 parser
    pub(crate) const RSA_FIXED_EXPONENT: u32 = 65537;
    /// The fixed exponent which we require when parsing any RSA key in a netdoc
    //
    // TODO this value is duplicated a lot in the v1 parser
    pub(crate) const RSA_MIN_BITS: usize = 1024;
    /// RSA public key, partially processed by `crate::paarse`.
    ///
    /// As parsed from a base64-encoded object.
    /// They key's properties (exponent and size) haven't been checked.
    #[allow(non_camel_case_types)]
    #[derive(Clone, Debug)]
    pub(crate) struct RsaPublicParse1Helper(PublicKey, Pos);
    /// RSA signature using SHA-1 as per "Signing documents" in dir-spec
    ///
    /// <https://spec.torproject.org/dir-spec/netdoc.html#signing>
    ///
    /// Used for
    /// [`AuthCert::dir-key-certification`](crate::doc::authcert::AuthCert::dir-key-certification),
    /// for example.
    ///
    /// # Caveats
    ///
    /// This type MUST NOT be used for anomalous signatures
    /// such as
    /// [`AuthCert::dir_key_crosscert`](crate::doc::authcert::AuthCert::dir_key_crosscert);
    /// in that case because `dir_key_crosscert`'s
    /// set of allowed object labels includes `ID SIGNATURE` whereas this type
    /// is always `SIGNATURE`
    #[derive(Debug, Clone, PartialEq, Eq, Deftly)]
    #[derive_deftly(ItemValueParseable, ItemValueEncodable)]
    #[deftly(netdoc(no_extra_args, signature(hash_accu = Sha1WholeKeywordLine)))]
    #[non_exhaustive]
    pub struct RsaSha1Signature {
        /// The bytes of the signature (base64-decoded).
        #[deftly(netdoc(object(label = "SIGNATURE"), with = crate::types::raw_data_object))]
        pub signature: Vec<u8>,
    }
    impl From<RsaPublicParse1Helper> for PublicKey {
5396
        fn from(k: RsaPublicParse1Helper) -> PublicKey {
5396
            k.0
5396
        }
    }
    impl super::FromBytes for RsaPublicParse1Helper {
5398
        fn from_bytes(b: &[u8], pos: Pos) -> Result<Self> {
5398
            let key = PublicKey::from_der(b)
5399
                .ok_or_else(|| EK::BadObjectVal.with_msg("unable to decode RSA public key"))?;
5396
            Ok(RsaPublicParse1Helper(key, pos))
5398
        }
    }
    impl RsaPublicParse1Helper {
        /// Give an error if the exponent of this key is not 'e'
5398
        pub(crate) fn check_exponent(self, e: u32) -> Result<Self> {
5398
            if self.0.exponent_is(e) {
5396
                Ok(self)
            } else {
2
                Err(EK::BadObjectVal
2
                    .at_pos(self.1)
2
                    .with_msg("invalid RSA exponent"))
            }
5398
        }
        /// Give an error if the length of this key's modulus, in
        /// bits, is not contained in 'bounds'
5402
        pub(crate) fn check_len<B: RangeBounds<usize>>(self, bounds: B) -> Result<Self> {
5402
            if bounds.contains(&self.0.bits()) {
5398
                Ok(self)
            } else {
4
                Err(EK::BadObjectVal
4
                    .at_pos(self.1)
4
                    .with_msg("invalid RSA length"))
            }
5402
        }
        /// Give an error if the length of this key's modulus, in
        /// bits, is not exactly `n`.
5020
        pub(crate) fn check_len_eq(self, n: usize) -> Result<Self> {
5020
            self.check_len(n..=n)
5020
        }
    }
    impl RsaSha1Signature {
        /// Make a signature according to "Signing documents" in the netdoc spec
        ///
        /// <https://spec.torproject.org/dir-spec/netdoc.html#signing>
        ///
        /// `NetdocEncoder` should have had the body of the document
        /// (everything except the signatures) already encoded.
        ///
        /// `item_keyword` is the keyword for the signature item.
        /// This is needed because different documents use different keywords,
        /// and the keyword is covered by the signature (an annoying is a layering violation).
        /// See <https://gitlab.torproject.org/tpo/core/torspec/-/issues/322>.
        ///
        /// # Example
        ///
        /// ```
        /// use derive_deftly::Deftly;
        /// use tor_error::Bug;
        /// use tor_llcrypto::pk::rsa;
        /// use tor_netdoc::derive_deftly_template_NetdocEncodable;
        /// use tor_netdoc::encode::{NetdocEncodable, NetdocEncoder};
        /// use tor_netdoc::types::RsaSha1Signature;
        ///
        /// #[derive(Deftly, Default)]
        /// #[derive_deftly(NetdocEncodable)]
        /// pub struct Document {
        ///     pub document_intro_keyword: (),
        /// }
        /// #[derive(Deftly)]
        /// #[derive_deftly(NetdocEncodable)]
        /// pub struct DocumentSignatures {
        ///     pub document_signature: RsaSha1Signature,
        /// }
        /// impl Document {
        ///     pub fn encode_sign(&self, k: &rsa::KeyPair) -> Result<String, Bug> {
        ///         let mut encoder = NetdocEncoder::new();
        ///         self.encode_unsigned(&mut encoder)?;
        ///         let document_signature =
        ///             RsaSha1Signature::new_sign_netdoc(k, &encoder, "document-signature")?;
        ///         let sigs = DocumentSignatures { document_signature };
        ///         sigs.encode_unsigned(&mut encoder)?;
        ///         let encoded = encoder.finish()?;
        ///         Ok(encoded)
        ///     }
        /// }
        ///
        /// # fn main() -> Result<(), anyhow::Error> {
        /// let k = rsa::KeyPair::generate(&mut tor_basic_utils::test_rng::testing_rng())?;
        /// let doc = Document::default();
        /// let encoded = doc.encode_sign(&k)?;
        /// assert!(encoded.starts_with(concat!(
        ///     "document-intro-keyword\n",
        ///     "document-signature\n",
        ///     "-----BEGIN SIGNATURE-----\n",
        /// )));
        /// # Ok(())
        /// # }
        /// ```
2
        pub fn new_sign_netdoc(
2
            private_key: &KeyPair,
2
            encoder: &NetdocEncoder,
2
            item_keyword: &str,
2
        ) -> StdResult<Self, Bug> {
2
            let mut h = Sha1::new();
2
            h.update(encoder.text_sofar()?);
2
            h.update(item_keyword);
2
            h.update("\n");
2
            let h = h.finalize();
2
            let signature = private_key
2
                .sign(&h)
2
                .map_err(into_internal!("RSA signing failed"))?;
2
            Ok(RsaSha1Signature { signature })
2
        }
    }
}
/// Types for decoding Ed25519 certificates
mod edcert {
    use crate::{NetdocErrorKind as EK, Pos, Result};
    use tor_cert::{CertType, Ed25519Cert, KeyUnknownCert};
    use tor_llcrypto::pk::ed25519;
    /// An ed25519 certificate as parsed from a directory object, with
    /// signature not validated.
    #[derive(Debug, Clone)]
    pub(crate) struct UnvalidatedEdCert(KeyUnknownCert, Pos);
    impl super::FromBytes for UnvalidatedEdCert {
9772
        fn from_bytes(b: &[u8], p: Pos) -> Result<Self> {
9773
            let cert = Ed25519Cert::decode(b).map_err(|e| {
2
                EK::BadObjectVal
2
                    .at_pos(p)
2
                    .with_msg("Bad certificate")
2
                    .with_source(e)
3
            })?;
9770
            Ok(Self(cert, p))
9772
        }
9772
        fn from_vec(v: Vec<u8>, p: Pos) -> Result<Self> {
9772
            Self::from_bytes(&v[..], p)
9772
        }
    }
    impl UnvalidatedEdCert {
        /// Give an error if this certificate's type is not `desired_type`.
9772
        pub(crate) fn check_cert_type(self, desired_type: CertType) -> Result<Self> {
9772
            if self.0.peek_cert_type() != desired_type {
2
                return Err(EK::BadObjectVal.at_pos(self.1).with_msg(format!(
2
                    "bad certificate type {} (wanted {})",
2
                    self.0.peek_cert_type(),
2
                    desired_type
2
                )));
9770
            }
9770
            Ok(self)
9772
        }
        /// Give an error if this certificate's subject_key is not `pk`
2290
        pub(crate) fn check_subject_key_is(self, pk: &ed25519::Ed25519Identity) -> Result<Self> {
2290
            if self.0.peek_subject_key().as_ed25519() != Some(pk) {
2
                return Err(EK::BadObjectVal
2
                    .at_pos(self.1)
2
                    .with_msg("incorrect subject key"));
2288
            }
2288
            Ok(self)
2290
        }
        /// Consume this object and return the inner Ed25519 certificate.
9768
        pub(crate) fn into_unchecked(self) -> KeyUnknownCert {
9768
            self.0
9768
        }
    }
}
/// Digest identifiers, and digests in the form `ALGORITHM=BASE64U`
///
/// As found in a vote's `m` line.
// TODO Use FixedB64 here.
mod identified_digest {
    use super::*;
    define_derive_deftly! {
        /// impl `FromStr` and `Display` for an enum with unit variants but also "unknown"
        ///
        /// Expected input: an enum whose variants are either
        ///  * unit variants, perhaps with `#[deftly(string_repr = "string")]`
        ///  * singleton tuple variant, containing `String` (or near equivalent)
        ///
        /// If `#[deftly(string_repro)]` is not specified,
        /// the default is snake case of the variant name.
        //
        // This macro may seem overkill, but open-coding these impls gives opportunities
        // for mismatches between FromStr, Display, and the variant name.
        //
        // TODO consider putting this in tor-basic-utils (maybe with a better name),
        // or possibly asking if derive_more want their FromStr to have this.
        StringReprUnitsOrUnknown for enum, expect items, beta_deftly:
        ${define STRING_REPR {
            ${vmeta(string_repr)
              as str,
              default { ${concat ${snake_case $vname}} }
            }
        }}
        impl FromStr for $ttype {
            type Err = Void;
14
            fn from_str(s: &str) -> Result<Self, Void> {
                $(
                    ${when v_is_unit}
                    if s == $STRING_REPR {
                        return Ok($vtype)
                    }
                )
                $(
                    ${when not(v_is_unit)} // anything else had better be Unknown
                    // not using `return ..;` makes this a syntax error if there are several.
                    Ok($vtype { 0: s.into() })
                )
            }
        }
        impl Display for $ttype {
            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
                let s: &str = match self {
                    $(
                        ${when v_is_unit}
                        $vtype => $STRING_REPR,
                    )
                    $(
                        ${when not(v_is_unit)}
                        $vpat => f_0,
                    )
                };
                Display::fmt(s, f)
            }
        }
    }
    /// The name of a digest algorithm.
    ///
    /// Can represent an unrecognised algorithm, so it's parsed and reproduced.
    #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deftly)]
    #[derive_deftly(StringReprUnitsOrUnknown)]
    #[non_exhaustive]
    pub enum DigestName {
        /// SHA-256
        Sha256,
        /// Unknown
        Unknown(String),
    }
    /// A single digest made with a nominated digest algorithm, `ALGORITHM=DIGEST`
    #[derive(Debug, Clone, Eq, PartialEq, Hash, derive_more::Display)]
    #[display("{alg}={value}")]
    #[non_exhaustive]
    pub struct IdentifiedDigest {
        /// The algorithm name.
        alg: DigestName,
        /// The digest value.
        ///
        /// Invariant: length is correct for `alg`, assuming `alg` is known.
        value: B64,
    }
    impl NormalItemArgument for DigestName {}
    impl NormalItemArgument for IdentifiedDigest {}
    /// Invalid syntax parsing an `IdentifiedDigest`
    #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, thiserror::Error)]
    #[error("invalid syntax, expected ALGORITHM=DIGEST: {0}")]
    pub struct IdentifiedDigestParseError(String);
    impl FromStr for IdentifiedDigest {
        type Err = IdentifiedDigestParseError;
14
        fn from_str(s: &str) -> Result<Self, Self::Err> {
14
            (|| {
14
                let (alg, value) = s.split_once('=').ok_or("missing equals sign")?;
14
                let alg = alg.parse().void_unwrap();
14
                let value = value
14
                    .parse::<B64>()
14
                    .map_err(|e| format!("bad value: {}", e.report()))?;
14
                if let Some(exp_len) = (|| {
                    Some({
                        use DigestName::*;
14
                        match alg {
14
                            Sha256 => 32,
                            Unknown(_) => None?,
                        }
                    })
                })() {
14
                    let val_len = value.as_bytes().len();
14
                    if val_len != exp_len {
                        return Err(format!("got {val_len} bytes, expected {exp_len}"));
14
                    }
                }
14
                Ok(IdentifiedDigest { alg, value })
            })()
14
            .map_err(IdentifiedDigestParseError)
14
        }
    }
}
/// Types for decoding RSA fingerprints
mod fingerprint {
    use super::*;
    use crate::parse2::{ArgumentError, ArgumentStream, ItemArgumentParseable};
    use crate::{Error, NetdocErrorKind as EK, Pos, Result};
    use base64ct::{Base64Unpadded, Encoding as _};
    use tor_llcrypto::pk::rsa::RsaIdentity;
    /// A hex-encoded RSA key identity (fingerprint) with spaces in it.
    ///
    /// <https://spec.torproject.org/dir-spec/server-descriptor-format.html?highlight=fingerprint#item:fingerprint>
    ///
    /// Netdoc parsing adapter for [`RsaIdentity`]
    #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deftly)]
    #[derive_deftly(Transparent)]
    #[allow(clippy::exhaustive_structs)]
    pub struct SpFingerprint(pub RsaIdentity);
    /// A hex-encoded fingerprint with no spaces.
    ///
    /// Netdoc parsing adapter for [`RsaIdentity`]
    #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deftly)]
    #[derive_deftly(Transparent)]
    #[allow(clippy::exhaustive_structs)]
    pub struct Fingerprint(pub RsaIdentity);
    /// A base64-encoded fingerprint (unpadded)
    ///
    /// Netdoc parsing adapter for [`RsaIdentity`]
    #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deftly)]
    #[derive_deftly(Transparent)]
    #[allow(clippy::exhaustive_structs)]
    pub struct Base64Fingerprint(pub RsaIdentity);
    /// A "long identity" in the format used for Family members.
    ///
    /// Netdoc parsing adapter for [`RsaIdentity`]
    #[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Deftly)]
    #[derive_deftly(Transparent)]
    #[allow(clippy::exhaustive_structs)]
    pub(crate) struct LongIdent(pub RsaIdentity);
    /// Helper: parse an identity from a hexadecimal string
828742
    fn parse_hex_ident(s: &str) -> Result<RsaIdentity> {
828795
        RsaIdentity::from_hex(s).ok_or_else(|| {
2186
            EK::BadArgument
2186
                .at_pos(Pos::at(s))
2186
                .with_msg("wrong length on fingerprint")
2186
        })
828742
    }
    impl FromStr for SpFingerprint {
        type Err = Error;
2290
        fn from_str(s: &str) -> Result<SpFingerprint> {
2292
            let ident = parse_hex_ident(&s.replace(' ', "")).map_err(|e| e.at_pos(Pos::at(s)))?;
2286
            Ok(SpFingerprint(ident))
2290
        }
    }
    impl ItemArgumentParseable for SpFingerprint {
10
        fn from_args<'s>(
10
            args: &mut ArgumentStream<'s>,
10
        ) -> std::result::Result<Self, ArgumentError> {
            // Take the first 10 arguments because an SpFingerprint consists of
            // 10 x 4 = 40 characters.
10
            let fp = args.take(10).collect::<Vec<_>>();
            // Less than 10 means missing arguments.
10
            if fp.len() < 10 {
2
                return Err(ArgumentError::Missing);
8
            }
            // More than 10 should be impossible due to .take(10).
8
            debug_assert_eq!(fp.len(), 10);
            // All arguments must be 4 characters long.
68
            if fp.iter().any(|arg| arg.len() != 4) {
2
                return Err(ArgumentError::Invalid);
6
            }
            // Convert it to a string without spaces, RsaIdentity::from_hex will
            // verify the rest.
            Ok(Self(
6
                RsaIdentity::from_hex(fp.join("").as_str()).ok_or(ArgumentError::Invalid)?,
            ))
10
        }
    }
    impl FromStr for Base64Fingerprint {
        type Err = Error;
2592
        fn from_str(s: &str) -> Result<Base64Fingerprint> {
2592
            let b = s.parse::<super::B64>()?;
2592
            let ident = RsaIdentity::from_bytes(b.as_bytes()).ok_or_else(|| {
                EK::BadArgument
                    .at_pos(Pos::at(s))
                    .with_msg("Wrong identity length")
            })?;
2592
            Ok(Base64Fingerprint(ident))
2592
        }
    }
    impl Display for Base64Fingerprint {
2
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
2
            Display::fmt(&Base64Unpadded::encode_string(self.as_bytes()), f)
2
        }
    }
    impl FromStr for Fingerprint {
        type Err = Error;
10514
        fn from_str(s: &str) -> Result<Fingerprint> {
10519
            let ident = parse_hex_ident(s).map_err(|e| e.at_pos(Pos::at(s)))?;
10504
            Ok(Fingerprint(ident))
10514
        }
    }
    impl Display for Fingerprint {
8
        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
8
            Display::fmt(&hex::encode_upper(self.as_bytes()), f)
8
        }
    }
    impl FromStr for LongIdent {
        type Err = Error;
815938
        fn from_str(mut s: &str) -> Result<LongIdent> {
815938
            if s.starts_with('$') {
410
                s = &s[1..];
815702
            }
815938
            if let Some(idx) = s.find(['=', '~']) {
6
                s = &s[..idx];
815932
            }
815938
            let ident = parse_hex_ident(s)?;
813766
            Ok(LongIdent(ident))
815938
        }
    }
    impl Display for LongIdent {
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
            write!(f, "${}", self.0.as_hex_upper())
        }
    }
    impl crate::NormalItemArgument for Fingerprint {}
    impl crate::NormalItemArgument for Base64Fingerprint {}
    impl crate::NormalItemArgument for LongIdent {}
}
/// A type for relay nicknames
mod nickname {
    use super::*;
    use tinystr::TinyAsciiStr;
    /// This is a strange limit, but it comes from Tor.
    const MAX_NICKNAME_LEN: usize = 19;
    /// The nickname for a Tor relay.
    ///
    /// These nicknames are legacy mechanism that's occasionally useful in
    /// debugging. They should *never* be used to uniquely identify relays;
    /// nothing prevents two relays from having the same nickname.
    ///
    /// Nicknames are required to be ASCII, alphanumeric, and between 1 and 19
    /// characters inclusive.
    #[derive(Clone, Debug, PartialEq, Eq)]
    pub struct Nickname(tinystr::TinyAsciiStr<MAX_NICKNAME_LEN>);
    /// Invalid nickname
    #[derive(Clone, Debug, thiserror::Error)]
    #[error("invalid nickname")]
    #[non_exhaustive]
    pub struct InvalidNickname {}
    impl Nickname {
        /// Return a view of this nickname as a string slice.
8
        pub(crate) fn as_str(&self) -> &str {
8
            self.0.as_str()
8
        }
    }
    impl Display for Nickname {
2
        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2
            self.as_str().fmt(f)
2
        }
    }
    impl FromStr for Nickname {
        type Err = InvalidNickname;
469034
        fn from_str(s: &str) -> Result<Self, InvalidNickname> {
469036
            let tiny = TinyAsciiStr::from_str(s).map_err(|_| InvalidNickname {})?;
469030
            if tiny.is_ascii_alphanumeric() && !tiny.is_empty() {
469026
                Ok(Nickname(tiny))
            } else {
4
                Err(InvalidNickname {})
            }
469034
        }
    }
    impl crate::NormalItemArgument for Nickname {}
}
/// Hostnames etc.
//
// TODO maybe move all this to tor-basic-utils
mod hostname {
    use super::*;
    use std::net::IpAddr;
    /// Internet hostname
    ///
    /// Valid according to Internet RFC1123,
    /// with the additional restriction that there must be at least one letter.
    /// (That means that anything accepted as a `Hostname`
    /// won't be accepted as an address even by very relaxed IPv4 address parsers.
    /// We presume that no TLD will ever exist that is entirely decimal digits.)
    ///
    /// Preserves case.
    ///
    /// Reserved hostname such as `example.come`, `tor.invalid` and `localhost`
    /// are accepted.
    ///
    /// # Comparisons; `PartialEq`, `Eq`
    ///
    /// The `PartialEq` and `Eq` implementations are case sensitive,
    /// even though internet hostnames are not case-sensitive.
    ///
    /// Comparing hostnames for identical apparent meaning is complicated.
    /// If you need to do that, you (may) need to engage with punycode (IDN),
    /// as well as arranging for a case-insensitive comparison.
    ///
    /// And of course, hostnames reference to the DNS.
    /// A single host may have multiple names and it may change its address.
    #[derive(Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] //
    #[derive(derive_more::Into, derive_more::Deref, derive_more::AsRef, derive_more::Display)]
    pub struct Hostname(String);
    /// Hostname, or IP address (v4 or v6)
    ///
    /// Preserves hostname case.  See [`Hostname`].
    ///
    /// Reserved hostnames and addresses (eg `0.0.0.0` or `tor.invalid`) are accepted.
    ///
    /// IPv6 addresses are represented *without* surrounding `[ ]`.
    ///
    /// Therefore, you cannot make this into a host-and-port by appending `:port`.
    /// To process name-and-port is complex.  `SRV` (or `MX`) records might be involved.
    //
    // This type is called `InternetHost` to emphasise that it is primarily for
    // hosts on the public internet and, unlike arti-client's `Host`,
    // has special handling of `.onion` addresses.
    #[derive(Clone, Debug, Hash, Eq, PartialEq, Ord, PartialOrd)] //
    #[derive(derive_more::Display)]
    #[allow(clippy::exhaustive_enums)]
    // TODO derive .as_hostname(), .as_ip_addr(), From<Hostname>, From<IpAddr>
    pub enum InternetHost {
        /// Hostname
        #[display("{_0}")]
        Name(Hostname),
        /// IP address (v4 or v6)
        #[display("{_0}")]
        IpAddr(IpAddr),
    }
    /// Invalid hostname
    #[derive(Clone, Debug, thiserror::Error)]
    #[error("invalid hostname")]
    #[non_exhaustive]
    pub struct InvalidHostname {}
    /// Invalid Internet hostname/address
    #[derive(Clone, Debug, thiserror::Error)]
    #[error("invalid: not a valid hostname, nor a valid IPv4 or IPv6 address")]
    #[non_exhaustive]
    pub struct InvalidInternetHost {}
    impl Hostname {
        /// Obtain this hostname as a `str`
8
        pub fn as_str(&self) -> &str {
8
            &self.0
8
        }
    }
    impl AsRef<str> for Hostname {
        fn as_ref(&self) -> &str {
            self.as_str()
        }
    }
    impl TryFrom<String> for Hostname {
        type Error = InvalidHostname;
496
        fn try_from(s: String) -> Result<Self, InvalidHostname> {
496
            if hostname_validator::is_valid(&s) &&
                // Reject hostnames that consist only of decimal digits and full stops.
                // Some of those are accepted by some old IPv4 address parsers.
                // If any fool makes a TLD that is only digits, they deserve everything they get.
560
                !s.chars().all(|c| c.is_ascii_digit() || c == '.')
            {
452
                Ok(Hostname(s))
            } else {
44
                Err(InvalidHostname {})
            }
496
        }
    }
    impl FromStr for Hostname {
        type Err = InvalidHostname;
496
        fn from_str(s: &str) -> Result<Self, InvalidHostname> {
496
            s.to_owned().try_into()
496
        }
    }
    impl FromStr for InternetHost {
        type Err = InvalidInternetHost;
3182
        fn from_str(s: &str) -> Result<Self, InvalidInternetHost> {
3182
            if let Ok(y) = s.parse() {
2720
                Ok(InternetHost::IpAddr(y))
462
            } else if let Ok(y) = s.parse() {
444
                Ok(InternetHost::Name(y))
            } else {
                // For simplicity, we  discard the errors from parsing the options
                // rather than trying to reproduce them.  Why something isn't a valid
                // address or hostname ought to be fairly obvious.
18
                Err(InvalidInternetHost {})
            }
3182
        }
    }
    impl NormalItemArgument for Hostname {}
    impl NormalItemArgument for InternetHost {}
}
/// Contact information of the relay operator.
mod contact_info {
    use super::*;
    /// `contact` item: contact information (eg of a relay dirauth operator)
    ///
    /// <https://spec.torproject.org/dir-spec/server-descriptor-format.html#item:contact>
    ///
    /// Also used for authority entries in netstatus documents.
    #[derive(Clone, Debug, PartialEq, Eq, Deftly)] //
    #[derive(derive_more::Into, derive_more::AsRef, derive_more::Deref, derive_more::Display)]
    #[derive_deftly(ItemValueEncodable)]
    #[non_exhaustive]
    pub struct ContactInfo(#[deftly(netdoc(rest))] String);
    /// Contact information (`contact` item value) has invalid syntax
    #[derive(Clone, Debug, thiserror::Error)]
    #[error("contact information (`contact` item value) has invalid syntax")]
    #[non_exhaustive]
    pub struct InvalidContactInfo {}
    impl FromStr for ContactInfo {
        type Err = InvalidContactInfo;
3162
        fn from_str(s: &str) -> Result<Self, InvalidContactInfo> {
            // TODO torspec#396 we should probably impose more restrictions
            // For now we forbid `\n` and initial whitespace, which is enough to ensure
            // that all values will roundtrip unchanged through netdoc encoding and parsing.
3162
            if s.contains('\n') || s.starts_with(char::is_whitespace) {
4
                Err(InvalidContactInfo {})
            } else {
3158
                Ok(ContactInfo(s.to_owned()))
            }
3162
        }
    }
    impl ItemValueParseable for ContactInfo {
1968
        fn from_unparsed(mut item: UnparsedItem<'_>) -> Result<Self, parse2::ErrorProblem> {
1968
            item.check_no_object()?;
1968
            item.args_mut()
1968
                .into_remaining()
1968
                .parse()
1968
                .map_err(|_e| item.args().handle_error("info", ArgumentError::Invalid))
1968
        }
    }
}
/// Types for boolean-like types.
mod boolean {
    use std::{fmt::Display, str::FromStr};
    use derive_more::{From, Into};
    use crate::{Error, NetdocErrorKind as EK, NormalItemArgument, Pos};
    /// A boolean that is represented by a `0` (false) or `1` (true).
    // TODO DIRMIRROR: Derive Transparent
    #[derive(Clone, Copy, Debug, Default, From, Into)]
    #[allow(clippy::exhaustive_structs)]
    pub struct NumericBoolean(pub bool);
    impl FromStr for NumericBoolean {
        type Err = Error;
6
        fn from_str(s: &str) -> Result<Self, Self::Err> {
6
            match s {
6
                "0" => Ok(Self(false)),
4
                "1" => Ok(Self(true)),
2
                _ => Err(EK::BadArgument
2
                    .at_pos(Pos::at(s))
2
                    .with_msg("Invalid numeric boolean")),
            }
6
        }
    }
    impl Display for NumericBoolean {
4
        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4
            write!(f, "{}", u8::from(self.0))
4
        }
    }
    impl NormalItemArgument for NumericBoolean {}
}
/// Types for router descriptors.
pub mod routerdesc {
    use super::*;
    use parse2::ErrorProblem as EP;
    use tor_llcrypto::pk::ed25519;
    /// Version argument found in an `overload-general` item.
    ///
    /// <https://spec.torproject.org/dir-spec/server-descriptor-format.html#item:overload-general>
    #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, strum::EnumString, strum::Display)]
    #[non_exhaustive]
    pub enum OverloadGeneralVersion {
        /// Version 1, currently the only supported and specified one.
        #[strum(serialize = "1")]
        V1,
    }
    impl NormalItemArgument for OverloadGeneralVersion {}
    /// The overload general type found in router descriptors.
    ///
    /// <https://spec.torproject.org/dir-spec/server-descriptor-format.html#item:overload-general>
    #[derive(Debug, Clone, Copy, PartialEq, Eq, Deftly)]
    #[derive_deftly(ItemValueParseable)]
    #[non_exhaustive]
    pub struct OverloadGeneral {
        /// The version of the item.
        pub version: OverloadGeneralVersion,
        /// The timestamp since when the relay is overloaded.
        pub since: Iso8601TimeSp,
    }
    /// Introduction line of a router descriptor.
    ///
    /// <https://spec.torproject.org/dir-spec/server-descriptor-format.html#item:router>
    #[derive(Clone, Debug, PartialEq, Eq, Deftly)]
    #[derive_deftly(ItemValueParseable)]
    #[non_exhaustive]
    pub struct RouterDescIntroItem {
        /// A valid router [`Nickname`].
        pub nickname: Nickname,
        /// An IPv4 address in dotted-squad format.
        pub address: std::net::Ipv4Addr,
        /// The TCP port of the onion router.
        pub orport: u16,
        /// Legacy.
        pub socksport: u16,
        /// Legacy.
        pub dirport: u16,
    }
    /// Digest identifying the extra-info document.
    ///
    /// <https://spec.torproject.org/dir-spec/server-descriptor-format.html#item:extra-info-digest>
    #[derive(Clone, Debug, PartialEq, Eq, Deftly)]
    #[derive_deftly(ItemValueParseable)]
    #[non_exhaustive]
    pub struct ExtraInfoDigests {
        /// Mandatory SHA-1 of the signed data in base 16.
        pub sha1: FixedB16U<20>,
        /// Optional SHA-256 of the entire extra-info in base 64.
        pub sha2: Option<FixedB64<32>>,
    }
    /// Accumulator for router descriptor hash signatures.
    #[derive(Debug, Clone, Default, Deftly)]
    #[derive_deftly(AsMutSelf)]
    #[allow(clippy::exhaustive_structs)]
    pub struct RouterHashAccu {
        /// Potentially the SHA-1 for the signature.
        pub sha1: Option<[u8; 20]>,
        /// Potentially the SHA-256 for the signature.
        pub sha256: Option<[u8; 32]>,
    }
    /// SHA-256 router descriptor signature including magic and the keyword.
    #[derive(Debug, Clone, PartialEq, Eq, Deftly)]
    #[derive_deftly(ItemValueEncodable)]
    #[allow(clippy::exhaustive_structs)]
    // TODO SPEC is RouterSigEd25519 not a standard-ish kind of signature?
    // TODO DIRAUTH is RouterSigEd25519 not a standard-ish kind of signature?
    pub struct RouterSigEd25519(pub ed25519::Signature);
    impl RouterSigEd25519 {
        /// The magic prefix for hashing this type of signature.
        const HASH_PREFIX_MAGIC: &str = "Tor router descriptor signature v1";
        /// Calculate the hash for signature
        ///
        /// `signature_item_kw_spc` is the keyword *with a trailing space*.
        /// It's `&[&str]` for the convenience of the two call sites.
4
        fn hash(document_sofar: &str, signature_item_kw_spc: &[&str]) -> [u8; 32] {
4
            debug_assert!(
4
                signature_item_kw_spc
4
                    .last()
4
                    .expect("signature_item_kw_spc")
4
                    .ends_with(" ")
            );
4
            let mut h = tor_llcrypto::d::Sha256::new();
4
            h.update(Self::HASH_PREFIX_MAGIC);
4
            h.update(document_sofar);
6
            for b in signature_item_kw_spc {
6
                h.update(b);
6
            }
4
            h.finalize().into()
4
        }
        /// Make a signature during document encoding
        ///
        /// `item_keyword` is the keyword for the signature item.
        ///
        /// # Example
        ///
        /// ```
        /// use derive_deftly::Deftly;
        /// use tor_error::Bug;
        /// use tor_llcrypto::pk::ed25519;
        /// use tor_netdoc::derive_deftly_template_NetdocEncodable;
        /// use tor_netdoc::encode::{NetdocEncodable, NetdocEncoder};
        /// use tor_netdoc::types::routerdesc::RouterSigEd25519;
        ///
        /// #[derive(Deftly, Default)]
        /// #[derive_deftly(NetdocEncodable)]
        /// pub struct Document {
        ///     pub document_intro_keyword: (),
        /// }
        /// #[derive(Deftly)]
        /// #[derive_deftly(NetdocEncodable)]
        /// pub struct DocumentSignatures {
        ///     pub document_signature: RouterSigEd25519,
        /// }
        /// impl Document {
        ///     pub fn encode_sign(&self, k: &ed25519::Keypair) -> Result<String, Bug> {
        ///         let mut encoder = NetdocEncoder::new();
        ///         self.encode_unsigned(&mut encoder)?;
        ///         let document_signature =
        ///             RouterSigEd25519::new_sign_netdoc(k, &encoder, "document-signature")?;
        ///         let sigs = DocumentSignatures { document_signature };
        ///         sigs.encode_unsigned(&mut encoder)?;
        ///         let encoded = encoder.finish()?;
        ///         Ok(encoded)
        ///     }
        /// }
        ///
        /// # fn main() -> Result<(), anyhow::Error> {
        /// let k = ed25519::Keypair::generate(&mut tor_basic_utils::test_rng::testing_rng());
        /// let doc = Document::default();
        /// let encoded = doc.encode_sign(&k)?;
        /// assert!(encoded.starts_with(concat!(
        ///     "document-intro-keyword\n",
        ///     "document-signature ",
        /// )));
        /// # Ok(())
        /// # }
        /// ```
2
        pub fn new_sign_netdoc(
2
            private_key: &ed25519::Keypair,
2
            encoder: &NetdocEncoder,
2
            item_keyword: &str,
2
        ) -> StdResult<Self, Bug> {
2
            let signature = private_key
2
                .sign(&Self::hash(encoder.text_sofar()?, &[item_keyword, " "]))
2
                .to_bytes()
2
                .into();
2
            Ok(RouterSigEd25519(signature))
2
        }
    }
    impl SignatureItemParseable for RouterSigEd25519 {
        type HashAccu = RouterHashAccu;
2
        fn from_unparsed_and_body(
2
            mut item: UnparsedItem<'_>,
2
            hash_inputs: &SignatureHashInputs<'_>,
2
            hash: &mut Self::HashAccu,
2
        ) -> Result<Self, EP> {
            // TODO DIRMIRROR break this out into impl ItemArgumentParseable for Signature
2
            let args = item.args_mut();
2
            let sig = FixedB64::<64>::from_args(args)
2
                .map_err(|e| args.handle_error("router-sig-ed25519", e))?
                .0;
2
            let sig = ed25519::Signature::from(sig);
2
            hash.sha256 = Some(Self::hash(
2
                hash_inputs.document_sofar,
2
                &[hash_inputs.signature_item_kw_spc],
2
            ));
2
            Ok(Self(sig))
2
        }
    }
    /// SHA-1 router descriptor signature over `router-sig-ed25519`.
    // TODO DIRMIRROR Is this not the same as RsaSha1Signature ?
    #[derive(Debug, Clone, PartialEq, Eq)]
    #[allow(clippy::exhaustive_structs)]
    pub struct RouterSignature(pub Vec<u8>);
    impl SignatureItemParseable for RouterSignature {
        type HashAccu = RouterHashAccu;
        fn from_unparsed_and_body(
            mut item: UnparsedItem<'_>,
            hash_inputs: &SignatureHashInputs<'_>,
            hash: &mut Self::HashAccu,
        ) -> Result<Self, EP> {
            // There must be no additonal arguments.
            let args = item.args_mut();
            if args.next().is_some() {
                return Err(EP::UnexpectedArgument {
                    column: args.prev_arg_column(),
                });
            }
            let obj = item.object().ok_or(EP::MissingObject)?.decode_data()?;
            let mut h = tor_llcrypto::d::Sha1::new();
            h.update(hash_inputs.document_sofar);
            h.update(hash_inputs.signature_item_line);
            h.update("\n");
            hash.sha1 = Some(h.finalize().into());
            Ok(Self(obj))
        }
    }
}
#[cfg(test)]
mod test {
    // @@ begin test lint list maintained by maint/add_warning @@
    #![allow(clippy::bool_assert_comparison)]
    #![allow(clippy::clone_on_copy)]
    #![allow(clippy::dbg_macro)]
    #![allow(clippy::mixed_attributes_style)]
    #![allow(clippy::print_stderr)]
    #![allow(clippy::print_stdout)]
    #![allow(clippy::single_char_pattern)]
    #![allow(clippy::unwrap_used)]
    #![allow(clippy::unchecked_time_subtraction)]
    #![allow(clippy::useless_vec)]
    #![allow(clippy::needless_pass_by_value)]
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
    use itertools::Itertools;
    use base64ct::Encoding;
    use super::*;
    use crate::{Pos, Result};
    /// Decode s as a multi-line base64 string, ignoring ascii whitespace.
    fn base64_decode_ignore_ws(s: &str) -> std::result::Result<Vec<u8>, base64ct::Error> {
        let mut s = s.to_string();
        s.retain(|c| !c.is_ascii_whitespace());
        base64ct::Base64::decode_vec(s.as_str())
    }
    #[test]
    fn base64() -> Result<()> {
        // Test parsing success:
        // Unpadded:
        assert_eq!("Mi43MTgyOA".parse::<B64>()?.as_bytes(), &b"2.71828"[..]);
        assert!("Mi43MTgyOA".parse::<B64>()?.check_len(7..8).is_ok());
        assert_eq!("Mg".parse::<B64>()?.as_bytes(), &b"2"[..]);
        assert!("Mg".parse::<B64>()?.check_len(1..2).is_ok());
        assert_eq!(
            "8J+NkvCfjZLwn42S8J+NkvCfjZLwn42S"
                .parse::<B64>()?
                .as_bytes(),
            "🍒🍒🍒🍒🍒🍒".as_bytes()
        );
        assert!(
            "8J+NkvCfjZLwn42S8J+NkvCfjZLwn42S"
                .parse::<B64>()?
                .check_len(24..25)
                .is_ok()
        );
        assert!(
            "ppwthHXW8kXD0f9fE7UPYsOAAu4uj5ORwSomCMxKkz8="
                .parse::<B64>()?
                .check_len(32..33)
                .is_ok()
        );
        // Padded:
        assert_eq!("Mi43MTgyOA==".parse::<B64>()?.as_bytes(), &b"2.71828"[..]);
        assert!("Mi43MTgyOA==".parse::<B64>()?.check_len(7..8).is_ok());
        assert_eq!("Mg==".parse::<B64>()?.as_bytes(), &b"2"[..]);
        assert!("Mg==".parse::<B64>()?.check_len(1..2).is_ok());
        // Test parsing failures:
        // Invalid character.
        assert!("Mi43!!!!!!".parse::<B64>().is_err());
        // Invalid last character.
        assert!("Mi".parse::<B64>().is_err());
        assert!(
            "ppwthHXW8kXD0f9fE7UPYsOAAu4uj5ORwSomCMxaaaa"
                .parse::<B64>()
                .is_err()
        );
        // Invalid length.
        assert!("Mi43MTgyOA".parse::<B64>()?.check_len(8..).is_err());
        Ok(())
    }
    #[test]
    fn base64_lengths() -> Result<()> {
        assert_eq!("".parse::<B64>()?.as_bytes(), b"");
        assert!("=".parse::<B64>().is_err());
        assert!("==".parse::<B64>().is_err());
        assert!("B".parse::<B64>().is_err());
        assert!("B=".parse::<B64>().is_err());
        assert!("B==".parse::<B64>().is_err());
        assert!("Bg=".parse::<B64>().is_err());
        assert_eq!("Bg".parse::<B64>()?.as_bytes(), b"\x06");
        assert_eq!("Bg==".parse::<B64>()?.as_bytes(), b"\x06");
        assert_eq!("BCg".parse::<B64>()?.as_bytes(), b"\x04\x28");
        assert_eq!("BCg=".parse::<B64>()?.as_bytes(), b"\x04\x28");
        assert!("BCg==".parse::<B64>().is_err());
        assert_eq!("BCDE".parse::<B64>()?.as_bytes(), b"\x04\x20\xc4");
        assert!("BCDE=".parse::<B64>().is_err());
        assert!("BCDE==".parse::<B64>().is_err());
        Ok(())
    }
    #[test]
    fn base64_rev() {
        use base64ct::{Base64, Base64Unpadded};
        // Check that strings that we accept are precisely ones which
        // can be generated by either Base64 or Base64Unpadded
        for n in 0..=5 {
            for c_vec in std::iter::repeat_n("ACEQg/=".chars(), n).multi_cartesian_product() {
                let s: String = c_vec.into_iter().collect();
                #[allow(clippy::print_stderr)]
                let b = match s.parse::<B64>() {
                    Ok(b) => {
                        eprintln!("{:10} {:?}", &s, b.as_bytes());
                        b
                    }
                    Err(_) => {
                        eprintln!("{:10} Err", &s);
                        continue;
                    }
                };
                let b = b.as_bytes();
                let ep = Base64::encode_string(b);
                let eu = Base64Unpadded::encode_string(b);
                assert!(
                    s == ep || s == eu,
                    "{:?} decoded to {:?} giving neither {:?} nor {:?}",
                    s,
                    b,
                    ep,
                    eu
                );
            }
        }
    }
    #[test]
    fn base16() -> anyhow::Result<()> {
        let chk = |s: &str, b: &[u8]| -> anyhow::Result<()> {
            let parsed = s.parse::<B16>()?;
            assert_eq!(parsed.as_bytes(), b, "{s:?}");
            assert_eq!(parsed.to_string(), s.to_ascii_lowercase());
            let parsed = s.parse::<B16U>()?;
            assert_eq!(parsed.as_bytes(), b, "{s:?}");
            assert_eq!(parsed.to_string(), s.to_ascii_uppercase());
            Ok(())
        };
        chk("332e313432", b"3.142")?;
        chk("332E313432", b"3.142")?;
        chk("332E3134", b"3.14")?;
        assert!("332E313".parse::<B16>().is_err());
        assert!("332G3134".parse::<B16>().is_err());
        Ok(())
    }
    #[test]
    fn curve25519() -> Result<()> {
        use tor_llcrypto::pk::curve25519::PublicKey;
        let k1 = "ppwthHXW8kXD0f9fE7UPYsOAAu4uj5ORwSomCMxKkz8=";
        let k2 = hex::decode("a69c2d8475d6f245c3d1ff5f13b50f62c38002ee2e8f9391c12a2608cc4a933f")
            .unwrap();
        let k2: &[u8; 32] = &k2[..].try_into().unwrap();
        let k1: PublicKey = k1.parse::<Curve25519Public>()?.into();
        assert_eq!(k1, (*k2).into());
        assert!(
            "ppwthHXW8kXD0f9fE7UPYsOAAu4uj5ORwSomCMxKkz"
                .parse::<Curve25519Public>()
                .is_err()
        );
        assert!(
            "ppwthHXW8kXD0f9fE7UPYsOAAu4uj5ORSomCMxKkz"
                .parse::<Curve25519Public>()
                .is_err()
        );
        assert!(
            "ppwthHXW8kXD0f9fE7UPYsOAAu4uj5wSomCMxKkz"
                .parse::<Curve25519Public>()
                .is_err()
        );
        assert!(
            "ppwthHXW8kXD0f9fE7UPYsOAAu4ORwSomCMxKkz"
                .parse::<Curve25519Public>()
                .is_err()
        );
        Ok(())
    }
    #[test]
    fn ed25519() -> Result<()> {
        use tor_llcrypto::pk::ed25519::Ed25519Identity;
        let k1 = "WVIPQ8oArAqLY4XzkcpIOI6U8KsUJHBQhG8SC57qru0";
        let k2 = hex::decode("59520f43ca00ac0a8b6385f391ca48388e94f0ab14247050846f120b9eeaaeed")
            .unwrap();
        let k1: Ed25519Identity = k1.parse::<Ed25519Public>()?.into();
        assert_eq!(k1, Ed25519Identity::from_bytes(&k2).unwrap());
        assert!(
            "WVIPQ8oArAqLY4Xzk0!!!!8KsUJHBQhG8SC57qru"
                .parse::<Ed25519Public>()
                .is_err()
        );
        assert!(
            "WVIPQ8oArAqLY4XzkcpIU8KsUJHBQhG8SC57qru"
                .parse::<Ed25519Public>()
                .is_err()
        );
        assert!(
            "WVIPQ8oArAqLY4XzkcpIU8KsUJHBQhG8SC57qr"
                .parse::<Ed25519Public>()
                .is_err()
        );
        // right length, bad key:
        assert!(
            "ppwthHXW8kXD0f9fE7UPYsOAAu4uj5ORwSomCMxaaaa"
                .parse::<Curve25519Public>()
                .is_err()
        );
        Ok(())
    }
    #[test]
    fn time() -> Result<()> {
        use humantime::parse_rfc3339;
        use std::time::SystemTime;
        let t = "2020-09-29 13:36:33".parse::<Iso8601TimeSp>()?;
        let t: SystemTime = t.into();
        assert_eq!(t, parse_rfc3339("2020-09-29T13:36:33Z").unwrap());
        assert!("2020-FF-29 13:36:33".parse::<Iso8601TimeSp>().is_err());
        assert!("2020-09-29Q13:99:33".parse::<Iso8601TimeSp>().is_err());
        assert!("2020-09-29".parse::<Iso8601TimeSp>().is_err());
        assert!("too bad, waluigi time".parse::<Iso8601TimeSp>().is_err());
        assert_eq!(
            "2020-09-29 13:36:33",
            "2020-09-29 13:36:33".parse::<Iso8601TimeSp>()?.to_string()
        );
        let t = "2020-09-29T13:36:33".parse::<Iso8601TimeNoSp>()?;
        let t: SystemTime = t.into();
        assert_eq!(t, parse_rfc3339("2020-09-29T13:36:33Z").unwrap());
        assert!("2020-09-29 13:36:33".parse::<Iso8601TimeNoSp>().is_err());
        assert!("2020-09-29Q13:99:33".parse::<Iso8601TimeNoSp>().is_err());
        assert!("2020-09-29".parse::<Iso8601TimeNoSp>().is_err());
        assert!("too bad, waluigi time".parse::<Iso8601TimeNoSp>().is_err());
        assert_eq!(
            "2020-09-29T13:36:33",
            "2020-09-29T13:36:33"
                .parse::<Iso8601TimeNoSp>()?
                .to_string()
        );
        Ok(())
    }
    #[test]
    fn rsa_public_key() {
        // Taken from a chutney network.
        let key_b64 = r#"
        MIIBigKCAYEAsDkzTcKS4kAF56R2ijb9qCek53tKC1EwMdpWMk58bB28fY6kHc55
        E7n1hB+LC5neZlx88GKuZ9k8P3g0MlO5ejalcfBdIIm28Nz86JXf/L23YnEpxnG/
        IpxZEcmx/EYN+vwp72W3DGuzyntaoaut6lGJk+O/aRCLLcTm4MNznvN1ackK2H6b
        Xm2ejRwtVRLoPKODJiPGl43snCfXXWsMH3IALFOgm0szPLv2fAJzBI8VWrUN81M/
        lgwJhG6+xbr1CkrXI5fKs/TNr0B0ydC9BIZplmPrnXaeNklnw1cqUJ1oxDSgBrvx
        rpDo7paObjSPV26opa68QKGa7Gu2MZQC3RzViNCbawka/108g6hSUkoM+Om2oivr
        DvtMOs10MjsfibEBVnwEhqnlb/gj3hJkYoGRsCwAyMIaMObHcmAevMJRWAjGCc8T
        GMS9dSmg1IZst+U+V2OCcIHXT6wZ1zPsBM0pYKVLCwtewaq1306k0n+ekriEo7eI
        FS3Dd/Dx/a6jAgMBAAE=
        "#;
        let key_bytes = base64_decode_ignore_ws(key_b64).unwrap();
        let rsa = RsaPublicParse1Helper::from_vec(key_bytes, Pos::None).unwrap();
        let bits = tor_llcrypto::pk::rsa::PublicKey::from(rsa.clone()).bits();
        assert_eq!(bits, 3072);
        // tests on a valid key
        assert!(rsa.clone().check_exponent(65537).is_ok());
        assert!(rsa.clone().check_exponent(1337).is_err());
        assert!(rsa.clone().check_len_eq(3072).is_ok());
        assert!(rsa.clone().check_len(1024..=4096).is_ok());
        assert!(rsa.clone().check_len(1024..=1024).is_err());
        assert!(rsa.check_len(4096..).is_err());
        // A string of bytes that is not an RSA key.
        let failure = RsaPublicParse1Helper::from_vec(vec![1, 2, 3], Pos::None);
        assert!(failure.is_err());
    }
    #[test]
    fn ed_cert() {
        use tor_llcrypto::pk::ed25519::Ed25519Identity;
        // From a chutney network.
        let cert_b64 = r#"
        AQQABwRNAR6m3kq5h8i3wwac+Ti293opoOP8RKGP9MT0WD4Bbz7YAQAgBACGCdys
        G7AwsoYMIKenDN6In6ReiGF8jaYoGqmWKDVBdGGMDIZyNIq+VdhgtAB1EyNFHJU1
        jGM0ir9dackL+PIsHbzJH8s/P/8RfUsKIL6/ZHbn3nKMxLH/8kjtxp5ScAA=
        "#;
        let cert_bytes = base64_decode_ignore_ws(cert_b64).unwrap();
        // From the cert above.
        let right_subject_key: Ed25519Identity = "HqbeSrmHyLfDBpz5OLb3eimg4/xEoY/0xPRYPgFvPtg"
            .parse::<Ed25519Public>()
            .unwrap()
            .into();
        // From `ed25519()` test above.
        let wrong_subject_key: Ed25519Identity = "WVIPQ8oArAqLY4XzkcpIOI6U8KsUJHBQhG8SC57qru0"
            .parse::<Ed25519Public>()
            .unwrap()
            .into();
        // decode and check correct type and key
        let cert = UnvalidatedEdCert::from_vec(cert_bytes, Pos::None)
            .unwrap()
            .check_cert_type(tor_cert::CertType::IDENTITY_V_SIGNING)
            .unwrap()
            .check_subject_key_is(&right_subject_key)
            .unwrap();
        // check wrong type.
        assert!(
            cert.clone()
                .check_cert_type(tor_cert::CertType::RSA_ID_X509)
                .is_err()
        );
        // check wrong key.
        assert!(cert.check_subject_key_is(&wrong_subject_key).is_err());
        // Try an invalid object that isn't a certificate.
        let failure = UnvalidatedEdCert::from_vec(vec![1, 2, 3], Pos::None);
        assert!(failure.is_err());
    }
    #[test]
    fn fingerprint() -> Result<()> {
        use tor_llcrypto::pk::rsa::RsaIdentity;
        let fp1 = "7467 A97D 19CD 2B4F 2BC0 388A A99C 5E67 710F 847E";
        let fp2 = "7467A97D19CD2B4F2BC0388AA99C5E67710F847E";
        let fp3 = "$7467A97D19CD2B4F2BC0388AA99C5E67710F847E";
        let fp4 = "$7467A97D19CD2B4F2BC0388AA99C5E67710F847E=fred";
        let k = hex::decode(fp2).unwrap();
        let k = RsaIdentity::from_bytes(&k[..]).unwrap();
        assert_eq!(RsaIdentity::from(fp1.parse::<SpFingerprint>()?), k);
        assert_eq!(RsaIdentity::from(fp2.parse::<SpFingerprint>()?), k);
        assert!(fp3.parse::<SpFingerprint>().is_err());
        assert!(fp4.parse::<SpFingerprint>().is_err());
        assert!(fp1.parse::<Fingerprint>().is_err());
        assert_eq!(RsaIdentity::from(fp2.parse::<Fingerprint>()?), k);
        assert!(fp3.parse::<Fingerprint>().is_err());
        assert!(fp4.parse::<Fingerprint>().is_err());
        assert_eq!(Fingerprint(k).to_string(), fp2);
        assert!(fp1.parse::<LongIdent>().is_err());
        assert_eq!(RsaIdentity::from(fp2.parse::<LongIdent>()?), k);
        assert_eq!(RsaIdentity::from(fp3.parse::<LongIdent>()?), k);
        assert_eq!(RsaIdentity::from(fp4.parse::<LongIdent>()?), k);
        assert!("xxxx".parse::<Fingerprint>().is_err());
        assert!("ffffffffff".parse::<Fingerprint>().is_err());
        let fp_b64 = "dGepfRnNK08rwDiKqZxeZ3EPhH4";
        assert_eq!(RsaIdentity::from(fp_b64.parse::<Base64Fingerprint>()?), k);
        assert_eq!(Base64Fingerprint(k).to_string(), fp_b64);
        Ok(())
    }
    #[test]
    fn nickname() -> anyhow::Result<()> {
        let n: Nickname = "Foo".parse()?;
        assert_eq!(n.as_str(), "Foo");
        assert_eq!(n.to_string(), "Foo");
        let word = "Untr1gonometr1cally";
        assert_eq!(word.len(), 19);
        let long: Nickname = word.parse()?;
        assert_eq!(long.as_str(), word);
        let too_long = "abcdefghijklmnopqrstuvwxyz";
        let not_ascii = "Eyjafjallajökull";
        let too_short = "";
        let other_invalid = "contains space";
        assert!(not_ascii.len() <= 19);
        assert!(too_long.parse::<Nickname>().is_err());
        assert!(not_ascii.parse::<Nickname>().is_err());
        assert!(too_short.parse::<Nickname>().is_err());
        assert!(other_invalid.parse::<Nickname>().is_err());
        Ok(())
    }
    /// Test for both `Hostname` and `InternetHost`
    #[test]
    fn hostname() {
        use std::net::IpAddr;
        // Test a string that we should treat as a valid hostname.
        let chk_name = |s: &str| {
            let n: Hostname = s.parse().expect(s);
            assert_eq!(n.as_str(), s);
            assert_eq!(n.to_string(), s);
            assert_eq!(s.parse::<InternetHost>().expect(s), InternetHost::Name(n));
        };
        // Test a string that looks like it could be an address or a hostname.
        // We parse those as addresses.
        let chk_either = |s: &str| {
            let h: InternetHost = s.parse().expect(s);
            let a: IpAddr = s.parse().expect(s);
            assert_eq!(h, InternetHost::IpAddr(a), "{s:?}");
            assert_eq!(h.to_string(), a.to_string(), "{s:?}");
        };
        // Test a string that's an address, and isn't a valid hostname.
        let chk_addr = |s: &str| {
            let _: InvalidHostname = s.parse::<Hostname>().expect_err(s);
            chk_either(s);
        };
        // Test a string that we should reject.
        let chk_bad = |s: &str| {
            let _: InvalidHostname = s.parse::<Hostname>().expect_err(s);
            let _: InvalidInternetHost = s.parse::<InternetHost>().expect_err(s);
        };
        chk_name("foo.bar");
        chk_name("localhost");
        chk_name("tor.invalid");
        chk_name("example.com");
        // Unarguably invalid.
        chk_bad("");
        chk_bad("foo bar");
        chk_bad("foo..bar");
        chk_bad("foo.-bar");
        chk_bad(" foo.bar ");
        chk_bad("[::1]");
        // Strings that some IP address parsers accept as addresses,
        // but which are also valid hostnames according to RFC1123.
        //
        // We reject them rather than processing of them as hostnames,
        // exposing downstream software to possible strangeness.
        chk_bad("1");
        chk_bad("127.0.0.023");
        // No-one thinks this is a valid IP address but we reject it as a hostname too,
        // even though it's syntactically legal per RFC1123, because it's quite bad.
        chk_bad("1.2.3.4.5");
        chk_either("0.0.0.0");
        chk_either("127.0.0.1");
        chk_addr("::");
        chk_addr("::1");
        chk_addr("2001:0db8:85a3:0000:0000:8a2e:0370:7334");
        chk_addr("::ffff:192.0.2.3"); // IPv6-mapped IPv4 address
    }
    #[test]
    fn contact_info() -> anyhow::Result<()> {
        use encode::NetdocEncodable;
        use parse2::{ParseInput, parse_netdoc};
        const S: &str = "some relay operator";
        let n: ContactInfo = S.parse()?;
        assert_eq!(n.as_str(), S);
        assert_eq!(n.to_string(), S);
        let bad = |s: &str| {
            let _: InvalidContactInfo = s.parse::<ContactInfo>().unwrap_err();
        };
        bad(" starts with space");
        bad("contains\nnewline");
        #[derive(PartialEq, Debug, Deftly)]
        #[derive_deftly(NetdocParseable, NetdocEncodable)]
        struct TestDoc {
            pub intro: (),
            pub contact: ContactInfo,
        }
        let roundtrip = |s: &str| -> anyhow::Result<()> {
            let doc = TestDoc {
                intro: (),
                contact: s.parse()?,
            };
            let mut enc = NetdocEncoder::new();
            doc.encode_unsigned(&mut enc)?;
            let enc = enc.finish()?;
            let reparsed = parse_netdoc::<TestDoc>(&ParseInput::new(&enc, "<test>"))?;
            assert_eq!(doc, reparsed);
            Ok(())
        };
        roundtrip("normal")?;
        roundtrip("trailing  white space  ")?;
        roundtrip("wtf is this allowed in \x03 netdocs\r")?; // TODO torspec#396
        Ok(())
    }
    /// Round trip test for [`NumericBoolean`] ensuring that `0` is false,
    /// `1` is true, and other things fail.
    #[test]
    fn numeric_boolean() {
        let chk = |s: &str| {
            assert_eq!(NumericBoolean::from_str(s).expect(s).to_string(), s);
        };
        chk("0");
        chk("1");
        // Testing this because it is not a u8.
        assert!(NumericBoolean::from_str("10000").is_err());
    }
    /// Test that ensures SpFingerprint matches the 10x4 requirement.
    #[test]
    fn sp_fingerprint() {
        use derive_deftly::Deftly;
        use tor_llcrypto::pk::rsa::RsaIdentity;
        use crate::parse2::ErrorProblem;
        #[derive(Deftly)]
        #[derive_deftly(NetdocParseable)]
        struct Wrapper {
            #[deftly(netdoc(single_arg))]
            fingerprint: SpFingerprint,
        }
        /// Small helper to parse an [`SpFingerprint`].
        fn parse2(s: &str) -> std::result::Result<SpFingerprint, ErrorProblem> {
            use crate::parse2::{self, ParseInput};
            let s = format!("fingerprint {s}\n");
            parse2::parse_netdoc::<Wrapper>(&ParseInput::new(&s, ""))
                .map(|x| x.fingerprint)
                .map_err(|x| x.problem)
        }
        // Test a valid one.
        assert_eq!(
            parse2(&vec!["ABAB"; 10].join(" ")).unwrap(),
            SpFingerprint(RsaIdentity::from_bytes(&[0xAB; 20]).unwrap())
        );
        // Test a valid one with tail.
        assert_eq!(
            parse2(&vec!["ABAB"; 11].join(" ")).unwrap(),
            SpFingerprint(RsaIdentity::from_bytes(&[0xAB; 20]).unwrap())
        );
        // Missing argument
        assert!(matches!(
            parse2(&vec!["ABAB"; 9].join(" ")).unwrap_err(),
            ErrorProblem::MissingArgument { .. }
        ));
        // Invalid argument.
        // In this case, we have string with a total length of 40 but with
        // one pair having 6 characters and another one having 2 as a proof
        // of that.
        assert!(matches!(
            parse2("0000 000000 00 0000 0000 0000 0000 0000 0000 0000").unwrap_err(),
            ErrorProblem::InvalidArgument { .. }
        ));
        // And of course invalid hex should fail too.
        assert!(matches!(
            parse2(&vec!["ZZZZ"; 10].join(" ")).unwrap_err(),
            ErrorProblem::InvalidArgument { .. }
        ));
    }
}