1
//! Parsing implementation for Tor microdescriptors.
2
//!
3
//! A "microdescriptor" is an incomplete, infrequently-changing
4
//! summary of a relay's information that is generated by
5
//! the directory authorities.
6
//!
7
//! Microdescriptors are much smaller than router descriptors, and
8
//! change less frequently. For this reason, they're currently used
9
//! for building circuits by all relays and clients.
10
//!
11
//! Microdescriptors can't be used on their own: you need to know
12
//! which relay they are for, which requires a valid consensus
13
//! directory.
14

            
15
use crate::parse::keyword::Keyword;
16
use crate::parse::parser::SectionRules;
17
use crate::parse::tokenize::{ItemResult, NetDocReader};
18
use crate::types::family::{RelayFamily, RelayFamilyId, RelayFamilyIds};
19
use crate::types::misc::*;
20
use crate::types::policy::PortPolicy;
21
use crate::util;
22
use crate::util::PeekableIterator;
23
use crate::util::str::Extent;
24
use crate::{AllowAnnotations, Error, NetdocErrorKind as EK, Result};
25
use tor_basic_utils::intern::Intern;
26
use tor_error::internal;
27
use tor_llcrypto::d;
28
use tor_llcrypto::pk::{curve25519, ed25519, rsa};
29

            
30
use derive_deftly::Deftly;
31
use digest::Digest;
32
use std::str::FromStr as _;
33
use std::sync::LazyLock;
34
use std::time;
35

            
36
#[cfg(feature = "build_docs")]
37
mod build;
38

            
39
#[cfg(feature = "build_docs")]
40
pub use build::MicrodescBuilder;
41

            
42
/// Length of a router microdescriptor digest
43
pub const DOC_DIGEST_LEN: usize = 32;
44

            
45
/// Annotations prepended to a microdescriptor that has been stored to
46
/// disk.
47
#[allow(dead_code)]
48
#[derive(Clone, Debug, Default)]
49
pub struct MicrodescAnnotation {
50
    /// A time at which this microdescriptor was last listed in some
51
    /// consensus document.
52
    last_listed: Option<time::SystemTime>,
53
}
54

            
55
/// The digest of a microdescriptor as used in microdesc consensuses
56
pub type MdDigest = [u8; DOC_DIGEST_LEN];
57

            
58
/// A single microdescriptor.
59
///
60
/// <https://spec.torproject.org/dir-spec/computing-microdescriptors.html>
61
#[derive(Clone, Debug, Deftly, PartialEq, Eq)]
62
#[derive_deftly(Constructor, NetdocEncodable, NetdocParseable)]
63
#[allow(clippy::exhaustive_structs)]
64
pub struct Microdesc {
65
    /// The legacy onion key, whose object is optional but whose item serves
66
    /// as the intro line for these kind of descriptors.
67
    pub onion_key: MicrodescIntroItem,
68

            
69
    /// Public key used for the ntor circuit extension protocol.
70
    #[deftly(constructor)]
71
    #[deftly(netdoc(single_arg))]
72
    pub ntor_onion_key: Curve25519Public,
73

            
74
    /// Declared family for this relay.
75
    #[deftly(netdoc(default(skip)))]
76
    pub family: Intern<RelayFamily>,
77

            
78
    /// Family identities for this relay.
79
    #[deftly(netdoc(default(skip)))]
80
    pub family_ids: RelayFamilyIds,
81

            
82
    /// List of IPv4 ports to which this relay will exit
83
    #[deftly(netdoc(keyword = "p", default(skip)))]
84
    pub ipv4_policy: Intern<PortPolicy>,
85

            
86
    /// List of IPv6 ports to which this relay will exit
87
    #[deftly(netdoc(keyword = "p6", default(skip)))]
88
    pub ipv6_policy: Intern<PortPolicy>,
89

            
90
    /// Ed25519 identity for this relay
91
    // TODO SPEC: Set this to "exactly once".
92
    #[deftly(constructor)]
93
    #[deftly(netdoc(keyword = "id", with = "Ed25519IdentityLine"))]
94
    pub ed25519_id: Ed25519IdentityLine,
95

            
96
    // addr is obsolete and doesn't go here any more
97
    // pr is obsolete and doesn't go here any more.
98
    #[doc(hidden)]
99
    #[deftly(netdoc(skip))]
100
    pub __non_exhaustive: (),
101
}
102

            
103
/// A single microdescriptor and also its SHA256 hash
104
///
105
/// API compatibility type.
106
///
107
/// This type is only generated when the microdescriptor is parsed
108
/// using the old parser ([`MicrodescAndHash::parse`])
109
/// rather than the new one
110
/// (`Microdesc as `[`NetdocParseable`](crate::parse2::NetdocParseable)).
111
#[derive(Clone, Debug, Deftly, PartialEq, Eq, derive_more::Deref, derive_more::DerefMut)]
112
#[non_exhaustive]
113
pub struct MicrodescAndHash {
114
    /// The microdescriptor
115
    #[deref]
116
    #[deref_mut]
117
    pub md: Microdesc,
118

            
119
    /// The SHA256 digest of the text of this microdescriptor.
120
    ///
121
    /// This value is used to identify the microdescriptor when
122
    /// downloading it, and when listing it in a consensus document.
123
    pub sha256: MdDigest,
124
}
125

            
126
impl Microdesc {
127
    /// Return the ntor onion key for this microdesc
128
1951980
    pub fn ntor_key(&self) -> &curve25519::PublicKey {
129
1951980
        &self.ntor_onion_key.0
130
1951980
    }
131
    /// Return the ipv4 exit policy for this microdesc
132
45168510
    pub fn ipv4_policy(&self) -> &Intern<PortPolicy> {
133
45168510
        &self.ipv4_policy
134
45168510
    }
135
    /// Return the ipv6 exit policy for this microdesc
136
6289150
    pub fn ipv6_policy(&self) -> &Intern<PortPolicy> {
137
6289150
        &self.ipv6_policy
138
6289150
    }
139
    /// Return the relay family for this microdesc
140
56526398
    pub fn family(&self) -> &RelayFamily {
141
56526398
        self.family.as_ref()
142
56526398
    }
143
    /// Return the ed25519 identity for this microdesc, if its
144
    /// Ed25519 identity is well-formed.
145
164372784
    pub fn ed25519_id(&self) -> &ed25519::Ed25519Identity {
146
164372784
        &self.ed25519_id.pk.0
147
164372784
    }
148
    /// Return a list of family ids for this microdesc.
149
108015238
    pub fn family_ids(&self) -> &[RelayFamilyId] {
150
108015238
        self.family_ids.as_ref()
151
108015238
    }
152
}
153

            
154
impl MicrodescAndHash {
155
    /// Create a new MicrodescBuilder that can be used to construct
156
    /// microdescriptors.
157
    ///
158
    /// This function is only available when the crate is built with the
159
    /// `build_docs` feature.
160
    ///
161
    /// # Limitations
162
    ///
163
    /// The generated microdescriptors cannot yet be encoded, and do
164
    /// not yet have correct sha256 digests. As such they are only
165
    /// useful for testing.
166
    #[cfg(feature = "build_docs")]
167
459686
    pub fn builder() -> MicrodescBuilder {
168
459686
        MicrodescBuilder::new()
169
459686
    }
170

            
171
    /// Return the sha256 digest of this microdesc.
172
70242330
    pub fn digest(&self) -> &MdDigest {
173
70242330
        &self.sha256
174
70242330
    }
175
}
176

            
177
/// Intro line for a [`Microdesc`].
178
///
179
/// The object (the onion key) is deprecated and optional, but the item itself
180
/// must be present, because it is used to mark the start of the netdoc.
181
///
182
/// The object is private to prevent interfacing applications
183
/// from generating microdesc's with an onion-key; they are not necessary
184
/// anymore and just waste space.
185
#[derive(Debug, Clone, Default, Deftly, PartialEq, Eq)]
186
#[derive_deftly(ItemValueEncodable, ItemValueParseable)]
187
pub struct MicrodescIntroItem(#[deftly(netdoc(object))] Option<rsa::PublicKey>);
188

            
189
/// A microdescriptor annotated with additional data
190
///
191
/// TODO: rename this.
192
#[allow(dead_code)]
193
#[derive(Clone, Debug)]
194
pub struct AnnotatedMicrodesc {
195
    /// The microdescriptor
196
    md: MicrodescAndHash,
197
    /// The annotations for the microdescriptor
198
    ann: MicrodescAnnotation,
199
    /// Where did we find the microdescriptor with the originally parsed
200
    /// string?
201
    location: Option<Extent>,
202
}
203

            
204
impl AnnotatedMicrodesc {
205
    /// Consume this annotated microdesc and discard its annotations.
206
364
    pub fn into_microdesc(self) -> MicrodescAndHash {
207
364
        self.md
208
364
    }
209

            
210
    /// Return a reference to the microdescriptor within this annotated
211
    /// microdescriptor.
212
18
    pub fn md(&self) -> &MicrodescAndHash {
213
18
        &self.md
214
18
    }
215

            
216
    /// If this Microdesc was parsed from `s`, return its original text.
217
364
    pub fn within<'a>(&self, s: &'a str) -> Option<&'a str> {
218
371
        self.location.as_ref().and_then(|ext| ext.reconstruct(s))
219
364
    }
