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 = "parse2")]
37
use crate::parse2::ItemObjectParseable;
38

            
39
#[cfg(feature = "build_docs")]
40
mod build;
41

            
42
#[cfg(feature = "build_docs")]
43
pub use build::MicrodescBuilder;
44

            
45
/// Length of a router microdescriptor digest
46
pub const DOC_DIGEST_LEN: usize = 32;
47

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

            
58
/// The digest of a microdescriptor as used in microdesc consensuses
59
pub type MdDigest = [u8; DOC_DIGEST_LEN];
60

            
61
/// A single microdescriptor.
62
///
63
/// <https://spec.torproject.org/dir-spec/computing-microdescriptors.html>
64
#[derive(Clone, Debug, Deftly, PartialEq, Eq)]
65
#[cfg_attr(feature = "parse2", derive_deftly(NetdocParseable))]
66
#[non_exhaustive]
67
pub struct Microdesc {
68
    /// The legacy onion key, whose object is optional but whose item serves
69
    /// as the intro line for these kind of descriptors.
70
    ///
71
    /// Let's keep this private for now to prevent interfacing applications
72
    /// from generating microdesc's with an onion-key; they are not necessary
73
    /// anymore and just waste space.
74
    onion_key: OnionKeyIntro,
75

            
76
    /// Public key used for the ntor circuit extension protocol.
77
    #[cfg_attr(feature = "parse2", deftly(netdoc(single_arg)))]
78
    pub ntor_onion_key: Curve25519Public,
79

            
80
    /// Declared family for this relay.
81
    #[cfg_attr(feature = "parse2", deftly(netdoc(default)))]
82
    pub family: Arc<RelayFamily>,
83

            
84
    /// Family identities for this relay.
85
    #[cfg_attr(feature = "parse2", deftly(netdoc(default)))]
86
    pub family_ids: RelayFamilyIds,
87

            
88
    /// List of IPv4 ports to which this relay will exit
89
    #[cfg_attr(feature = "parse2", deftly(netdoc(keyword = "p", default)))]
90
    pub ipv4_policy: Arc<PortPolicy>,
91

            
92
    /// List of IPv6 ports to which this relay will exit
93
    #[cfg_attr(feature = "parse2", deftly(netdoc(keyword = "p6", default)))]
94
    pub ipv6_policy: Arc<PortPolicy>,
95

            
96
    /// Ed25519 identity for this relay
97
    // TODO SPEC: Set this to "exactly once".
98
    #[cfg_attr(
99
        feature = "parse2",
100
        deftly(netdoc(keyword = "id", with = "Ed25519IdentityLine"))
101
    )]
102
    pub ed25519_id: Ed25519IdentityLine,
103

            
104
    // addr is obsolete and doesn't go here any more
105
    // pr is obsolete and doesn't go here any more.
106
    /// The SHA256 digest of the text of this microdescriptor.  This
107
    /// value is used to identify the microdescriptor when downloading
108
    /// it, and when listing it in a consensus document.
109
    // TODO: maybe this belongs somewhere else. Once it's used to store
110
    // correlate the microdesc to a consensus, it's never used again.
111
    #[cfg_attr(feature = "parse2", deftly(netdoc(skip)))]
112
    pub sha256: MdDigest,
113
}
114

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

            
132
    /// Return the sha256 digest of this microdesc.
133
71603691
    pub fn digest(&self) -> &MdDigest {
134
71603691
        &self.sha256
135
71603691
    }
136
    /// Return the ntor onion key for this microdesc
137
1984695
    pub fn ntor_key(&self) -> &curve25519::PublicKey {
138
1984695
        &self.ntor_onion_key.0
139
1984695
    }
140
    /// Return the ipv4 exit policy for this microdesc
141
46037135
    pub fn ipv4_policy(&self) -> &Arc<PortPolicy> {
142
46037135
        &self.ipv4_policy
143
46037135
    }
144
    /// Return the ipv6 exit policy for this microdesc
