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_error::internal;
26
use tor_llcrypto::d;
27
use tor_llcrypto::pk::{curve25519, ed25519, rsa};
28

            
29
use derive_deftly::Deftly;
30
use digest::Digest;
31
use std::str::FromStr as _;
32
use std::sync::Arc;
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(NetdocParseable)]
63
#[non_exhaustive]
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
    ///
68
    /// Let's keep this private for now to prevent interfacing applications
69
    /// from generating microdesc's with an onion-key; they are not necessary
70
    /// anymore and just waste space.
71
    onion_key: OnionKeyIntro,
72

            
73
    /// Public key used for the ntor circuit extension protocol.
74
    #[deftly(netdoc(single_arg))]
75
    pub ntor_onion_key: Curve25519Public,
76

            
77
    /// Declared family for this relay.
78
    #[deftly(netdoc(default))]
79
    pub family: Arc<RelayFamily>,
80

            
81
    /// Family identities for this relay.
82
    #[deftly(netdoc(default))]
83
    pub family_ids: RelayFamilyIds,
84

            
85
    /// List of IPv4 ports to which this relay will exit
86
    #[deftly(netdoc(keyword = "p", default))]
87
    pub ipv4_policy: Arc<PortPolicy>,
88

            
89
    /// List of IPv6 ports to which this relay will exit
90
    #[deftly(netdoc(keyword = "p6", default))]
91
    pub ipv6_policy: Arc<PortPolicy>,
92

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

            
98
    // addr is obsolete and doesn't go here any more
99
    // pr is obsolete and doesn't go here any more.
100
    /// The SHA256 digest of the text of this microdescriptor.  This
101
    /// value is used to identify the microdescriptor when downloading
102
    /// it, and when listing it in a consensus document.
103
    // TODO: maybe this belongs somewhere else. Once it's used to store
104
    // correlate the microdesc to a consensus, it's never used again.
105
    #[deftly(netdoc(skip))]
106
    pub sha256: MdDigest,
107
}
108

            
109
impl Microdesc {
110
    /// Create a new MicrodescBuilder that can be used to construct
111
    /// microdescriptors.
112
    ///
113
    /// This function is only available when the crate is built with the
114
    /// `build_docs` feature.
115
    ///
116
    /// # Limitations
117
    ///
118
    /// The generated microdescriptors cannot yet be encoded, and do
119
    /// not yet have correct sha256 digests. As such they are only
120
    /// useful for testing.
121
    #[cfg(feature = "build_docs")]
122
477366
    pub fn builder() -> MicrodescBuilder {
123
477366
        MicrodescBuilder::new()
124
477366
    }
125

            
126
    /// Return the sha256 digest of this microdesc.
127
72966692
    pub fn digest(&self) -> &MdDigest {
128
72966692
        &self.sha256
129
72966692
    }
130
    /// Return the ntor onion key for this microdesc
131
2026624
    pub fn ntor_key(&self) -> &curve25519::PublicKey {
132
2026624
        &self.ntor_onion_key.0
133
2026624
    }
134
    /// Return the ipv4 exit policy for this microdesc
135
46905760
    pub fn ipv4_policy(&self) -> &Arc<PortPolicy> {
136
46905760
        &self.ipv4_policy
137
46905760
    }
138
    /// Return the ipv6 exit policy for this microdesc
139
6531040
    pub fn ipv6_policy(&self) -> &Arc<PortPolicy> {
140
6531040
        &self.ipv6_policy
141
6531040
    }
142
    /// Return the relay family for this microdesc
143
58634556
    pub fn family(&self) -> &RelayFamily {
144
58634556
        self.family.as_ref()
145
58634556
    }
146
    /// Return the ed25519 identity for this microdesc, if its
147
    /// Ed25519 identity is well-formed.
148
170699458
    pub fn ed25519_id(&self) -> &ed25519::Ed25519Identity {
149
170699458
        &self.ed25519_id.pk.0
150
170699458
    }
151
    /// Return a list of family ids for this microdesc.
152
112094394
    pub fn family_ids(&self) -> &[RelayFamilyId] {
153
112094394
        self.family_ids.as_ref()
154
112094394
    }
155
}
156

            
157
/// Intro line for a [`Microdesc`].
158
///
159
/// The object (the onion key) is deprecated and optional, but the item itself
160
/// must be present, because it is used to mark the start of the netdoc.
161
#[derive(Debug, Clone, Default, Deftly, PartialEq, Eq)]
162
#[derive_deftly(ItemValueParseable)]
163
struct OnionKeyIntro(#[deftly(netdoc(object))] Option<rsa::PublicKey>);
164

            
165
/// A microdescriptor annotated with additional data
166
///
167
/// TODO: rename this.
168
#[allow(dead_code)]
169
#[derive(Clone, Debug)]
170
pub struct AnnotatedMicrodesc {
171
    /// The microdescriptor
172
    md: Microdesc,
173
    /// The annotations for the microdescriptor
174
    ann: MicrodescAnnotation,
175
    /// Where did we find the microdescriptor with the originally parsed
176
    /// string?
177
    location: Option<Extent>,
178
}
179

            
180
impl AnnotatedMicrodesc {
181
    /// Consume this annotated microdesc and discard its annotations.
182
378
    pub fn into_microdesc(self) -> Microdesc {
183
378
        self.md
184
378
    }
185

            
186
    /// Return a reference to the microdescriptor within this annotated
187
    /// microdescriptor.
188
18
    pub fn md(&self) -> &Microdesc {
189
18
        &self.md
190
18
    }
191

            
192
    /// If this Microdesc was parsed from `s`, return its original text.
193
378
    pub fn within<'a>(&self, s: &'a str) -> Option<&'a str> {
194
385
        self.location.as_ref().and_then(|ext| ext.reconstruct(s))
195
378
    }