220
}
221

            
222
decl_keyword! {
223
    /// Keyword type for recognized objects in microdescriptors.
224
    MicrodescKwd {
225
        annotation "@last-listed" => ANN_LAST_LISTED,
226
        "onion-key" => ONION_KEY,
227
        "ntor-onion-key" => NTOR_ONION_KEY,
228
        "family" => FAMILY,
229
        "family-ids" => FAMILY_IDS,
230
        "p" => P,
231
        "p6" => P6,
232
        "id" => ID,
233
    }
234
}
235

            
236
/// Rules about annotations that can appear before a Microdescriptor
237
2
static MICRODESC_ANNOTATIONS: LazyLock<SectionRules<MicrodescKwd>> = LazyLock::new(|| {
238
    use MicrodescKwd::*;
239
2
    let mut rules = SectionRules::builder();
240
2
    rules.add(ANN_LAST_LISTED.rule().args(1..));
241
2
    rules.add(ANN_UNRECOGNIZED.rule().may_repeat().obj_optional());
242
    // unrecognized annotations are okay; anything else is a bug in this
243
    // context.
244
2
    rules.reject_unrecognized();
245
2
    rules.build()
246
2
});
247
/// Rules about entries that must appear in an Microdesc, and how they must
248
/// be formed.
249
54
static MICRODESC_RULES: LazyLock<SectionRules<MicrodescKwd>> = LazyLock::new(|| {
250
    use MicrodescKwd::*;
251

            
252
54
    let mut rules = SectionRules::builder();
253
54
    rules.add(ONION_KEY.rule().required().no_args().obj_optional());
254
54
    rules.add(NTOR_ONION_KEY.rule().required().args(1..));
255
54
    rules.add(FAMILY.rule().args(1..));
256
54
    rules.add(FAMILY_IDS.rule().args(0..));
257
54
    rules.add(P.rule().args(2..));
258
54
    rules.add(P6.rule().args(2..));
259
54
    rules.add(ID.rule().may_repeat().args(2..));
260
54
    rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
261
54
    rules.build()
262
54
});
263

            
264
impl MicrodescAnnotation {
265
    /// Extract a (possibly empty) microdescriptor annotation from a
266
    /// reader.
267
    #[allow(dead_code)]
268
14
    fn parse_from_reader(
269
14
        reader: &mut NetDocReader<'_, MicrodescKwd>,
270
14
    ) -> Result<MicrodescAnnotation> {
271
        use MicrodescKwd::*;
272

            
273
29
        let mut items = reader.pause_at(|item| item.is_ok_with_non_annotation());
274
14
        let body = MICRODESC_ANNOTATIONS.parse(&mut items)?;
275

            
276
14
        let last_listed = match body.get(ANN_LAST_LISTED) {
277
6
            None => None,
278
8
            Some(item) => Some(item.args_as_str().parse::<Iso8601TimeSp>()?.into()),
279
        };
280

            
281
14
        Ok(MicrodescAnnotation { last_listed })
282
14
    }
283
}
284

            
285
impl MicrodescAndHash {
286
    /// Parse a string into a new microdescriptor.
287
64
    pub fn parse(s: &str) -> Result<MicrodescAndHash> {
288
64
        let mut items = crate::parse::tokenize::NetDocReader::new(s)?;
289
68
        let (result, _) = Self::parse_from_reader(&mut items).map_err(|e| e.within(s))?;
290
56
        items.should_be_exhausted()?;
291
56
        Ok(result)
292
64
    }
293

            
294
    /// Extract a single microdescriptor from a NetDocReader.
295
446
    fn parse_from_reader(
296
446
        reader: &mut NetDocReader<'_, MicrodescKwd>,
297
446
    ) -> Result<(MicrodescAndHash, Option<Extent>)> {
298
        use MicrodescKwd::*;
299
446
        let s = reader.str();
300

            
301
446
        let mut first_onion_key = true;
302
        // We'll pause at the next annotation, or at the _second_ onion key.
303
1779
        let mut items = reader.pause_at(|item| match item {
304
            Err(_) => false,
305
1756
            Ok(item) => {
306
1756
                item.kwd().is_annotation()
307
1750
                    || if item.kwd() == ONION_KEY {
308
712
                        let was_first = first_onion_key;
309
712
                        first_onion_key = false;
310
712
                        !was_first
311
                    } else {
312
1038
                        false
313
                    }
314
            }
315
1756
        });
316

            
317
446
        let body = MICRODESC_RULES.parse(&mut items)?;
318

            
319
        // We have to start with onion-key
320
442
        let start_pos = {
321
            // unwrap here is safe because parsing would have failed
322
            // had there not been at least one item.
323
            #[allow(clippy::unwrap_used)]
324
446
            let first = body.first_item().unwrap();
325
446
            if first.kwd() != ONION_KEY {
326
4
                return Err(EK::WrongStartingToken
327
4
                    .with_msg(first.kwd_str().to_string())
328
4
                    .at_pos(first.pos()));
329
442
            }
330
            // Unwrap is safe here because we are parsing these strings from s
331
            #[allow(clippy::unwrap_used)]
332
442
            util::str::str_offset(s, first.kwd_str()).unwrap()
333
        };
334

            
335
        // Legacy (tap) onion key.  We parse this to make sure it's well-formed,
336
        // but then we discard it immediately, since we never want to use it.
337
        //
338
        // In microdescriptors, the ONION_KEY field is mandatory, but its
339
        // associated object is optional.
340
        {
341
442
            let tok = body.required(ONION_KEY)?;
342
442
            if tok.has_obj() {
343
436
                let _: rsa::PublicKey = tok
344
436
                    .parse_obj::<RsaPublicParse1Helper>("RSA PUBLIC KEY")?
345
436
                    .check_len_eq(1024)?
346
436
                    .check_exponent(65537)?
347
436
                    .into();
348
6
            }
349
        }
350

            
351
        // Ntor onion key
352
442
        let ntor_onion_key = body
353
442
            .required(NTOR_ONION_KEY)?
354
442
            .parse_arg::<Curve25519Public>(0)?;
355

            
356
        // family
357
        //
358
        // (We don't need to add the relay's own ID to this family, as we do in
359
        // RouterDescs: the authorities already took care of that for us.)
360
442
        let family = body
361
442
            .maybe(FAMILY)
362
442
            .parse_args_as_str::<RelayFamily>()?
363
442
            .unwrap_or_else(RelayFamily::new)
364
442
            .intern();
365

            
366
        // Family ids (happy families case).
367
442
        let family_ids = body
368
442
            .maybe(FAMILY_IDS)
369
442
            .args_as_str()
370
442
            .unwrap_or("")
371
442
            .split_ascii_whitespace()
372
442
            .map(RelayFamilyId::from_str)
373
442
            .collect::<Result<RelayFamilyIds>>()?;
374

            
375
        // exit policies.
376
442
        let ipv4_policy = body
377
442
            .maybe(P)
378
442
            .parse_args_as_str::<PortPolicy>()?
379
442
            .unwrap_or_else(PortPolicy::new_reject_all);
380
442
        let ipv6_policy = body
381
442
            .maybe(P6)
382
442
            .parse_args_as_str::<PortPolicy>()?
383
438
            .unwrap_or_else(PortPolicy::new_reject_all);
384

            
385
        // ed25519 identity
386
434
        let ed25519_id = {
387
438
            let id_tok = body
388
438
                .slice(ID)
389
438
                .iter()
390
457
                .find(|item| item.arg(0) == Some("ed25519"));
391
438
            match id_tok {
392
                None => {
393
4
                    return Err(EK::MissingToken.with_msg("id ed25519"));
394
                }
395
434
                Some(tok) => Ed25519IdentityLine {
396
434
                    alg: Ed25519AlgorithmString::Ed25519,
397
434
                    pk: tok.parse_arg::<Ed25519Public>(1)?,
398
                },
399
            }
400
        };
401

            
402
434
        let end_pos = {
403
            // unwrap here is safe because parsing would have failed
404
            // had there not been at least one item.
405
            #[allow(clippy::unwrap_used)]
406
434
            let last_item = body.last_item().unwrap();
407
434
            last_item.offset_after(s).ok_or_else(|| {
408
                Error::from(internal!("last item was not within source string"))
409
                    .at_pos(last_item.end_pos())
410
            })?
411
        };
412

            
413
434
        let text = s.get(start_pos..end_pos).ok_or(internal!("chopped utf8"))?;
414
434
        let sha256 = d::Sha256::digest(text.as_bytes()).into();
415

            
416
434
        let location = Extent::new(s, text);
417

            
418
434
        let md = Microdesc {
419
434
            onion_key: Default::default(),
420
434
            ntor_onion_key,
421
434
            family,
422
434
            ipv4_policy: ipv4_policy.intern(),
423
434
            ipv6_policy: ipv6_policy.intern(),
424
434
            ed25519_id,
425
434
            family_ids,
426
434
            __non_exhaustive: (),
427
434
        };
428
434
        let md = MicrodescAndHash { md, sha256 };
429
434
        Ok((md, location))
430
446
    }
431
}
432

            
433
/// Consume tokens from 'reader' until the next token is the beginning
434
/// of a microdescriptor: an annotation or an ONION_KEY.  If no such
435
/// token exists, advance to the end of the reader.
436
4
fn advance_to_next_microdesc(reader: &mut NetDocReader<'_, MicrodescKwd>, annotated: bool) {
437
    use MicrodescKwd::*;
438
    loop {
439
4
        let item = reader.peek();
440
2
        match item {
441
2
            Some(Ok(t)) => {
442
2
                let kwd = t.kwd();
443
2
                if (annotated && kwd.is_annotation()) || kwd == ONION_KEY {
444
2
                    return;
445
                }
446
            }
447
            Some(Err(_)) => {
448
                // We skip over broken tokens here.
449
                //
450
                // (This case can't happen in practice, since if there had been
451
                // any error tokens, they would have been handled as part of
452
                // handling the previous microdesc.)
453
            }
454
            None => {
455
2
                return;
456
            }
457
        };
458
        let _ = reader.next();
459
    }
460
4
}
461

            
462
/// An iterator that parses one or more (possibly annotated)
463
/// microdescriptors from a string.
464
#[derive(Debug)]
465
pub struct MicrodescReader<'a> {
466
    /// True if we accept annotations; false otherwise.