145
6410095
    pub fn ipv6_policy(&self) -> &Arc<PortPolicy> {
146
6410095
        &self.ipv6_policy
147
6410095
    }
148
    /// Return the relay family for this microdesc
149
57605229
    pub fn family(&self) -> &RelayFamily {
150
57605229
        self.family.as_ref()
151
57605229
    }
152
    /// Return the ed25519 identity for this microdesc, if its
153
    /// Ed25519 identity is well-formed.
154
167508624
    pub fn ed25519_id(&self) -> &ed25519::Ed25519Identity {
155
167508624
        &self.ed25519_id.pk.0
156
167508624
    }
157
    /// Return a list of family ids for this microdesc.
158
109694424
    pub fn family_ids(&self) -> &[RelayFamilyId] {
159
109694424
        self.family_ids.as_ref()
160
109694424
    }
161
}
162

            
163
/// Intro line for a [`Microdesc`].
164
///
165
/// The object (the onion key) is deprecated and optional, but the item itself
166
/// must be present, because it is used to mark the start of the netdoc.
167
#[derive(Debug, Clone, Default, Deftly, PartialEq, Eq)]
168
#[cfg_attr(feature = "parse2", derive_deftly(ItemValueParseable))]
169
struct OnionKeyIntro(
170
    #[cfg_attr(feature = "parse2", deftly(netdoc(object)))] Option<rsa::PublicKey>,
171
);
172

            
173
/// A microdescriptor annotated with additional data
174
///
175
/// TODO: rename this.
176
#[allow(dead_code)]
177
#[derive(Clone, Debug)]
178
pub struct AnnotatedMicrodesc {
179
    /// The microdescriptor
180
    md: Microdesc,
181
    /// The annotations for the microdescriptor
182
    ann: MicrodescAnnotation,
183
    /// Where did we find the microdescriptor with the originally parsed
184
    /// string?
185
    location: Option<Extent>,
186
}
187

            
188
impl AnnotatedMicrodesc {
189
    /// Consume this annotated microdesc and discard its annotations.
190
371
    pub fn into_microdesc(self) -> Microdesc {
191
371
        self.md
192
371
    }
193

            
194
    /// Return a reference to the microdescriptor within this annotated
195
    /// microdescriptor.
196
18
    pub fn md(&self) -> &Microdesc {
197
18
        &self.md
198
18
    }
199

            
200
    /// If this Microdesc was parsed from `s`, return its original text.
201
371
    pub fn within<'a>(&self, s: &'a str) -> Option<&'a str> {
202
378
        self.location.as_ref().and_then(|ext| ext.reconstruct(s))
203
371
    }
204
}
205

            
206
decl_keyword! {
207
    /// Keyword type for recognized objects in microdescriptors.
208
    MicrodescKwd {
209
        annotation "@last-listed" => ANN_LAST_LISTED,
210
        "onion-key" => ONION_KEY,
211
        "ntor-onion-key" => NTOR_ONION_KEY,
212
        "family" => FAMILY,
213
        "family-ids" => FAMILY_IDS,
214
        "p" => P,
215
        "p6" => P6,
216
        "id" => ID,
217
    }
218
}
219

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

            
236
55
    let mut rules = SectionRules::builder();
237
55
    rules.add(ONION_KEY.rule().required().no_args().obj_optional());
238
55
    rules.add(NTOR_ONION_KEY.rule().required().args(1..));
239
55
    rules.add(FAMILY.rule().args(1..));
240
55
    rules.add(FAMILY_IDS.rule().args(0..));
241
55
    rules.add(P.rule().args(2..));
242
55
    rules.add(P6.rule().args(2..));
243
55
    rules.add(ID.rule().may_repeat().args(2..));
244
55
    rules.add(UNRECOGNIZED.rule().may_repeat().obj_optional());
245
55
    rules.build()