196
}
197

            
198
decl_keyword! {
199
    /// Keyword type for recognized objects in microdescriptors.
200
    MicrodescKwd {
201
        annotation "@last-listed" => ANN_LAST_LISTED,
202
        "onion-key" => ONION_KEY,
203
        "ntor-onion-key" => NTOR_ONION_KEY,
204
        "family" => FAMILY,
205
        "family-ids" => FAMILY_IDS,
206
        "p" => P,
207
        "p6" => P6,
208
        "id" => ID,
209
    }
210
}
211

            
212
/// Rules about annotations that can appear before a Microdescriptor
213
2
static MICRODESC_ANNOTATIONS: LazyLock<SectionRules<MicrodescKwd>> = LazyLock::new(|| {
214
    use MicrodescKwd::*;
215
2
    let mut rules = SectionRules::builder();
216
2
    rules.add(ANN_LAST_LISTED.rule().args(1..));
217
2
    rules.add(ANN_UNRECOGNIZED.rule().may_repeat().obj_optional());
218
    // unrecognized annotations are okay; anything else is a bug in this
219
    // context.
220
2
    rules.reject_unrecognized();
221
2
    rules.build()
222
2
});
223
/// Rules about entries that must appear in an Microdesc, and how they must
224
/// be formed.
225
56
static MICRODESC_RULES: LazyLock<SectionRules<MicrodescKwd>> = LazyLock::new(|| {
226
    use MicrodescKwd::*;
227

            
228
56
    let mut rules = SectionRules::builder();
229
56
    rules.add(ONION_KEY.rule().required().no_args().obj_optional());
230
56
    rules.add(NTOR_ONION_KEY.rule().required().args(1..));
231
56
    rules.add(FAMILY.rule().args(1..));
232
56
    rules.add(FAMILY_IDS.rule().args(0..));
233
56
    rules.add(P.rule().args(2..));
234
56
    rules.add(P6.rule().args(2..));
235
56
    rules.add(ID.rule().may_repeat().args(2..));
236
56
    rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
237
56
    rules.build()
238
56
});
239

            
240
impl MicrodescAnnotation {
241
    /// Extract a (possibly empty) microdescriptor annotation from a
242
    /// reader.
243
    #[allow(dead_code)]
244
14
    fn parse_from_reader(
245
14
        reader: &mut NetDocReader<'_, MicrodescKwd>,
246
14
    ) -> Result<MicrodescAnnotation> {
247
        use MicrodescKwd::*;
248

            
249
29
        let mut items = reader.pause_at(|item| item.is_ok_with_non_annotation());
250
14
        let body = MICRODESC_ANNOTATIONS.parse(&mut items)?;
251

            
252
14
        let last_listed = match body.get(ANN_LAST_LISTED) {
253
6
            None => None,
254
8
            Some(item) => Some(item.args_as_str().parse::<Iso8601TimeSp>()?.into()),
255
        };
256

            
257
14
        Ok(MicrodescAnnotation { last_listed })
258
14
    }
259
}
260

            
261
impl Microdesc {
262
    /// Parse a string into a new microdescriptor.
263
64
    pub fn parse(s: &str) -> Result<Microdesc> {
264
64
        let mut items = crate::parse::tokenize::NetDocReader::new(s)?;
265
67
        let (result, _) = Self::parse_from_reader(&mut items).map_err(|e| e.within(s))?;
266
58
        items.should_be_exhausted()?;
267
58
        Ok(result)
268
64
    }
269

            
270
    /// Extract a single microdescriptor from a NetDocReader.
271
460
    fn parse_from_reader(
272
460
        reader: &mut NetDocReader<'_, MicrodescKwd>,
273
460
    ) -> Result<(Microdesc, Option<Extent>)> {
274
        use MicrodescKwd::*;
275
460
        let s = reader.str();
276

            
277
460
        let mut first_onion_key = true;
278
        // We'll pause at the next annotation, or at the _second_ onion key.
279
1830
        let mut items = reader.pause_at(|item| match item {
280
            Err(_) => false,
281
1808
            Ok(item) => {
282
1808
                item.kwd().is_annotation()
283
1802
                    || if item.kwd() == ONION_KEY {
284
736
                        let was_first = first_onion_key;
285
736
                        first_onion_key = false;
286
736
                        !was_first
287
                    } else {
288
1066
                        false
289
                    }
290
            }
291
1808
        });
292

            
293
460
        let body = MICRODESC_RULES.parse(&mut items)?;
294

            
295
        // We have to start with onion-key
296
456
        let start_pos = {
297
            // unwrap here is safe because parsing would have failed
298
            // had there not been at least one item.
299
            #[allow(clippy::unwrap_used)]
300
460
            let first = body.first_item().unwrap();
301
460
            if first.kwd() != ONION_KEY {
302
4
                return Err(EK::WrongStartingToken
303
4
                    .with_msg(first.kwd_str().to_string())
304
4
                    .at_pos(first.pos()));
305
456
            }
306
            // Unwrap is safe here because we are parsing these strings from s
307
            #[allow(clippy::unwrap_used)]
308
456
            util::str::str_offset(s, first.kwd_str()).unwrap()
309
        };
310

            
311
        // Legacy (tap) onion key.  We parse this to make sure it's well-formed,
312
        // but then we discard it immediately, since we never want to use it.
313
        //
314
        // In microdescriptors, the ONION_KEY field is mandatory, but its
315
        // associated object is optional.
316
        {
317
456
            let tok = body.required(ONION_KEY)?;
318
456
            if tok.has_obj() {
319
450
                let _: rsa::PublicKey = tok
320
450
                    .parse_obj::<RsaPublicParse1Helper>("RSA PUBLIC KEY")?
321
450
                    .check_len_eq(1024)?
322
450
                    .check_exponent(65537)?
323
450
                    .into();
324
6
            }
325
        }
326

            
327
        // Ntor onion key
328
456
        let ntor_onion_key = body
329
456
            .required(NTOR_ONION_KEY)?
330
456
            .parse_arg::<Curve25519Public>(0)?;
331

            
332
        // family
333
        //
334
        // (We don't need to add the relay's own ID to this family, as we do in
335
        // RouterDescs: the authorities already took care of that for us.)
336
456
        let family = body
337
456
            .maybe(FAMILY)
338
456
            .parse_args_as_str::<RelayFamily>()?
339
456
            .unwrap_or_else(RelayFamily::new)
340
456
            .intern();
341

            
342
        // Family ids (happy families case).
343
456
        let family_ids = body
344
456
            .maybe(FAMILY_IDS)
345
456
            .args_as_str()
346
456
            .unwrap_or("")
347
456
            .split_ascii_whitespace()
348
456
            .map(RelayFamilyId::from_str)
349
456
            .collect::<Result<RelayFamilyIds>>()?;
350

            
351
        // exit policies.
352
456
        let ipv4_policy = body
353
456
            .maybe(P)
354
456
            .parse_args_as_str::<PortPolicy>()?
355
456
            .unwrap_or_else(PortPolicy::new_reject_all);
356
456
        let ipv6_policy = body
357
456
            .maybe(P6)
358
456
            .parse_args_as_str::<PortPolicy>()?
359
454
            .unwrap_or_else(PortPolicy::new_reject_all);
360

            
361
        // ed25519 identity
362
450
        let ed25519_id = {
363
454
            let id_tok = body
364
454
                .slice(ID)
365
454
                .iter()
366
473
                .find(|item| item.arg(0) == Some("ed25519"));
367
454
            match id_tok {
368
                None => {
369
4
                    return Err(EK::MissingToken.with_msg("id ed25519"));
370
                }
371
450
                Some(tok) => Ed25519IdentityLine {
372
450
                    alg: Ed25519AlgorithmString::Ed25519,
373
450
                    pk: tok.parse_arg::<Ed25519Public>(1)?,
374
                },
375
            }
376
        };
377

            
378
450
        let end_pos = {
379
            // unwrap here is safe because parsing would have failed
380
            // had there not been at least one item.
381
            #[allow(clippy::unwrap_used)]
382
450
            let last_item = body.last_item().unwrap();
383
450
            last_item.offset_after(s).ok_or_else(|| {
384
                Error::from(internal!("last item was not within source string"))
385
                    .at_pos(last_item.end_pos())
386
            })?
387
        };
388

            
389
450
        let text = &s[start_pos..end_pos];
390
450
        let sha256 = d::Sha256::digest(text.as_bytes()).into();
391

            
392
450
        let location = Extent::new(s, text);
393

            
394
450
        let md = Microdesc {
395
450
            onion_key: Default::default(),
396
450
            sha256,
397
450
            ntor_onion_key,
398
450
            family,
399
450
            ipv4_policy: ipv4_policy.intern(),
400
450
            ipv6_policy: ipv6_policy.intern(),
401
450
            ed25519_id,
402
450
            family_ids,
403
450
        };
404
450
        Ok((md, location))
405
460
    }
406
}
407

            
408
/// Consume tokens from 'reader' until the next token is the beginning
409
/// of a microdescriptor: an annotation or an ONION_KEY.  If no such
410
/// token exists, advance to the end of the reader.
411
4
fn advance_to_next_microdesc(reader: &mut NetDocReader<'_, MicrodescKwd>, annotated: bool) {
412
    use MicrodescKwd::*;
413
    loop {
414
4
        let item = reader.peek();
415
2
        match item {
416
2
            Some(Ok(t)) => {
417
2
                let kwd = t.kwd();
418
2
                if (annotated && kwd.is_annotation()) || kwd == ONION_KEY {
419
2
                    return;
420
                }
421
            }
422
            Some(Err(_)) => {
423
                // We skip over broken tokens here.
424
                //
425
                // (This case can't happen in practice, since if there had been
426
                // any error tokens, they would have been handled as part of
427
                // handling the previous microdesc.)
428
            }
429
            None => {
430
2
                return;
431
            }
432
        };
433
        let _ = reader.next();
434
    }
435
4
}
436

            
437
/// An iterator that parses one or more (possibly annotated)
438
/// microdescriptors from a string.
439
#[derive(Debug)]
440
pub struct MicrodescReader<'a> {
441
    /// True if we accept annotations; false otherwise.