467
    annotated: bool,
468
    /// An underlying reader to give us Items for the microdescriptors
469
    reader: NetDocReader<'a, MicrodescKwd>,
470
}
471

            
472
impl<'a> MicrodescReader<'a> {
473
    /// Construct a MicrodescReader to take microdescriptors from a string
474
    /// 's'.
475
110
    pub fn new(s: &'a str, allow: &AllowAnnotations) -> Result<Self> {
476
110
        let reader = NetDocReader::new(s)?;
477
110
        let annotated = allow == &AllowAnnotations::AnnotationsAllowed;
478
110
        Ok(MicrodescReader { annotated, reader })
479
110
    }
480

            
481
    /// If we're annotated, parse an annotation from the reader. Otherwise
482
    /// return a default annotation.
483
382
    fn take_annotation(&mut self) -> Result<MicrodescAnnotation> {
484
382
        if self.annotated {
485
14
            MicrodescAnnotation::parse_from_reader(&mut self.reader)
486
        } else {
487
368
            Ok(MicrodescAnnotation::default())
488
        }
489
382
    }
490

            
491
    /// Parse a (possibly annotated) microdescriptor from the reader.
492
    ///
493
    /// On error, parsing stops after the first failure.
494
382
    fn take_annotated_microdesc_raw(&mut self) -> Result<AnnotatedMicrodesc> {
495
382
        let ann = self.take_annotation()?;
496
382
        let (md, location) = MicrodescAndHash::parse_from_reader(&mut self.reader)?;
497
378
        Ok(AnnotatedMicrodesc { md, ann, location })
498
382
    }
499

            
500
    /// Parse a (possibly annotated) microdescriptor from the reader.
501
    ///
502
    /// On error, advance the reader to the start of the next microdescriptor.
503
382
    fn take_annotated_microdesc(&mut self) -> Result<AnnotatedMicrodesc> {
504
382
        let pos_orig = self.reader.pos();
505
382
        let result = self.take_annotated_microdesc_raw();
506
382
        if result.is_err() {
507
4
            if self.reader.pos() == pos_orig {
508
                // No tokens were consumed from the reader.  We need to
509
                // drop at least one token to ensure we aren't looping.
510
                //
511
                // (This might not be able to happen, but it's easier to
512
                // explicitly catch this case than it is to prove that
513
                // it's impossible.)
514
                let _ = self.reader.next();
515
4
            }
516
4
            advance_to_next_microdesc(&mut self.reader, self.annotated);
517
378
        }
518
382
        result
519
382
    }
520
}
521

            
522
impl<'a> Iterator for MicrodescReader<'a> {
523
    type Item = Result<AnnotatedMicrodesc>;
524
492
    fn next(&mut self) -> Option<Self::Item> {
525
        // If there is no next token, we're at the end.
526
492
        self.reader.peek()?;
527

            
528
        Some(
529
382
            self.take_annotated_microdesc()
530
384
                .map_err(|e| e.within(self.reader.str())),
531
        )
532
492
    }
533
}
534

            
535
#[cfg(test)]
536
mod test {
537
    // @@ begin test lint list maintained by maint/add_warning @@
538
    #![allow(clippy::bool_assert_comparison)]
539
    #![allow(clippy::clone_on_copy)]
540
    #![allow(clippy::dbg_macro)]
541
    #![allow(clippy::mixed_attributes_style)]
542
    #![allow(clippy::print_stderr)]
543
    #![allow(clippy::print_stdout)]
544
    #![allow(clippy::single_char_pattern)]
545
    #![allow(clippy::unwrap_used)]
546
    #![allow(clippy::unchecked_time_subtraction)]
547
    #![allow(clippy::useless_vec)]
548
    #![allow(clippy::needless_pass_by_value)]
549
    #![allow(clippy::string_slice)] // See arti#2571
550
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
551
    use super::*;
552
    use crate::encode::{NetdocEncodable, NetdocEncoder};
553
    use hex_literal::hex;
554
    const TESTDATA: &str = include_str!("../../testdata/microdesc1.txt");
555
    const TESTDATA2: &str = include_str!("../../testdata/microdesc2.txt");
556
    const TESTDATA3: &str = include_str!("../../testdata/microdesc3.txt");
557
    const TESTDATA4: &str = include_str!("../../testdata/microdesc4.txt");
558

            
559
    fn read_bad(fname: &str) -> String {
560
        use std::fs;
561
        use std::path::PathBuf;
562
        let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
563
        path.push("testdata");
564
        path.push("bad-mds");
565
        path.push(fname);
566

            
567
        fs::read_to_string(path).unwrap()
568
    }
569

            
570
    #[test]
571
    fn parse_single() -> Result<()> {
572
        let _md = MicrodescAndHash::parse(TESTDATA)?;
573
        Ok(())
574
    }
575

            
576
    #[test]
577
    fn parse_no_tap_key() -> Result<()> {
578
        let _md = MicrodescAndHash::parse(TESTDATA3)?;
579
        Ok(())
580
    }
581

            
582
    #[test]
583
    fn parse_multi() -> Result<()> {
584
        use humantime::parse_rfc3339;
585
        let mds: Result<Vec<_>> =
586
            MicrodescReader::new(TESTDATA2, &AllowAnnotations::AnnotationsAllowed)?.collect();
587
        let mds = mds?;
588
        assert_eq!(mds.len(), 4);
589

            
590
        assert_eq!(
591
            mds[0].ann.last_listed.unwrap(),
592
            parse_rfc3339("2020-01-27T18:52:09Z").unwrap()
593
        );
594
        assert_eq!(
595
            mds[0].md().digest(),
596
            &hex!("38c71329a87098cb341c46c9c62bd646622b4445f7eb985a0e6adb23a22ccf4f")
597
        );
598
        assert_eq!(
599
            mds[0].md().ntor_key().as_bytes(),
600
            &hex!("5e895d65304a3a1894616660143f7af5757fe08bc18045c7855ee8debb9e6c47")
601
        );
602
        assert!(mds[0].md().ipv4_policy().allows_port(993));
603
        assert!(mds[0].md().ipv6_policy().allows_port(993));
604
        assert!(!mds[0].md().ipv4_policy().allows_port(25));
605
        assert!(!mds[0].md().ipv6_policy().allows_port(25));
606
        assert_eq!(
607
            mds[0].md().ed25519_id().as_bytes(),
608
            &hex!("2d85fdc88e6c1bcfb46897fca1dba6d1354f93261d68a79e0b5bc170dd923084")
609
        );
610

            
611
        Ok(())
612
    }
613

            
614
    #[test]
615
    fn parse_family_ids() -> Result<()> {
616
        let mds: Vec<AnnotatedMicrodesc> =
617
            MicrodescReader::new(TESTDATA4, &AllowAnnotations::AnnotationsNotAllowed)?
618
                .collect::<Result<_>>()?;
619
        assert_eq!(mds.len(), 2);
620
        let md0 = mds[0].md();
621
        let md1 = mds[1].md();
622
        assert!(md0.family_ids().is_empty());
623
        assert_eq!(
624
            md1.family_ids(),
625
            &[
626
                "ed25519:dXMgdGhlIHRyaXVtcGguICAgIC1UaG9tYXMgUGFpbmU"
627
                    .parse()
628
                    .unwrap(),
629
                "other:Example".parse().unwrap()
630
            ]
631
        );
632
        assert!(matches!(md1.family_ids()[0], RelayFamilyId::Ed25519(_)));
633

            
634
        Ok(())
635
    }
636

            
637
    #[test]
638
    fn test_bad() {
639
        use crate::Pos;
640
        use crate::types::policy::PolicyError;
641
        fn check(fname: &str, e: &Error) {
642
            let content = read_bad(fname);
643
            let res = MicrodescAndHash::parse(&content);
644
            assert!(res.is_err());
645
            assert_eq!(&res.err().unwrap(), e);
646
        }
647

            
648
        check(
649
            "wrong-start",
650
            &EK::WrongStartingToken
651
                .with_msg("family")
652
                .at_pos(Pos::from_line(1, 1)),
653
        );
654
        check(
655
            "bogus-policy",
656
            &EK::BadPolicy
657
                .at_pos(Pos::from_line(9, 1))
658
                .with_source(PolicyError::InvalidPort),
659
        );
660
        check(
661
            "non-ascii-policy",
662
            &EK::BadPolicy
663
                .at_pos(Pos::from_line(9, 1))
664
                .with_source(PolicyError::InvalidPort),
665
        );
666
        check("wrong-id", &EK::MissingToken.with_msg("id ed25519"));
667
    }
668

            
669
    #[test]
670
    fn test_recover() -> Result<()> {
671
        let mut data = read_bad("wrong-start");
672
        data += TESTDATA;
673
        data += &read_bad("wrong-id");
674

            
675
        let res: Vec<Result<_>> =
676
            MicrodescReader::new(&data, &AllowAnnotations::AnnotationsAllowed)?.collect();
677

            
678
        assert_eq!(res.len(), 3);
679
        assert!(res[0].is_err());
680
        assert!(res[1].is_ok());
681
        assert!(res[2].is_err());
682
        Ok(())
683
    }
684

            
685
    /// Checks whether parse2 works on [`Microdesc`].
686
    ///
687
    /// Certain values such as public keys are hardcoded and can be simply
688
    /// replaced by a copy and paste in the case one replaces the testdata2
689
    /// vector's in the future.
690
    #[test]
691
    fn parse2() -> anyhow::Result<()> {
692
        use tor_llcrypto::pk::ed25519::Ed25519Identity;
693

            
694
        use crate::parse2;
695

            
696
        let md = include_str!("../../testdata2/cached-microdescs.new");
697
        let mds = parse2::parse_netdoc_multiple::<Microdesc>(&parse2::ParseInput::new(
698
            md,
699
            "../../testdata2/cached-microdescs.new",
700
        ))
701
        .unwrap();
702

            
703
        assert_eq!(mds.len(), 7);
704
        assert_eq!(
705
            mds[0],
706
            Microdesc {
707
                onion_key: MicrodescIntroItem(rsa::PublicKey::from_der(
708
                    pem::parse(
709
                        "
710
-----BEGIN RSA PUBLIC KEY-----
711
MIGJAoGBANF8Zgxp8amY1esYdPj2Ada1ORiVB/A4sgKLQ5ij/wsasO3yjjLcvHRB
712
UJ0mAQWql/nauvjnKUeZFcGm3t7q0v3F9uUsOGTAZ/IKh31UQAm5OS/TJyf8IHky
713
Yl0wCKpUZFHs5CHsajLSfXZKHkwfqRXFEJu9aMtmQdQFfqE9JOJHAgMBAAE=
714
-----END RSA PUBLIC KEY-----
715
                        "
716
                    )
717
                    .unwrap()
718
                    .contents()
719
                )),
720
                ntor_onion_key: curve25519::PublicKey::from(<[u8; 32]>::from(
721
                    FixedB64::<32>::from_str("I1S8JfcqPPHWVTxfjq/eGmGiu/OtR+fF0Z86Ge1mq3s")
722
                        .unwrap()
723
                ))
724
                .into(),
725
                family: Default::default(),
726
                ipv4_policy: Default::default(),
727
                ipv6_policy: Default::default(),
728
                ed25519_id: Ed25519Identity::from(<[u8; 32]>::from(
729
                    FixedB64::<32>::from_str("yhO6nETO5AUdvJbLgPnw4mFjozGXWMCqOp30nY6nM8E")
730
                        .unwrap()
731
                ))
732
                .into(),
733
                family_ids: Default::default(),
734
                __non_exhaustive: (),
735
            }
736
        );
737

            
738
        let mut enc = NetdocEncoder::new();
739
        for md in &mds {
740
            md.encode_unsigned(&mut enc)?;
741
        }
742
        let enc = enc.finish()?;
743
        let exp = md;
744
        assert_eq_or_diff!(&enc, &exp);
745

            
746
        Ok(())
747
    }
748

            
749
    /// Manual test for happy families.
750
    // TODO: This should be included in testdata2/ but that would require the
751
    // chutney/shadow integration test to actually do families at all.
752
    #[test]
753
    fn parse2_happy_family() {
754
        use tor_llcrypto::pk::ed25519::Ed25519Identity;
755

            
756
        use crate::parse2::{self, ParseInput};
757
        use std::iter;
758

            
759
        // A microdescriptor taken from the wild containing happy families.
760
        const MICRODESC: &str = "\
761
onion-key
762
-----BEGIN RSA PUBLIC KEY-----
763
MIGJAoGBAMk57F7qGHVadBJ6m4028w13I1Qk67Ee0JU88w7NObKBph3DQYjgYs4e
764
eUdiW4Gdsx8w/xOuK0foCo0O8Iqq5MXtVcpUP/N+5uB7SVvGdJFsKw21KdIc6v8g
765
ACZAijw5ZPOdhLbyLQyFHNV8zXUov1dlx/Fb9M3lPMVevnDbuKM5AgMBAAE=
766
-----END RSA PUBLIC KEY-----
767
ntor-onion-key fhhP23UKD4L2jehA5gopAo5b6NSoB+kZN5Q4ULv3Zww
768
family $4CFFD403DAB89A689F3FDB80B5366E46D879E736 $4D6C1486939A42D7FFE69BCD9F3FDAA86C743433 $73955E6A69BA5E0827F48206CAD78C045BBE8873 $8DBA9ADCA5B3A3AB6D2B4F88AC2F96614D33DAB3 $B29E3E30443F897F48B86765F1BC1DB917F5DF46 $CD642E7E722979580B6D631697772C0B72BCF25C $D9E7B6A73C8278274081B77D373ECCE4552E75FB $F2515315FE0DB7456194CABC503B526B49951415
769
family-ids ed25519:b54cKgML0ykRyhdIRcq1xtW19iEVsMYnGNbdY+vvcas
770
id ed25519 /MU/FVKRGcZAy8XFnzLS6Dgcg6s1VpYeFjkwb6+CVhw
771
";
772

            
773
        let md = parse2::parse_netdoc::<Microdesc>(&ParseInput::new(MICRODESC, "")).unwrap();
774
        assert_eq!(
775
            md.family_ids,
776
            RelayFamilyIds::from_iter(iter::once(RelayFamilyId::Ed25519(
777
                Ed25519Identity::from_base64("b54cKgML0ykRyhdIRcq1xtW19iEVsMYnGNbdY+vvcas")
778
                    .unwrap()
779
            )))
780
        );
781
    }
782
}