246
55
});
247

            
248
impl MicrodescAnnotation {
249
    /// Extract a (possibly empty) microdescriptor annotation from a
250
    /// reader.
251
    #[allow(dead_code)]
252
14
    fn parse_from_reader(
253
14
        reader: &mut NetDocReader<'_, MicrodescKwd>,
254
14
    ) -> Result<MicrodescAnnotation> {
255
        use MicrodescKwd::*;
256

            
257
29
        let mut items = reader.pause_at(|item| item.is_ok_with_non_annotation());
258
14
        let body = MICRODESC_ANNOTATIONS.parse(&mut items)?;
259

            
260
14
        let last_listed = match body.get(ANN_LAST_LISTED) {
261
6
            None => None,
262
8
            Some(item) => Some(item.args_as_str().parse::<Iso8601TimeSp>()?.into()),
263
        };
264

            
265
14
        Ok(MicrodescAnnotation { last_listed })
266
14
    }
267
}
268

            
269
impl Microdesc {
270
    /// Parse a string into a new microdescriptor.
271
63
    pub fn parse(s: &str) -> Result<Microdesc> {
272
63
        let mut items = crate::parse::tokenize::NetDocReader::new(s)?;
273
66
        let (result, _) = Self::parse_from_reader(&mut items).map_err(|e| e.within(s))?;
274
57
        items.should_be_exhausted()?;
275
57
        Ok(result)
276
63
    }
277

            
278
    /// Extract a single microdescriptor from a NetDocReader.
279
452
    fn parse_from_reader(
280
452
        reader: &mut NetDocReader<'_, MicrodescKwd>,
281
452
    ) -> Result<(Microdesc, Option<Extent>)> {
282
        use MicrodescKwd::*;
283
452
        let s = reader.str();
284

            
285
452
        let mut first_onion_key = true;
286
        // We'll pause at the next annotation, or at the _second_ onion key.
287
1799
        let mut items = reader.pause_at(|item| match item {
288
            Err(_) => false,
289
1777
            Ok(item) => {
290
1777
                item.kwd().is_annotation()
291
1771
                    || if item.kwd() == ONION_KEY {
292
723
                        let was_first = first_onion_key;
293
723
                        first_onion_key = false;
294
723
                        !was_first
295
                    } else {
296
1048
                        false
297
                    }
298
            }
299
1777
        });
300

            
301
452
        let body = MICRODESC_RULES.parse(&mut items)?;
302

            
303
        // We have to start with onion-key
304
448
        let start_pos = {
305
            // unwrap here is safe because parsing would have failed
306
            // had there not been at least one item.
307
            #[allow(clippy::unwrap_used)]
308
452
            let first = body.first_item().unwrap();
309
452
            if first.kwd() != ONION_KEY {
310
4
                return Err(EK::WrongStartingToken
311
4
                    .with_msg(first.kwd_str().to_string())
312
4
                    .at_pos(first.pos()));
313
448
            }
314
            // Unwrap is safe here because we are parsing these strings from s
315
            #[allow(clippy::unwrap_used)]
316
448
            util::str::str_offset(s, first.kwd_str()).unwrap()
317
        };
318

            
319
        // Legacy (tap) onion key.  We parse this to make sure it's well-formed,
320
        // but then we discard it immediately, since we never want to use it.
321
        //
322
        // In microdescriptors, the ONION_KEY field is mandatory, but its
323
        // associated object is optional.
324
        {
325
448
            let tok = body.required(ONION_KEY)?;
326
448
            if tok.has_obj() {
327
442
                let _: rsa::PublicKey = tok
328
442
                    .parse_obj::<RsaPublicParse1Helper>("RSA PUBLIC KEY")?
329
442
                    .check_len_eq(1024)?
330
442
                    .check_exponent(65537)?
331
442
                    .into();
332
6
            }
333
        }
334

            
335
        // Ntor onion key
336
448
        let ntor_onion_key = body
337
448
            .required(NTOR_ONION_KEY)?
338
448
            .parse_arg::<Curve25519Public>(0)?;
339

            
340
        // family
341
        //
342
        // (We don't need to add the relay's own ID to this family, as we do in
343
        // RouterDescs: the authorities already took care of that for us.)
344
448
        let family = body
345
448
            .maybe(FAMILY)
346
448
            .parse_args_as_str::<RelayFamily>()?
347
448
            .unwrap_or_else(RelayFamily::new)
348
448
            .intern();
349

            
350
        // Family ids (happy families case).
351
448
        let family_ids = body
352
448
            .maybe(FAMILY_IDS)
353
448
            .args_as_str()
354
448
            .unwrap_or("")
355
448
            .split_ascii_whitespace()
356
448
            .map(RelayFamilyId::from_str)
357
448
            .collect::<Result<RelayFamilyIds>>()?;
358

            
359
        // exit policies.
360
448
        let ipv4_policy = body
361
448
            .maybe(P)
362
448
            .parse_args_as_str::<PortPolicy>()?
363
448
            .unwrap_or_else(PortPolicy::new_reject_all);
364
448
        let ipv6_policy = body
365
448
            .maybe(P6)
366
448
            .parse_args_as_str::<PortPolicy>()?
367
446
            .unwrap_or_else(PortPolicy::new_reject_all);
368

            
369
        // ed25519 identity
370
442
        let ed25519_id = {
371
446
            let id_tok = body
372
446
                .slice(ID)
373
446
                .iter()
374
465
                .find(|item| item.arg(0) == Some("ed25519"));
375
446
            match id_tok {
376
                None => {
377
4
                    return Err(EK::MissingToken.with_msg("id ed25519"));
378
                }
379
442
                Some(tok) => Ed25519IdentityLine {
380
442
                    alg: Ed25519AlgorithmString::Ed25519,
381
442
                    pk: tok.parse_arg::<Ed25519Public>(1)?,
382
                },
383
            }
384
        };
385

            
386
442
        let end_pos = {
387
            // unwrap here is safe because parsing would have failed
388
            // had there not been at least one item.
389
            #[allow(clippy::unwrap_used)]
390
442
            let last_item = body.last_item().unwrap();
391
442
            last_item.offset_after(s).ok_or_else(|| {
392
                Error::from(internal!("last item was not within source string"))
393
                    .at_pos(last_item.end_pos())
394
            })?
395
        };
396

            
397
442
        let text = &s[start_pos..end_pos];
398
442
        let sha256 = d::Sha256::digest(text.as_bytes()).into();
399

            
400
442
        let location = Extent::new(s, text);
401

            
402
442
        let md = Microdesc {
403
442
            onion_key: Default::default(),
404
442
            sha256,
405
442
            ntor_onion_key,
406
442
            family,
407
442
            ipv4_policy: ipv4_policy.intern(),
408
442
            ipv6_policy: ipv6_policy.intern(),
409
442
            ed25519_id,
410
442
            family_ids,
411
442
        };
412
442
        Ok((md, location))
413
452
    }
414
}
415

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

            
445
/// An iterator that parses one or more (possibly annotated)
446
/// microdescriptors from a string.
447
#[derive(Debug)]
448
pub struct MicrodescReader<'a> {
449
    /// True if we accept annotations; false otherwise.