442
    annotated: bool,
443
    /// An underlying reader to give us Items for the microdescriptors
444
    reader: NetDocReader<'a, MicrodescKwd>,
445
}
446

            
447
impl<'a> MicrodescReader<'a> {
448
    /// Construct a MicrodescReader to take microdescriptors from a string
449
    /// 's'.
450
114
    pub fn new(s: &'a str, allow: &AllowAnnotations) -> Result<Self> {
451
114
        let reader = NetDocReader::new(s)?;
452
114
        let annotated = allow == &AllowAnnotations::AnnotationsAllowed;
453
114
        Ok(MicrodescReader { annotated, reader })
454
114
    }
455

            
456
    /// If we're annotated, parse an annotation from the reader. Otherwise
457
    /// return a default annotation.
458
396
    fn take_annotation(&mut self) -> Result<MicrodescAnnotation> {
459
396
        if self.annotated {
460
14
            MicrodescAnnotation::parse_from_reader(&mut self.reader)
461
        } else {
462
382
            Ok(MicrodescAnnotation::default())
463
        }
464
396
    }
465

            
466
    /// Parse a (possibly annotated) microdescriptor from the reader.
467
    ///
468
    /// On error, parsing stops after the first failure.
469
396
    fn take_annotated_microdesc_raw(&mut self) -> Result<AnnotatedMicrodesc> {
470
396
        let ann = self.take_annotation()?;
471
396
        let (md, location) = Microdesc::parse_from_reader(&mut self.reader)?;
472
392
        Ok(AnnotatedMicrodesc { md, ann, location })
473
396
    }
474

            
475
    /// Parse a (possibly annotated) microdescriptor from the reader.
476
    ///
477
    /// On error, advance the reader to the start of the next microdescriptor.
478
396
    fn take_annotated_microdesc(&mut self) -> Result<AnnotatedMicrodesc> {
479
396
        let pos_orig = self.reader.pos();
480
396
        let result = self.take_annotated_microdesc_raw();
481
396
        if result.is_err() {
482
4
            if self.reader.pos() == pos_orig {
483
                // No tokens were consumed from the reader.  We need to
484
                // drop at least one token to ensure we aren't looping.
485
                //
486
                // (This might not be able to happen, but it's easier to
487
                // explicitly catch this case than it is to prove that
488
                // it's impossible.)
489
                let _ = self.reader.next();
490
4
            }
491
4
            advance_to_next_microdesc(&mut self.reader, self.annotated);
492
392
        }
493
396
        result
494
396
    }
495
}
496

            
497
impl<'a> Iterator for MicrodescReader<'a> {
498
    type Item = Result<AnnotatedMicrodesc>;
499
510
    fn next(&mut self) -> Option<Self::Item> {
500
        // If there is no next token, we're at the end.
501
510
        self.reader.peek()?;
502

            
503
        Some(
504
396
            self.take_annotated_microdesc()
505
398
                .map_err(|e| e.within(self.reader.str())),
506
        )
507
510
    }
508
}
509

            
510
#[cfg(test)]
511
mod test {
512
    // @@ begin test lint list maintained by maint/add_warning @@
513
    #![allow(clippy::bool_assert_comparison)]
514
    #![allow(clippy::clone_on_copy)]
515
    #![allow(clippy::dbg_macro)]
516
    #![allow(clippy::mixed_attributes_style)]
517
    #![allow(clippy::print_stderr)]
518
    #![allow(clippy::print_stdout)]
519
    #![allow(clippy::single_char_pattern)]
520
    #![allow(clippy::unwrap_used)]
521
    #![allow(clippy::unchecked_time_subtraction)]
522
    #![allow(clippy::useless_vec)]
523
    #![allow(clippy::needless_pass_by_value)]
524
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
525
    use super::*;
526
    use hex_literal::hex;
527
    const TESTDATA: &str = include_str!("../../testdata/microdesc1.txt");
528
    const TESTDATA2: &str = include_str!("../../testdata/microdesc2.txt");
529
    const TESTDATA3: &str = include_str!("../../testdata/microdesc3.txt");
530
    const TESTDATA4: &str = include_str!("../../testdata/microdesc4.txt");
531

            
532
    fn read_bad(fname: &str) -> String {
533
        use std::fs;
534
        use std::path::PathBuf;
535
        let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
536
        path.push("testdata");
537
        path.push("bad-mds");
538
        path.push(fname);
539

            
540
        fs::read_to_string(path).unwrap()
541
    }
542

            
543
    #[test]
544
    fn parse_single() -> Result<()> {
545
        let _md = Microdesc::parse(TESTDATA)?;
546
        Ok(())
547
    }
548

            
549
    #[test]
550
    fn parse_no_tap_key() -> Result<()> {
551
        let _md = Microdesc::parse(TESTDATA3)?;
552
        Ok(())
553
    }
554

            
555
    #[test]
556
    fn parse_multi() -> Result<()> {
557
        use humantime::parse_rfc3339;
558
        let mds: Result<Vec<_>> =
559
            MicrodescReader::new(TESTDATA2, &AllowAnnotations::AnnotationsAllowed)?.collect();
560
        let mds = mds?;
561
        assert_eq!(mds.len(), 4);
562

            
563
        assert_eq!(
564
            mds[0].ann.last_listed.unwrap(),
565
            parse_rfc3339("2020-01-27T18:52:09Z").unwrap()
566
        );
567
        assert_eq!(
568
            mds[0].md().digest(),
569
            &hex!("38c71329a87098cb341c46c9c62bd646622b4445f7eb985a0e6adb23a22ccf4f")
570
        );
571
        assert_eq!(
572
            mds[0].md().ntor_key().as_bytes(),
573
            &hex!("5e895d65304a3a1894616660143f7af5757fe08bc18045c7855ee8debb9e6c47")
574
        );
575
        assert!(mds[0].md().ipv4_policy().allows_port(993));
576
        assert!(mds[0].md().ipv6_policy().allows_port(993));
577
        assert!(!mds[0].md().ipv4_policy().allows_port(25));
578
        assert!(!mds[0].md().ipv6_policy().allows_port(25));
579
        assert_eq!(
580
            mds[0].md().ed25519_id().as_bytes(),
581
            &hex!("2d85fdc88e6c1bcfb46897fca1dba6d1354f93261d68a79e0b5bc170dd923084")
582
        );
583

            
584
        Ok(())
585
    }
586

            
587
    #[test]
588
    fn parse_family_ids() -> Result<()> {
589
        let mds: Vec<AnnotatedMicrodesc> =
590
            MicrodescReader::new(TESTDATA4, &AllowAnnotations::AnnotationsNotAllowed)?
591
                .collect::<Result<_>>()?;
592
        assert_eq!(mds.len(), 2);
593
        let md0 = mds[0].md();
594
        let md1 = mds[1].md();
595
        assert!(md0.family_ids().is_empty());
596
        assert_eq!(
597
            md1.family_ids(),
598
            &[
599
                "ed25519:dXMgdGhlIHRyaXVtcGguICAgIC1UaG9tYXMgUGFpbmU"
600
                    .parse()
601
                    .unwrap(),
602
                "other:Example".parse().unwrap()
603
            ]
604
        );
605
        assert!(matches!(md1.family_ids()[0], RelayFamilyId::Ed25519(_)));
606

            
607
        Ok(())
608
    }
609

            
610
    #[test]
611
    fn test_bad() {
612
        use crate::Pos;
613
        use crate::types::policy::PolicyError;
614
        fn check(fname: &str, e: &Error) {
615
            let content = read_bad(fname);
616
            let res = Microdesc::parse(&content);
617
            assert!(res.is_err());
618
            assert_eq!(&res.err().unwrap(), e);
619
        }
620

            
621
        check(
622
            "wrong-start",
623
            &EK::WrongStartingToken
624
                .with_msg("family")
625
                .at_pos(Pos::from_line(1, 1)),
626
        );
627
        check(
628
            "bogus-policy",
629
            &EK::BadPolicy
630
                .at_pos(Pos::from_line(9, 1))
631
                .with_source(PolicyError::InvalidPort),
632
        );
633
        check("wrong-id", &EK::MissingToken.with_msg("id ed25519"));
634
    }
635

            
636
    #[test]
637
    fn test_recover() -> Result<()> {
638
        let mut data = read_bad("wrong-start");
639
        data += TESTDATA;
640
        data += &read_bad("wrong-id");
641

            
642
        let res: Vec<Result<_>> =
643
            MicrodescReader::new(&data, &AllowAnnotations::AnnotationsAllowed)?.collect();
644

            
645
        assert_eq!(res.len(), 3);
646
        assert!(res[0].is_err());
647
        assert!(res[1].is_ok());
648
        assert!(res[2].is_err());
649
        Ok(())
650
    }
651

            
652
    /// Checks whether parse2 works on [`Microdesc`].
653
    ///
654
    /// Certain values such as public keys are hardcoded and can be simply
655
    /// replaced by a copy and paste in the case one replaces the testdata2
656
    /// vector's in the future.
657
    #[test]
658
    fn parse2() {
659
        use tor_llcrypto::pk::ed25519::Ed25519Identity;
660

            
661
        use crate::parse2;
662

            
663
        let md = include_str!("../../testdata2/cached-microdescs.new");
664
        let mds = parse2::parse_netdoc_multiple::<Microdesc>(&parse2::ParseInput::new(
665
            md,
666
            "../../testdata2/cached-microdescs.new",
667
        ))
668
        .unwrap();
669

            
670
        assert_eq!(mds.len(), 7);
671
        assert_eq!(
672
            mds[0],
673
            Microdesc {
674
                onion_key: OnionKeyIntro(rsa::PublicKey::from_der(
675
                    pem::parse(
676
                        "
677
-----BEGIN RSA PUBLIC KEY-----
678
MIGJAoGBANF8Zgxp8amY1esYdPj2Ada1ORiVB/A4sgKLQ5ij/wsasO3yjjLcvHRB
679
UJ0mAQWql/nauvjnKUeZFcGm3t7q0v3F9uUsOGTAZ/IKh31UQAm5OS/TJyf8IHky
680
Yl0wCKpUZFHs5CHsajLSfXZKHkwfqRXFEJu9aMtmQdQFfqE9JOJHAgMBAAE=
681
-----END RSA PUBLIC KEY-----
682
                        "
683
                    )
684
                    .unwrap()
685
                    .contents()
686
                )),
687
                sha256: [0; 32],
688
                ntor_onion_key: curve25519::PublicKey::from(<[u8; 32]>::from(
689
                    FixedB64::<32>::from_str("I1S8JfcqPPHWVTxfjq/eGmGiu/OtR+fF0Z86Ge1mq3s")
690
                        .unwrap()
691
                ))
692
                .into(),
693
                family: Default::default(),
694
                ipv4_policy: Default::default(),
695
                ipv6_policy: Default::default(),
696
                ed25519_id: Ed25519Identity::from(<[u8; 32]>::from(
697
                    FixedB64::<32>::from_str("yhO6nETO5AUdvJbLgPnw4mFjozGXWMCqOp30nY6nM8E")
698
                        .unwrap()
699
                ))
700
                .into(),
701
                family_ids: Default::default(),
702
            }
703
        );
704
    }
705

            
706
    /// Manual test for happy families.
707
    // TODO: This should be included in testdata2/ but that would require the
708
    // chutney/shadow integration test to actually do families at all.
709
    #[test]
710
    fn parse2_happy_family() {
711
        use tor_llcrypto::pk::ed25519::Ed25519Identity;
712

            
713
        use crate::parse2::{self, ParseInput};
714
        use std::iter;
715

            
716
        // A microdescriptor taken from the wild containing happy families.
717
        const MICRODESC: &str = "\
718
onion-key
719
-----BEGIN RSA PUBLIC KEY-----
720
MIGJAoGBAMk57F7qGHVadBJ6m4028w13I1Qk67Ee0JU88w7NObKBph3DQYjgYs4e
721
eUdiW4Gdsx8w/xOuK0foCo0O8Iqq5MXtVcpUP/N+5uB7SVvGdJFsKw21KdIc6v8g
722
ACZAijw5ZPOdhLbyLQyFHNV8zXUov1dlx/Fb9M3lPMVevnDbuKM5AgMBAAE=
723
-----END RSA PUBLIC KEY-----
724
ntor-onion-key fhhP23UKD4L2jehA5gopAo5b6NSoB+kZN5Q4ULv3Zww
725
family $4CFFD403DAB89A689F3FDB80B5366E46D879E736 $4D6C1486939A42D7FFE69BCD9F3FDAA86C743433 $73955E6A69BA5E0827F48206CAD78C045BBE8873 $8DBA9ADCA5B3A3AB6D2B4F88AC2F96614D33DAB3 $B29E3E30443F897F48B86765F1BC1DB917F5DF46 $CD642E7E722979580B6D631697772C0B72BCF25C $D9E7B6A73C8278274081B77D373ECCE4552E75FB $F2515315FE0DB7456194CABC503B526B49951415
726
family-ids ed25519:b54cKgML0ykRyhdIRcq1xtW19iEVsMYnGNbdY+vvcas
727
id ed25519 /MU/FVKRGcZAy8XFnzLS6Dgcg6s1VpYeFjkwb6+CVhw
728
";
729

            
730
        let md = parse2::parse_netdoc::<Microdesc>(&ParseInput::new(MICRODESC, "")).unwrap();
731
        assert_eq!(
732
            md.family_ids,
733
            RelayFamilyIds::from_iter(iter::once(RelayFamilyId::Ed25519(
734
                Ed25519Identity::from_base64("b54cKgML0ykRyhdIRcq1xtW19iEVsMYnGNbdY+vvcas")
735
                    .unwrap()
736
            )))
737
        );
738
    }
739
}