450
    annotated: bool,
451
    /// An underlying reader to give us Items for the microdescriptors
452
    reader: NetDocReader<'a, MicrodescKwd>,
453
}
454

            
455
impl<'a> MicrodescReader<'a> {
456
    /// Construct a MicrodescReader to take microdescriptors from a string
457
    /// 's'.
458
112
    pub fn new(s: &'a str, allow: &AllowAnnotations) -> Result<Self> {
459
112
        let reader = NetDocReader::new(s)?;
460
112
        let annotated = allow == &AllowAnnotations::AnnotationsAllowed;
461
112
        Ok(MicrodescReader { annotated, reader })
462
112
    }
463

            
464
    /// If we're annotated, parse an annotation from the reader. Otherwise
465
    /// return a default annotation.
466
389
    fn take_annotation(&mut self) -> Result<MicrodescAnnotation> {
467
389
        if self.annotated {
468
14
            MicrodescAnnotation::parse_from_reader(&mut self.reader)
469
        } else {
470
375
            Ok(MicrodescAnnotation::default())
471
        }
472
389
    }
473

            
474
    /// Parse a (possibly annotated) microdescriptor from the reader.
475
    ///
476
    /// On error, parsing stops after the first failure.
477
389
    fn take_annotated_microdesc_raw(&mut self) -> Result<AnnotatedMicrodesc> {
478
389
        let ann = self.take_annotation()?;
479
389
        let (md, location) = Microdesc::parse_from_reader(&mut self.reader)?;
480
385
        Ok(AnnotatedMicrodesc { md, ann, location })
481
389
    }
482

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

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

            
511
        Some(
512
389
            self.take_annotated_microdesc()
513
391
                .map_err(|e| e.within(self.reader.str())),
514
        )
515
501
    }
516
}
517

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

            
540
    fn read_bad(fname: &str) -> String {
541
        use std::fs;
542
        use std::path::PathBuf;
543
        let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
544
        path.push("testdata");
545
        path.push("bad-mds");
546
        path.push(fname);
547

            
548
        fs::read_to_string(path).unwrap()
549
    }
550

            
551
    #[test]
552
    fn parse_single() -> Result<()> {
553
        let _md = Microdesc::parse(TESTDATA)?;
554
        Ok(())
555
    }
556

            
557
    #[test]
558
    fn parse_no_tap_key() -> Result<()> {
559
        let _md = Microdesc::parse(TESTDATA3)?;
560
        Ok(())
561
    }
562

            
563
    #[test]
564
    fn parse_multi() -> Result<()> {
565
        use humantime::parse_rfc3339;
566
        let mds: Result<Vec<_>> =
567
            MicrodescReader::new(TESTDATA2, &AllowAnnotations::AnnotationsAllowed)?.collect();
568
        let mds = mds?;
569
        assert_eq!(mds.len(), 4);
570

            
571
        assert_eq!(
572
            mds[0].ann.last_listed.unwrap(),
573
            parse_rfc3339("2020-01-27T18:52:09Z").unwrap()
574
        );
575
        assert_eq!(
576
            mds[0].md().digest(),
577
            &hex!("38c71329a87098cb341c46c9c62bd646622b4445f7eb985a0e6adb23a22ccf4f")
578
        );
579
        assert_eq!(
580
            mds[0].md().ntor_key().as_bytes(),
581
            &hex!("5e895d65304a3a1894616660143f7af5757fe08bc18045c7855ee8debb9e6c47")
582
        );
583
        assert!(mds[0].md().ipv4_policy().allows_port(993));
584
        assert!(mds[0].md().ipv6_policy().allows_port(993));
585
        assert!(!mds[0].md().ipv4_policy().allows_port(25));
586
        assert!(!mds[0].md().ipv6_policy().allows_port(25));
587
        assert_eq!(
588
            mds[0].md().ed25519_id().as_bytes(),
589
            &hex!("2d85fdc88e6c1bcfb46897fca1dba6d1354f93261d68a79e0b5bc170dd923084")
590
        );
591

            
592
        Ok(())
593
    }
594

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

            
615
        Ok(())
616
    }
617

            
618
    #[test]
619
    fn test_bad() {
620
        use crate::Pos;
621
        use crate::types::policy::PolicyError;
622
        fn check(fname: &str, e: &Error) {
623
            let content = read_bad(fname);
624
            let res = Microdesc::parse(&content);
625
            assert!(res.is_err());
626
            assert_eq!(&res.err().unwrap(), e);
627
        }
628

            
629
        check(
630
            "wrong-start",
631
            &EK::WrongStartingToken
632
                .with_msg("family")
633
                .at_pos(Pos::from_line(1, 1)),
634
        );
635
        check(
636
            "bogus-policy",
637
            &EK::BadPolicy
638
                .at_pos(Pos::from_line(9, 1))
639
                .with_source(PolicyError::InvalidPort),
640
        );
641
        check("wrong-id", &EK::MissingToken.with_msg("id ed25519"));
642
    }
643

            
644
    #[test]
645
    fn test_recover() -> Result<()> {
646
        let mut data = read_bad("wrong-start");
647
        data += TESTDATA;
648
        data += &read_bad("wrong-id");
649

            
650
        let res: Vec<Result<_>> =
651
            MicrodescReader::new(&data, &AllowAnnotations::AnnotationsAllowed)?.collect();
652

            
653
        assert_eq!(res.len(), 3);
654
        assert!(res[0].is_err());
655
        assert!(res[1].is_ok());
656
        assert!(res[2].is_err());
657
        Ok(())
658
    }
659

            
660
    /// Checks whether parse2 works on [`Microdesc`].
661
    ///
662
    /// Certain values such as public keys are hardcoded and can be simply
663
    /// replaced by a copy and paste in the case one replaces the testdata2
664
    /// vector's in the future.
665
    #[test]
666
    #[cfg(feature = "parse2")]
667
    fn parse2() {
668
        use tor_llcrypto::pk::ed25519::Ed25519Identity;
669

            
670
        use crate::parse2;
671

            
672
        let md = include_str!("../../testdata2/cached-microdescs.new");
673
        let mds = parse2::parse_netdoc_multiple::<Microdesc>(&parse2::ParseInput::new(
674
            md,
675
            "../../testdata2/cached-microdescs.new",
676
        ))
677
        .unwrap();
678

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

            
715
    /// Manual test for happy families.
716
    // TODO: This should be included in testdata2/ but that would require the
717
    // chutney/shadow integration test to actually do families at all.
718
    #[test]
719
    #[cfg(feature = "parse2")]
720
    fn parse2_happy_family() {
721
        use tor_llcrypto::pk::ed25519::Ed25519Identity;
722

            
723
        use crate::parse2::{self, ParseInput};
724
        use std::iter;
725

            
726
        // A microdescriptor taken from the wild containing happy families.
727
        const MICRODESC: &str = "\
728
onion-key
729
-----BEGIN RSA PUBLIC KEY-----
730
MIGJAoGBAMk57F7qGHVadBJ6m4028w13I1Qk67Ee0JU88w7NObKBph3DQYjgYs4e
731
eUdiW4Gdsx8w/xOuK0foCo0O8Iqq5MXtVcpUP/N+5uB7SVvGdJFsKw21KdIc6v8g
732
ACZAijw5ZPOdhLbyLQyFHNV8zXUov1dlx/Fb9M3lPMVevnDbuKM5AgMBAAE=
733
-----END RSA PUBLIC KEY-----
734
ntor-onion-key fhhP23UKD4L2jehA5gopAo5b6NSoB+kZN5Q4ULv3Zww
735
family $4CFFD403DAB89A689F3FDB80B5366E46D879E736 $4D6C1486939A42D7FFE69BCD9F3FDAA86C743433 $73955E6A69BA5E0827F48206CAD78C045BBE8873 $8DBA9ADCA5B3A3AB6D2B4F88AC2F96614D33DAB3 $B29E3E30443F897F48B86765F1BC1DB917F5DF46 $CD642E7E722979580B6D631697772C0B72BCF25C $D9E7B6A73C8278274081B77D373ECCE4552E75FB $F2515315FE0DB7456194CABC503B526B49951415
736
family-ids ed25519:b54cKgML0ykRyhdIRcq1xtW19iEVsMYnGNbdY+vvcas
737
id ed25519 /MU/FVKRGcZAy8XFnzLS6Dgcg6s1VpYeFjkwb6+CVhw
738
";
739

            
740
        let md = parse2::parse_netdoc::<Microdesc>(&ParseInput::new(MICRODESC, "")).unwrap();
741
        assert_eq!(
742
            md.family_ids,
743
            RelayFamilyIds::from_iter(iter::once(RelayFamilyId::Ed25519(
744
                Ed25519Identity::from_base64("b54cKgML0ykRyhdIRcq1xtW19iEVsMYnGNbdY+vvcas")
745
                    .unwrap()
746
            )))
747
        );
748
    }
749
}