1
#![cfg_attr(docsrs, feature(doc_cfg))]
2
#![doc = include_str!("../README.md")]
3
// @@ begin lint list maintained by maint/add_warning @@
4
#![allow(renamed_and_removed_lints)] // @@REMOVE_WHEN(ci_arti_stable)
5
#![allow(unknown_lints)] // @@REMOVE_WHEN(ci_arti_nightly)
6
#![warn(missing_docs)]
7
#![warn(noop_method_call)]
8
#![warn(unreachable_pub)]
9
#![warn(clippy::all)]
10
#![deny(clippy::await_holding_lock)]
11
#![deny(clippy::cargo_common_metadata)]
12
#![deny(clippy::cast_lossless)]
13
#![deny(clippy::checked_conversions)]
14
#![allow(clippy::cognitive_complexity)] // See arti#2556
15
#![deny(clippy::debug_assert_with_mut_call)]
16
#![deny(clippy::exhaustive_enums)]
17
#![deny(clippy::exhaustive_structs)]
18
#![deny(clippy::expl_impl_clone_on_copy)]
19
#![deny(clippy::fallible_impl_from)]
20
#![deny(clippy::implicit_clone)]
21
#![deny(clippy::large_stack_arrays)]
22
#![warn(clippy::manual_ok_or)]
23
#![deny(clippy::missing_docs_in_private_items)]
24
#![warn(clippy::needless_borrow)]
25
#![warn(clippy::needless_pass_by_value)]
26
#![warn(clippy::option_option)]
27
#![warn(clippy::or_fun_call)]
28
#![deny(clippy::print_stderr)]
29
#![deny(clippy::print_stdout)]
30
#![warn(clippy::rc_buffer)]
31
#![deny(clippy::ref_option_ref)]
32
#![warn(clippy::semicolon_if_nothing_returned)]
33
#![warn(clippy::trait_duplication_in_bounds)]
34
#![deny(clippy::unchecked_time_subtraction)]
35
#![deny(clippy::unnecessary_wraps)]
36
#![warn(clippy::unseparated_literal_suffix)]
37
#![deny(clippy::unwrap_used)]
38
#![deny(clippy::mod_module_files)]
39
#![allow(clippy::let_unit_value)] // This can reasonably be done for explicitness
40
#![allow(clippy::uninlined_format_args)]
41
#![allow(clippy::significant_drop_in_scrutinee)] // arti/-/merge_requests/588/#note_2812945
42
#![allow(clippy::result_large_err)] // temporary workaround for arti#587
43
#![allow(clippy::needless_raw_string_hashes)] // complained-about code is fine, often best
44
#![allow(clippy::needless_lifetimes)] // See arti#1765
45
#![allow(mismatched_lifetime_syntaxes)] // temporary workaround for arti#2060
46
#![allow(clippy::collapsible_if)] // See arti#2342
47
#![deny(clippy::unused_async)]
48
#![deny(clippy::string_slice)] // See arti#2571
49
#![allow(recursion_depth_exceeding_limit)] // arti#2715, rust/issues/159228
50
//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
51

            
52
#![allow(non_upper_case_globals)]
53
#![allow(clippy::upper_case_acronyms)]
54

            
55
use caret::caret_int;
56

            
57
use derive_deftly::Deftly;
58
use thiserror::Error;
59
use tor_basic_utils::intern::{GloballyInternable as _, Intern};
60

            
61
pub mod named;
62

            
63
/// Types we export for macros.
64
#[doc(hidden)]
65
pub mod macro_export {
66
    pub use paste;
67
}
68

            
69
caret_int! {
70
    /// A subprotocol.
71
    ///
72
    /// These names are kept in sync with the names used in consensus
73
    /// documents; the values are kept in sync with the values in the
74
    /// cbor document format in the walking onions proposal.
75
    ///
76
    /// For the full semantics of each subprotocol, see tor-spec.txt.
77
    #[derive(Hash,Ord,PartialOrd)]
78
    pub struct ProtoKind(u8) {
79
        /// Initiating and receiving channels, and getting cells on them.
80
        Link = 0,
81
        /// Different kinds of authenticate cells
82
        LinkAuth = 1,
83
        /// CREATE cells, CREATED cells, and the encryption that they
84
        /// create.
85
        Relay = 2,
86
        /// Serving and fetching network directory documents.
87
        DirCache = 3,
88
        /// Serving onion service descriptors
89
        HSDir = 4,
90
        /// Providing an onion service introduction point
91
        HSIntro = 5,
92
        /// Providing an onion service rendezvous point
93
        HSRend = 6,
94
        /// Describing a relay's functionality using router descriptors.
95
        Desc = 7,
96
        /// Describing a relay's functionality using microdescriptors.
97
        Microdesc = 8,
98
        /// Describing the network as a consensus directory document.
99
        Cons = 9,
100
        /// Sending and accepting circuit-level padding
101
        Padding = 10,
102
        /// Improved means of flow control on circuits.
103
        FlowCtrl = 11,
104
        /// Multi-path circuit support.
105
        Conflux = 12,
106
    }
107
}
108

            
109
/// How many recognized protocols are there?
110
const N_RECOGNIZED: usize = 13;
111

            
112
/// Maximum allowable value for a protocol's version field.
113
const MAX_VER: usize = 63;
114

            
115
/// A specific, named subversion of a protocol.
116
#[derive(Eq, PartialEq, Copy, Clone, Debug)]
117
pub struct NamedSubver {
118
    /// The protocol in question
119
    ///
120
    /// Must be in-range for ProtoKind (0..N_RECOGNIZED).
121
    kind: ProtoKind,
122
    /// The version of the protocol
123
    ///
124
    /// Must be in 1..=MAX_VER
125
    version: u8,
126
}
127

            
128
impl NamedSubver {
129
    /// Create a new NamedSubver.
130
    ///
131
    /// # Panics
132
    ///
133
    /// Panics if `kind` is unrecognized or `version` is invalid.
134
    const fn new(kind: ProtoKind, version: u8) -> Self {
135
        assert!((kind.0 as usize) < N_RECOGNIZED);
136
        assert!((version as usize) <= MAX_VER);
137
        Self { kind, version }
138
    }
139
}
140

            
141
/// A subprotocol capability as represented by a (kind, version) tuple.
142
///
143
/// Does not necessarily represent a real subprotocol capability;
144
/// this type is meant for use in other pieces of the protocol.
145
///
146
/// # Ordering
147
///
148
/// Instances of `NumberedSubver` are sorted in lexicographic order by
149
/// their (kind, version) tuples.
150
//
151
// TODO: As with most other types in the crate, we should decide how to rename them as as part
152
// of #1934.
153
#[derive(Clone, Copy, Debug, Ord, PartialOrd, Eq, PartialEq)]
154
pub struct NumberedSubver {
155
    /// The protocol in question
156
    kind: ProtoKind,
157
    /// The version of the protocol
158
    version: u8,
159
}
160

            
161
impl NumberedSubver {
162
    /// Construct a new [`NumberedSubver`]
163
461
    pub fn new(kind: impl Into<ProtoKind>, version: u8) -> Self {
164
461
        Self {
165
461
            kind: kind.into(),
166
461
            version,
167
461
        }
168
461
    }
169
    /// Return the ProtoKind and version for this [`NumberedSubver`].
170
    pub fn into_parts(self) -> (ProtoKind, u8) {
171
        (self.kind, self.version)
172
    }
173
}
174
impl From<NamedSubver> for NumberedSubver {
175
1105
    fn from(value: NamedSubver) -> Self {
176
1105
        Self {
177
1105
            kind: value.kind,
178
1105
            version: value.version,
179
1105
        }
180
1105
    }
181
}
182

            
183
impl From<NamedSubver> for Protocols {
184
    fn from(value: NamedSubver) -> Self {
185
        Self::from_iter([value])
186
    }
187
}
188

            
189
#[cfg(feature = "tor-bytes")]
190
impl tor_bytes::Readable for NumberedSubver {
191
520
    fn take_from(b: &mut tor_bytes::Reader<'_>) -> tor_bytes::Result<Self> {
192
520
        let kind = b.take_u8()?;
193
520
        let version = b.take_u8()?;
194
455
        Ok(Self::new(kind, version))
195
520
    }
196
}
197

            
198
#[cfg(feature = "tor-bytes")]
199
impl tor_bytes::Writeable for NumberedSubver {
200
2
    fn write_onto<B: tor_bytes::Writer + ?Sized>(&self, b: &mut B) -> tor_bytes::EncodeResult<()> {
201
2
        b.write_u8(self.kind.into());
202
2
        b.write_u8(self.version);
203
2
        Ok(())
204
2
    }
205
}
206

            
207
/// Representation for a known or unknown protocol.
208
#[derive(Eq, PartialEq, Clone, Debug, Hash, Ord, PartialOrd)]
209
enum Protocol {
210
    /// A known protocol; represented by one of ProtoKind.
211
    ///
212
    /// ProtoKind must always be in the range 0..N_RECOGNIZED.
213
    Proto(ProtoKind),
214
    /// An unknown protocol; represented by its name.
215
    Unrecognized(String),
216
}
217

            
218
impl Protocol {
219
    /// Return true iff `s` is the name of a protocol we do not recognize.
220
4
    fn is_unrecognized(&self, s: &str) -> bool {
221
4
        match self {
222
4
            Protocol::Unrecognized(s2) => s2 == s,
223
            _ => false,
224
        }
225
4
    }
226
    /// Return a string representation of this protocol.
227
1302
    fn to_str(&self) -> &str {
228
1302
        match self {
229
            Protocol::Proto(k) => k.to_str().unwrap_or("<bug>"),
230
1302
            Protocol::Unrecognized(s) => s,
231
        }
232
1302
    }
233
}
234

            
235
/// Return true if `s` is a valid protocol name.
236
1665
fn is_valid_proto_name(s: &str) -> bool {
237
9288
    (1..=100).contains(&s.len()) && s.chars().all(|c| c.is_ascii_alphanumeric() || c == '-')
238
1665
}
239

            
240
impl std::str::FromStr for Protocol {
241
    type Err = ParseError;
242

            
243
5791687
    fn from_str(s: &str) -> Result<Self, Self::Err> {
244
5791687
        match ProtoKind::from_name(s) {
245
5790022
            Some(p) => Ok(Protocol::Proto(p)),
246
            None => {
247
1665
                if is_valid_proto_name(s) {
248
1665
                    Ok(Protocol::Unrecognized(s.to_string()))
249
                } else {
250
                    Err(ParseError::Malformed)
251
                }
252
            }
253
        }
254
5791687
    }
255
}
256

            
257
/// Representation of a set of versions supported by a protocol.
258
///
259
/// For now, we only use this type for unrecognized protocols.
260
#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
261
struct SubprotocolEntry {
262
    /// Which protocol's versions does this describe?
263
    proto: Protocol,
264
    /// A bit-vector defining which versions are supported.  If bit
265
    /// `(1<<i)` is set, then protocol version `i` is supported.
266
    supported: u64,
267
}
268

            
269
/// A degree of strictness with which to parse a Protocols (or one of its components).
270
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
271
enum ParseStrictness {
272
    /// We should enforce all rules declared in the spec.
273
    Strict,
274

            
275
    /// We should only enforce rules that are also enforced by C tor.
276
    CTorCompatible,
277
}
278

            
279
/// A set of supported or required subprotocol versions.
280
///
281
/// This type supports both recognized subprotocols (listed in ProtoKind),
282
/// and unrecognized subprotocols (stored by name).
283
///
284
/// To construct an instance, use the FromStr trait:
285
/// ```
286
/// use tor_protover::Protocols;
287
/// let p: Result<Protocols,_> = "Link=1-3 LinkAuth=2-3 Relay=1-2".parse();
288
/// ```
289
///
290
/// # Implementation notes
291
///
292
/// Because the number of distinct `Protocols` sets at any given time
293
/// is much smaller than the number of relays, this type is interned in order to
294
/// save memory and copying time.
295
///
296
/// This type is an Arc internally; it is cheap to clone.
297
#[derive(Debug, Clone, Default, Eq, PartialEq, Hash)]
298
#[cfg_attr(
299
    feature = "serde",
300
    derive(serde_with::DeserializeFromStr, serde_with::SerializeDisplay)
301
)]
302
pub struct Protocols(
303
    /// We intern ProtocolsInner objects because:
304
    ///  - There are very few _distinct_ values in any given set of relays.
305
    ///  - Every relay has one.
306
    ///  - We often want to copy them when we're remembering information about circuits.
307
    Intern<ProtocolsInner>,
308
);
309

            
310
/// Inner representation of Protocols.
311
///
312
/// We make this a separate type so that we can intern it inside an `Intern`
313
#[derive(Default, Clone, Debug, Eq, PartialEq, Hash, Deftly)]
314
#[derive_deftly(tor_basic_utils::GloballyInternable)]
315
struct ProtocolsInner {
316
    /// A mapping from protocols' integer encodings to bit-vectors.
317
    recognized: [u64; N_RECOGNIZED],
318
    /// A vector of unrecognized protocol versions,
319
    /// in sorted order.
320
    ///
321
    /// Every entry in this list has supported != 0.
322
    unrecognized: Vec<SubprotocolEntry>,
323
}
324

            
325
impl From<ProtocolsInner> for Protocols {
326
1148021
    fn from(value: ProtocolsInner) -> Self {
327
1148021
        Protocols(value.into_intern())
328
1148021
    }
329
}
330

            
331
impl Protocols {
332
    /// Return a new empty set of protocol versions.
333
    ///
334
    /// # Warning
335
    ///
336
    /// To the extend possible, avoid using empty lists to represent the capabilities
337
    /// of an unknown target.  Instead, if there is a consensus present, use the
338
    /// `required-relay-protocols` field of the consensus.
339
2275
    pub fn new() -> Self {
340
2275
        Protocols::default()
341
2275
    }
342

            
343
    /// Construct a new [`Protocols`] from a single recognized kind and a list of associated versions.
344
    ///
345
    /// (This method should not usually be needed for new parts of Arti:
346
    /// its only use-case is a legacy piece of hsdesc parsing.)
347
1235
    pub fn from_kind_and_versions(kind: ProtoKind, versions: &str) -> Result<Self, ParseError> {
348
1235
        let versions = parse_version_mask(versions, ParseStrictness::Strict)?;
349
1235
        let mut protocols = ProtocolsInner::default();
350

            
351
1235
        if let Some(p) = protocols.recognized.get_mut(usize::from(kind.get())) {
352
1235
            *p = versions;
353
1235
        } else {
354
            return Err(ParseError::Malformed);
355
        }
356

            
357
1235
        Ok(protocols.into())
358
1235
    }
359

            
360
    /// Helper: return true iff this protocol set contains the
361
    /// version `ver` of the protocol represented by the integer `proto`.
362
1610058
    fn supports_recognized_ver(&self, proto: usize, ver: u8) -> bool {
363
1610058
        if usize::from(ver) > MAX_VER {
364
1
            return false;
365
1610057
        }
366
1610057
        if proto >= self.0.recognized.len() {
367
            return false;
368
1610057
        }
369
1610057
        (self.0.recognized[proto] & (1 << ver)) != 0
370
1610058
    }
371
    /// Helper: return true iff this protocol set contains version
372
    /// `ver` of the unrecognized protocol represented by the string
373
    /// `proto`.
374
    ///
375
    /// Requires that `proto` is not the name of a recognized protocol.
376
5
    fn supports_unrecognized_ver(&self, proto: &str, ver: u8) -> bool {
377
5
        if usize::from(ver) > MAX_VER {
378
1
            return false;
379
4
        }
380
4
        let ent = self
381
4
            .0
382
4
            .unrecognized
383
4
            .iter()
384
4
            .find(|ent| ent.proto.is_unrecognized(proto));
385
4
        match ent {
386
2
            Some(e) => (e.supported & (1 << ver)) != 0,
387
2
            None => false,
388
        }
389
5
    }
390

            
391
    /// Return true if this list of protocols is empty.
392
11895
    pub fn is_empty(&self) -> bool {
393
104650
        self.0.recognized.iter().all(|v| *v == 0)
394
6825
            && self.0.unrecognized.iter().all(|p| p.supported == 0)
395
11895
    }
396

            
397
    // TODO: Combine these next two functions into one by using a trait.
398
    /// Check whether a known protocol version is supported.
399
    ///
400
    /// ```
401
    /// use tor_protover::*;
402
    /// let protos: Protocols = "Link=1-3 HSDir=2,4-5".parse().unwrap();
403
    ///
404
    /// assert!(protos.supports_known_subver(ProtoKind::Link, 2));
405
    /// assert!(protos.supports_known_subver(ProtoKind::HSDir, 4));
406
    /// assert!(! protos.supports_known_subver(ProtoKind::HSDir, 3));
407
    /// assert!(! protos.supports_known_subver(ProtoKind::LinkAuth, 3));
408
    /// ```
409
1609926
    pub fn supports_known_subver(&self, proto: ProtoKind, ver: u8) -> bool {
410
1609926
        self.supports_recognized_ver(proto.get() as usize, ver)
411
1609926
    }
412
    /// Check whether a protocol version identified by a string is supported.
413
    ///
414
    /// ```
415
    /// use tor_protover::*;
416
    /// let protos: Protocols = "Link=1-3 Foobar=7".parse().unwrap();
417
    ///
418
    /// assert!(protos.supports_subver("Link", 2));
419
    /// assert!(protos.supports_subver("Foobar", 7));
420
    /// assert!(! protos.supports_subver("Link", 5));
421
    /// assert!(! protos.supports_subver("Foobar", 6));
422
    /// assert!(! protos.supports_subver("Wombat", 3));
423
    /// ```
424
137
    pub fn supports_subver(&self, proto: &str, ver: u8) -> bool {
425
137
        match ProtoKind::from_name(proto) {
426
132
            Some(p) => self.supports_recognized_ver(p.get() as usize, ver),
427
5
            None => self.supports_unrecognized_ver(proto, ver),
428
        }
429
137
    }
430

            
431
    /// Check whether a protocol version is supported.
432
    ///
433
    /// ```
434
    /// use tor_protover::*;
435
    /// let protos: Protocols = "Link=1-5 Desc=2-4".parse().unwrap();
436
    /// assert!(protos.supports_named_subver(named::DESC_FAMILY_IDS)); // Desc=4
437
    /// assert!(! protos.supports_named_subver(named::CONFLUX_BASE)); // Conflux=1
438
    /// ```
439
1609140
    pub fn supports_named_subver(&self, protover: NamedSubver) -> bool {
440
1609140
        self.supports_known_subver(protover.kind, protover.version)
441
1609140
    }
442

            
443
    /// Check whether a numbered subprotocol capability is supported.
444
    ///
445
    /// ```
446
    /// use tor_protover::*;
447
    /// let protos: Protocols = "Link=1-5 Desc=2-4".parse().unwrap();
448
    /// assert!(protos.supports_numbered_subver(NumberedSubver::new(ProtoKind::Desc, 4)));
449
    /// assert!(! protos.supports_numbered_subver(NumberedSubver::new(ProtoKind::Conflux, 1)));
450
    /// ```
451
780
    pub fn supports_numbered_subver(&self, protover: NumberedSubver) -> bool {
452
780
        self.supports_known_subver(protover.kind, protover.version)
453
780
    }
454

            
455
    /// Return a Protocols holding every protocol flag that is present in `self`
456
    /// but not `other`.
457
    ///
458
    /// ```
459
    /// use tor_protover::*;
460
    /// let protos: Protocols = "Desc=2-4 Microdesc=1-5".parse().unwrap();
461
    /// let protos2: Protocols = "Desc=3 Microdesc=3".parse().unwrap();
462
    /// assert_eq!(protos.difference(&protos2),
463
    ///            "Desc=2,4 Microdesc=1-2,4-5".parse().unwrap());
464
    /// ```
465
1046
    pub fn difference(&self, other: &Protocols) -> Protocols {
466
1046
        let mut r = ProtocolsInner::default();
467

            
468
13598
        for i in 0..N_RECOGNIZED {
469
13598
            r.recognized[i] = self.0.recognized[i] & !other.0.recognized[i];
470
13598
        }
471
        // This is not super efficient, but we don't have to do it often.
472
1048
        for ent in self.0.unrecognized.iter() {
473
73
            let mut ent = ent.clone();
474
73
            if let Some(other_ent) = other.0.unrecognized.iter().find(|e| e.proto == ent.proto) {
475
2
                ent.supported &= !other_ent.supported;
476
71
            }
477
73
            if ent.supported != 0 {
478
72
                r.unrecognized.push(ent);
479
72
            }
480
        }
481
1046
        Protocols::from(r)
482
1046
    }
483

            
484
    /// Return a Protocols holding every protocol flag that is present in `self`
485
    /// or `other` or both.
486
    ///
487
    /// ```
488
    /// use tor_protover::*;
489
    /// let protos: Protocols = "Desc=2-4 Microdesc=1-5".parse().unwrap();
490
    /// let protos2: Protocols = "Desc=3 Microdesc=10".parse().unwrap();
491
    /// assert_eq!(protos.union(&protos2),
492
    ///            "Desc=2-4 Microdesc=1-5,10".parse().unwrap());
493
    /// ```
494
5856
    pub fn union(&self, other: &Protocols) -> Protocols {
495
5856
        let mut r = (**self.0).clone();
496
76128
        for i in 0..N_RECOGNIZED {
497
76128
            r.recognized[i] |= other.0.recognized[i];
498
76128
        }
499
5858
        for ent in other.0.unrecognized.iter() {
500
9
            if let Some(my_ent) = r.unrecognized.iter_mut().find(|e| e.proto == ent.proto) {
501
2
                my_ent.supported |= ent.supported;
502
6
            } else {
503
6
                r.unrecognized.push(ent.clone());
504
6
            }
505
        }
506
5856
        r.unrecognized.sort();
507
5856
        Protocols::from(r)
508
5856
    }
509

            
510
    /// Return a Protocols holding every protocol flag that is present in both `self`
511
    /// and `other`.
512
    ///
513
    /// ```
514
    /// use tor_protover::*;
515
    /// let protos: Protocols = "Desc=2-4 Microdesc=1-5".parse().unwrap();
516
    /// let protos2: Protocols = "Desc=3 Microdesc=10".parse().unwrap();
517
    /// assert_eq!(protos.intersection(&protos2),
518
    ///            "Desc=3".parse().unwrap());
519
    /// ```
520
19636
    pub fn intersection(&self, other: &Protocols) -> Protocols {
521
19636
        let mut r = ProtocolsInner::default();
522
255268
        for i in 0..N_RECOGNIZED {
523
255268
            r.recognized[i] = self.0.recognized[i] & other.0.recognized[i];
524
255268
        }
525
19638
        for ent in self.0.unrecognized.iter() {
526
8
            if let Some(other_ent) = other.0.unrecognized.iter().find(|e| e.proto == ent.proto) {
527
2
                let supported = ent.supported & other_ent.supported;
528
2
                if supported != 0 {
529
2
                    r.unrecognized.push(SubprotocolEntry {
530
2
                        proto: ent.proto.clone(),
531
2
                        supported,
532
2
                    });
533
2
                }
534
6
            }
535
        }
536
19636
        r.unrecognized.sort();
537
19636
        Protocols::from(r)
538
19636
    }
539

            
540
    /// As the regular `FromStr` implementation,
541
    /// but imitate the result of a the C Tor implementation, which is rather more lax.
542
    ///
543
    /// The principle differences are:
544
    /// - We allow all decimal integer formats, even those starting with 0.
545
    /// - We allow (and ignore) the integer '0'.
546
    /// - We do not require that there is no more than one `SubprotocolEntry` with a given
547
    ///   `ProtoKind`; instead, we ignore all but the first.
548
    /// - We permit duplicate bits within the same SubprotocolEntry.
549
281977
    pub fn from_str_c_compatible(s: &str) -> Result<Self, ParseError> {
550
281977
        Self::from_str_impl(s, ParseStrictness::CTorCompatible)
551
281977
    }
552
}
553

            
554
impl ProtocolsInner {
555
    /// Parsing helper: Try to add a new entry `ent` to this set of protocols.
556
    ///
557
    /// Uses `foundmask`, a bit mask saying which recognized protocols
558
    /// we've already found entries for.  Returns an error if `ent` is
559
    /// for a recognized protocol we've already added.
560
    ///
561
    /// WARNING: This method DOES NOT enforce uniqueness for unrecognized protocols.
562
    /// The caller is responsible for doing that in a way appropriate
563
    /// for the provided strictness level.
564
    ///
565
    /// Does not preserve sorting order; the caller must call `self.unrecognized.sort()` before returning.
566
5791667
    fn add(
567
5791667
        &mut self,
568
5791667
        foundmask: &mut u64,
569
5791667
        ent: SubprotocolEntry,
570
5791667
        strictness: ParseStrictness,
571
5791667
    ) -> Result<(), ParseError> {
572
5791667
        match ent.proto {
573
5790005
            Protocol::Proto(k) => {
574
5790005
                let idx = k.get() as usize;
575
5790005
                assert!(idx < N_RECOGNIZED); // guaranteed by invariant on Protocol::Proto
576
5790005
                let bit = 1 << u64::from(k.get());
577
5790005
                if (*foundmask & bit) != 0 {
578
4
                    match strictness {
579
2
                        ParseStrictness::Strict => return Err(ParseError::Duplicate),
580
2
                        ParseStrictness::CTorCompatible => return Ok(()),
581
                    }
582
5790001
                }
583
5790001
                *foundmask |= bit;
584
5790001
                self.recognized[idx] = ent.supported;
585
            }
586
            Protocol::Unrecognized(_) => {
587
1662
                if ent.supported != 0 {
588
1662
                    self.unrecognized.push(ent);
589
1662
                }
590
            }
591
        }
592
5791663
        Ok(())
593
5791667
    }
594
}
595

            
596
/// An error representing a failure to parse a set of protocol versions.
597
#[derive(Error, Debug, PartialEq, Eq, Clone)]
598
#[non_exhaustive]
599
pub enum ParseError {
600
    /// A protocol version was not in the range 1..=63.
601
    #[error("Protocol version out of range")]
602
    OutOfRange,
603
    /// Some subprotocol or protocol version appeared more than once.
604
    #[error("Duplicate protocol entry")]
605
    Duplicate,
606
    /// The list of protocol versions was malformed in some other way.
607
    #[error("Malformed protocol entry")]
608
    Malformed,
609
}
610

            
611
/// Helper: return a new u64 in which bits `lo` through `hi` inclusive
612
/// are set to 1, and all the other bits are set to 0.
613
///
614
/// In other words, `bitrange(a,b)` is how we represent the range of
615
/// versions `a-b` in a protocol version bitmask.
616
///
617
/// ```ignore
618
/// # use tor_protover::bitrange;
619
/// assert_eq!(bitrange(0, 5), 0b111111);
620
/// assert_eq!(bitrange(2, 5), 0b111100);
621
/// assert_eq!(bitrange(2, 7), 0b11111100);
622
/// ```
623
5800172
fn bitrange(lo: u64, hi: u64) -> u64 {
624
5800172
    assert!(lo <= hi && lo <= 63 && hi <= 63);
625
5800172
    let mut mask = !0;
626
5800172
    mask <<= 63 - hi;
627
5800172
    mask >>= 63 - hi + lo;
628
5800172
    mask <<= lo;
629
5800172
    mask
630
5800172
}
631

            
632
/// Helper: return true if the provided string is a valid "integer"
633
/// in the form accepted by the protover spec.  This may be stricter than
634
/// rust's integer parsing format.
635
11600359
fn is_good_number(n: &str, strictness: ParseStrictness) -> bool {
636
11600359
    if strictness == ParseStrictness::Strict && n.starts_with('0') {
637
5
        return false;
638
11600354
    }
639
11604015
    n.chars().all(|ch| ch.is_ascii_digit())
640
11600359
}
641

            
642
/// Parse a version-list in `versions` into a bitmask.
643
5792922
fn parse_version_mask(versions: &str, strictness: ParseStrictness) -> Result<u64, ParseError> {
644
5792922
    if versions.is_empty() {
645
        // We need to handle this case specially, since otherwise
646
        // it would be treated below as a single empty value, which
647
        // would be rejected.
648
1
        return Ok(0);
649
5792921
    }
650
    // Construct a bitmask based on the comma-separated versions.
651
5792921
    let mut supported = 0_u64;
652
5800184
    for ent in versions.split(',') {
653
        // Find and parse lo and hi for a single range of versions.
654
        // (If this is not a range, but rather a single version v,
655
        // treat it as if it were a range v-v.)
656
5800184
        let (lo_s, hi_s) = ent.split_once('-').unwrap_or((ent, ent));
657

            
658
5800184
        if !is_good_number(lo_s, strictness) {
659
9
            return Err(ParseError::Malformed);
660
5800175
        }
661
5800175
        if !is_good_number(hi_s, strictness) {
662
1
            return Err(ParseError::Malformed);
663
5800174
        }
664
5800174
        let lo: u64 = lo_s.parse().map_err(|_| ParseError::Malformed)?;
665
5800173
        let hi: u64 = hi_s.parse().map_err(|_| ParseError::Malformed)?;
666
        // Make sure that lo and hi are in-bounds and consistent.
667
5800171
        if lo > (MAX_VER as u64) || hi > (MAX_VER as u64) {
668
3
            return Err(ParseError::OutOfRange);
669
5800168
        }
670
5800168
        if lo > hi {
671
1
            return Err(ParseError::Malformed);
672
5800167
        }
673
5800167
        let mask = bitrange(lo, hi);
674
        // Make sure that no version is included twice.
675
5800167
        if strictness == ParseStrictness::Strict && (supported & mask) != 0 {
676
3
            return Err(ParseError::Duplicate);
677
5800164
        }
678
        // Ignore bit 0, which can be set for non-strict bitmasks.
679
5800164
        let mask = mask & !1;
680
        // Add the appropriate bits to the mask.
681
5800164
        supported |= mask;
682
    }
683

            
684
5792901
    Ok(supported)
685
5792922
}
686

            
687
/// A single SubprotocolEntry is parsed from a string of the format
688
/// Name=Versions, where Versions is a comma-separated list of
689
/// integers or ranges of integers.
690
impl SubprotocolEntry {
691
    /// Parse a [`SubprotocolEntry`] with a given level of strictness.
692
5791688
    fn from_str(s: &str, strictness: ParseStrictness) -> Result<Self, ParseError> {
693
        // split the string on the =.
694
5791688
        let (name, versions) = s.split_once('=').ok_or(ParseError::Malformed)?;
695

            
696
        // Look up the protocol by name.
697
5791687
        let proto: Protocol = name.parse()?;
698
        Ok(SubprotocolEntry {
699
5791687
            proto,
700
5791687
            supported: parse_version_mask(versions, strictness)?,
701
        })
702
5791688
    }
703
}
704

            
705
/// A Protocols set can be parsed from a string according to the
706
/// format used in Tor consensus documents.
707
///
708
/// A protocols set is represented by a space-separated list of
709
/// entries.  Each entry is of the form `Name=Versions`, where `Name`
710
/// is the name of a protocol, and `Versions` is a comma-separated
711
/// list of version numbers and version ranges.  Each version range is
712
/// a pair of integers separated by `-`.
713
///
714
/// No protocol name may be listed twice.  No version may be listed
715
/// twice for a single protocol.  All versions must be in range 0
716
/// through 63 inclusive.
717
impl std::str::FromStr for Protocols {
718
    type Err = ParseError;
719

            
720
778622
    fn from_str(s: &str) -> Result<Self, ParseError> {
721
778622
        Protocols::from_str_impl(s, ParseStrictness::Strict)
722
778622
    }
723
}
724

            
725
impl Protocols {
726
    /// Parse a [`Protocols`] with the requested degree of strictness.
727
1060599
    fn from_str_impl(s: &str, strictness: ParseStrictness) -> Result<Self, ParseError> {
728
1060599
        let mut result = ProtocolsInner::default();
729
1060599
        let mut foundmask = 0_u64;
730
6084646
        for ent in s.split(' ') {
731
6084646
            if ent.is_empty() {
732
292958
                continue;
733
5791688
            }
734

            
735
5791688
            let s = SubprotocolEntry::from_str(ent, strictness)?;
736
5791667
            result.add(&mut foundmask, s, strictness)?;
737
        }
738
        // We require a stable sort here.
739
        // We can't use sort_by_key because of lifetime issues.
740
1060576
        result.unrecognized.sort_by(|a, b| a.proto.cmp(&b.proto));
741
1060576
        match strictness {
742
            ParseStrictness::Strict => {
743
778599
                if result
744
778599
                    .unrecognized
745
778599
                    .windows(2)
746
778599
                    .any(|w| w[0].proto == w[1].proto)
747
                {
748
2
                    return Err(ParseError::Duplicate);
749
778597
                }
750
            }
751
            ParseStrictness::CTorCompatible => {
752
281977
                result.unrecognized.dedup_by(|a, b| a.proto.eq(&b.proto));
753
            }
754
        }
755

            
756
1060574
        Ok(result.into())
757
1060599
    }
758
}
759

            
760
/// Given a bitmask, return a list of the bits set in the mask, as a
761
/// String in the format expected by Tor consensus documents.
762
///
763
/// This implementation constructs ranges greedily.  For example, the
764
/// bitmask `0b0111011` will be represented as `0-1,3-5`, and not
765
/// `0,1,3,4,5` or `0,1,3-5`.
766
///
767
/// ```ignore
768
/// # use tor_protover::dumpmask;
769
/// assert_eq!(dumpmask(0b111111), "0-5");
770
/// assert_eq!(dumpmask(0b111100), "2-5");
771
/// assert_eq!(dumpmask(0b11111100), "2-7");
772
/// ```
773
75607
fn dumpmask(mut mask: u64) -> String {
774
    /// Helper: push a range (which may be a singleton) onto `v`.
775
75675
    fn append(v: &mut Vec<String>, lo: u32, hi: u32) {
776
75675
        if lo == hi {
777
30492
            v.push(lo.to_string());
778
45183
        } else {
779
45183
            v.push(format!("{}-{}", lo, hi));
780
45183
        }
781
75675
    }
782
    // We'll be building up our result here, then joining it with
783
    // commas.
784
75607
    let mut result = Vec::new();
785
    // This implementation is a little tricky, but it should be more
786
    // efficient than a raw search.  Basically, we're using the
787
    // function u64::trailing_zeros to count how large each range of
788
    // 1s or 0s is, and then shifting by that amount.
789

            
790
    // How many bits have we already shifted `mask`?
791
75607
    let mut shift = 0;
792
151281
    while mask != 0 {
793
75675
        let zeros = mask.trailing_zeros();
794
75675
        mask >>= zeros;
795
75675
        shift += zeros;
796
75675
        let ones = mask.trailing_ones();
797
75675
        append(&mut result, shift, shift + ones - 1);
798
75675
        shift += ones;
799
75675
        if ones == 64 {
800
            // We have to do this check to avoid overflow when formatting
801
            // the range `0-63`.
802
1
            break;
803
75674
        }
804
75674
        mask >>= ones;
805
    }
806
75607
    result.join(",")
807
75607
}
808

            
809
/// The Display trait formats a protocol set in the format expected by Tor
810
/// consensus documents.
811
///
812
/// ```
813
/// use tor_protover::*;
814
/// let protos: Protocols = "Link=1,2,3 Foobar=7 Relay=2".parse().unwrap();
815
/// assert_eq!(format!("{}", protos),
816
///            "Foobar=7 Link=1-3 Relay=2");
817
/// ```
818
impl std::fmt::Display for Protocols {
819
17425
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
820
17425
        let mut entries = Vec::new();
821
226525
        for (idx, mask) in self.0.recognized.iter().enumerate() {
822
226525
            if *mask != 0 {
823
74300
                let pk: ProtoKind = (idx as u8).into();
824
74300
                entries.push(format!("{}={}", pk, dumpmask(*mask)));
825
152225
            }
826
        }
827
17425
        for ent in &self.0.unrecognized {
828
1302
            if ent.supported != 0 {
829
1302
                entries.push(format!(
830
1302
                    "{}={}",
831
1302
                    ent.proto.to_str(),
832
1302
                    dumpmask(ent.supported)
833
1302
                ));
834
1302
            }
835
        }
836
        // This sort is required.
837
17425
        entries.sort();
838
17425
        write!(f, "{}", entries.join(" "))
839
17425
    }
840
}
841

            
842
impl FromIterator<NamedSubver> for Protocols {
843
16580
    fn from_iter<T: IntoIterator<Item = NamedSubver>>(iter: T) -> Self {
844
16580
        let mut r = ProtocolsInner::default();
845
116440
        for named_subver in iter {
846
116440
            let proto_idx = usize::from(named_subver.kind.get());
847
116440
            let proto_ver = named_subver.version;
848

            
849
            // These are guaranteed by invariants on NamedSubver.
850
116440
            assert!(proto_idx < N_RECOGNIZED);
851
116440
            assert!(usize::from(proto_ver) <= MAX_VER);
852
116440
            r.recognized[proto_idx] |= 1_u64 << proto_ver;
853
        }
854
16580
        Protocols::from(r)
855
16580
    }
856
}
857

            
858
/// Documentation: when is a protocol "supported"?
859
///
860
/// Arti should consider itself to "support" a protocol if, _as built_,
861
/// it implements the protocol completely.
862
///
863
/// Just having the protocol listed among the [`named`]
864
/// protocols is not enough, and neither is an incomplete
865
/// or uncompliant implementation.
866
///
867
/// Similarly, if the protocol is not compiled in,
868
/// it is not technically _supported_.
869
///
870
/// When in doubt, ask yourself:
871
/// - If another Tor implementation believed that we implemented this protocol,
872
///   and began to speak it to us, would we be able to do so?
873
/// - If the protocol were required,
874
///   would this software as built actually meet that requirement?
875
///
876
/// If either answer is no, the protocol is not supported.
877
pub mod doc_supported {}
878

            
879
/// Documentation about changing lists of supported versions.
880
///
881
/// # Warning
882
///
883
/// You need to be extremely careful when removing
884
/// _any_ entry from a list of supported protocols.
885
///
886
/// If you remove an entry while it still appears as "recommended" in the consensus,
887
/// you'll cause all the instances without it to warn.
888
///
889
/// If you remove an entry while it still appears as "required" in the
890
///  consensus, you'll cause all the instances without it to refuse to connect
891
/// to the network, and shut down.
892
///
893
/// If you need to remove a version from a list of supported protocols,
894
/// you need to make sure that it is not listed in the _current consensuses_:
895
/// just removing it from the list that the authorities vote for is NOT ENOUGH.
896
/// You need to remove it from the required list,
897
/// and THEN let the authorities upgrade and vote on new
898
/// consensuses without it. Only once those consensuses are out is it safe to
899
/// remove from the list of required protocols.
900
///
901
/// ## Example
902
///
903
/// One concrete example of a very dangerous race that could occur:
904
///
905
/// Suppose that the client supports protocols "HsDir=1-2" and the consensus
906
/// requires protocols "HsDir=1-2".  If the client supported protocol list is
907
/// then changed to "HSDir=2", while the consensus stills lists "HSDir=1-2",
908
/// then these clients, even very recent ones, will shut down because they
909
/// don't support "HSDir=1".
910
///
911
/// And so, changes need to be done in strict sequence as described above.
912
pub mod doc_changing {}
913

            
914
#[cfg(test)]
915
mod test {
916
    // @@ begin test lint list maintained by maint/add_warning @@
917
    #![allow(clippy::bool_assert_comparison)]
918
    #![allow(clippy::clone_on_copy)]
919
    #![allow(clippy::dbg_macro)]
920
    #![allow(clippy::mixed_attributes_style)]
921
    #![allow(clippy::print_stderr)]
922
    #![allow(clippy::print_stdout)]
923
    #![allow(clippy::single_char_pattern)]
924
    #![allow(clippy::unwrap_used)]
925
    #![allow(clippy::unchecked_time_subtraction)]
926
    #![allow(clippy::useless_vec)]
927
    #![allow(clippy::needless_pass_by_value)]
928
    #![allow(clippy::string_slice)] // See arti#2571
929
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
930
    use std::str::FromStr;
931

            
932
    use super::*;
933

            
934
    #[test]
935
    fn test_bitrange() {
936
        assert_eq!(0b1, bitrange(0, 0));
937
        assert_eq!(0b10, bitrange(1, 1));
938
        assert_eq!(0b11, bitrange(0, 1));
939
        assert_eq!(0b1111110000000, bitrange(7, 12));
940
        assert_eq!(!0, bitrange(0, 63));
941
    }
942

            
943
    #[test]
944
    fn test_dumpmask() {
945
        assert_eq!("", dumpmask(0));
946
        assert_eq!("0-5", dumpmask(0b111111));
947
        assert_eq!("4-5", dumpmask(0b110000));
948
        assert_eq!("1,4-5", dumpmask(0b110010));
949
        assert_eq!("0-63", dumpmask(!0));
950
    }
951

            
952
    #[test]
953
    fn test_canonical() -> Result<(), ParseError> {
954
        fn t(orig: &str, canonical: &str) -> Result<(), ParseError> {
955
            let protos: Protocols = orig.parse()?;
956
            let enc = format!("{}", protos);
957
            assert_eq!(enc, canonical);
958
            Ok(())
959
        }
960

            
961
        t("", "")?;
962
        t(" ", "")?;
963
        t("Link=5,6,7,9 Relay=4-7,2", "Link=5-7,9 Relay=2,4-7")?;
964
        t("FlowCtrl= Padding=8,7 Desc=1-5,6-8", "Desc=1-8 Padding=7-8")?;
965
        t("Zelda=7 Gannon=3,6 Link=4", "Gannon=3,6 Link=4 Zelda=7")?;
966

            
967
        Ok(())
968
    }
969

            
970
    #[test]
971
    fn test_invalid() {
972
        fn t(s: &str) -> ParseError {
973
            let protos: Result<Protocols, ParseError> = s.parse();
974
            assert!(protos.is_err());
975
            protos.err().unwrap()
976
        }
977

            
978
        assert_eq!(t("Link=1-100"), ParseError::OutOfRange);
979
        assert_eq!(t("Zelda=100"), ParseError::OutOfRange);
980
        assert_eq!(t("Link=100-200"), ParseError::OutOfRange);
981

            
982
        assert_eq!(t("Link=1,1"), ParseError::Duplicate);
983
        assert_eq!(t("Link=1 Link=1"), ParseError::Duplicate);
984
        assert_eq!(t("Link=1 Link=3"), ParseError::Duplicate);
985
        assert_eq!(t("Zelda=1 Zelda=3"), ParseError::Duplicate);
986

            
987
        assert_eq!(t("Link=Zelda"), ParseError::Malformed);
988
        assert_eq!(t("Link=6-2"), ParseError::Malformed);
989
        assert_eq!(t("Link=6-"), ParseError::Malformed);
990
        assert_eq!(t("Link=6-,2"), ParseError::Malformed);
991
        assert_eq!(t("Link=1,,2"), ParseError::Malformed);
992
        assert_eq!(t("Link=6-frog"), ParseError::Malformed);
993
        assert_eq!(t("Link=gannon-9"), ParseError::Malformed);
994
        assert_eq!(t("Link Zelda"), ParseError::Malformed);
995

            
996
        assert_eq!(t("Link=01"), ParseError::Malformed);
997
        assert_eq!(t("Link=waffle"), ParseError::Malformed);
998
        assert_eq!(t("Link=1_1"), ParseError::Malformed);
999
    }
    #[test]
    fn test_ctor_compat() {
        for (loose, strict) in &[
            // With duplicate entries, those after the first are ignored
            ("Link=0-10 Link=25-30", "Link=1-10"),
            ("Link=25-30 Link=0-10", "Link=25-30"),
            ("Zelda=6-8 Link=1-10 Zelda=6-10", "Link=1-10 Zelda=6-8"),
            // The 0 bit and any 0 prefixes are ignored.
            ("LinkAuth=0-00040", "LinkAuth=1-40"),
            ("Foo=0,3,004,5,0010", "Foo=3-5,10"),
            // Duplicate bits are permitted
            ("Foo=1-10,2-20,3-5", "Foo=1-20"),
            ("Link=1-10,2-20,3-5", "Link=1-20"),
        ] {
            let p1 = Protocols::from_str_c_compatible(loose).unwrap();
            let p2 = Protocols::from_str(strict).unwrap();
            assert_eq!(p1, p2);
            assert!(Protocols::from_str(loose).is_err());
        }
    }
    #[test]
    fn test_supports() -> Result<(), ParseError> {
        let p: Protocols = "Link=4,5-7 Padding=2 Lonk=1-3,5".parse()?;
        assert!(p.supports_known_subver(ProtoKind::Padding, 2));
        assert!(!p.supports_known_subver(ProtoKind::Padding, 1));
        assert!(p.supports_known_subver(ProtoKind::Link, 6));
        assert!(!p.supports_known_subver(ProtoKind::Link, 255));
        assert!(!p.supports_known_subver(ProtoKind::Cons, 1));
        assert!(!p.supports_known_subver(ProtoKind::Cons, 0));
        assert!(p.supports_subver("Link", 6));
        assert!(!p.supports_subver("link", 6));
        assert!(!p.supports_subver("Cons", 0));
        assert!(p.supports_subver("Lonk", 3));
        assert!(!p.supports_subver("Lonk", 4));
        assert!(!p.supports_subver("lonk", 3));
        assert!(!p.supports_subver("Lonk", 64));
        Ok(())
    }
    #[test]
    fn test_difference() -> Result<(), ParseError> {
        let p1: Protocols = "Link=1-10 Desc=5-10 Relay=1,3,5,7,9 Other=7-60 Mine=1-20".parse()?;
        let p2: Protocols = "Link=3-4 Desc=1-6 Relay=2-6 Other=8 Theirs=20".parse()?;
        assert_eq!(
            p1.difference(&p2),
            Protocols::from_str("Link=1-2,5-10 Desc=7-10 Relay=1,7,9 Other=7,9-60 Mine=1-20")?
        );
        assert_eq!(
            p2.difference(&p1),
            Protocols::from_str("Desc=1-4 Relay=2,4,6 Theirs=20")?,
        );
        let nil = Protocols::default();
        assert_eq!(p1.difference(&nil), p1);
        assert_eq!(p2.difference(&nil), p2);
        assert_eq!(nil.difference(&p1), nil);
        assert_eq!(nil.difference(&p2), nil);
        Ok(())
    }
    #[test]
    fn test_union() -> Result<(), ParseError> {
        let p1: Protocols = "Link=1-10 Desc=5-10 Relay=1,3,5,7,9 Other=7-60 Mine=1-20".parse()?;
        let p2: Protocols = "Link=3-4 Desc=1-6 Relay=2-6 Other=2,8 Theirs=20".parse()?;
        assert_eq!(
            p1.union(&p2),
            Protocols::from_str(
                "Link=1-10 Desc=1-10 Relay=1-7,9 Other=2,7-60 Theirs=20 Mine=1-20"
            )?
        );
        assert_eq!(
            p2.union(&p1),
            Protocols::from_str(
                "Link=1-10 Desc=1-10 Relay=1-7,9 Other=2,7-60 Theirs=20 Mine=1-20"
            )?
        );
        let nil = Protocols::default();
        assert_eq!(p1.union(&nil), p1);
        assert_eq!(p2.union(&nil), p2);
        assert_eq!(nil.union(&p1), p1);
        assert_eq!(nil.union(&p2), p2);
        Ok(())
    }
    #[test]
    fn test_intersection() -> Result<(), ParseError> {
        let p1: Protocols = "Link=1-10 Desc=5-10 Relay=1,3,5,7,9 Other=7-60 Mine=1-20".parse()?;
        let p2: Protocols = "Link=3-4 Desc=1-6 Relay=2-6 Other=2,8 Theirs=20".parse()?;
        assert_eq!(
            p1.intersection(&p2),
            Protocols::from_str("Link=3-4 Desc=5-6 Relay=3,5 Other=8")?
        );
        assert_eq!(
            p2.intersection(&p1),
            Protocols::from_str("Link=3-4 Desc=5-6 Relay=3,5 Other=8")?
        );
        let nil = Protocols::default();
        assert_eq!(p1.intersection(&nil), nil);
        assert_eq!(p2.intersection(&nil), nil);
        assert_eq!(nil.intersection(&p1), nil);
        assert_eq!(nil.intersection(&p2), nil);
        Ok(())
    }
    #[test]
    fn from_iter() {
        use named as n;
        let empty: [NamedSubver; 0] = [];
        let prs: Protocols = empty.iter().copied().collect();
        assert_eq!(prs, Protocols::default());
        let prs: Protocols = empty.into_iter().collect();
        assert_eq!(prs, Protocols::default());
        let prs = [
            n::LINK_V3,
            n::HSDIR_V3,
            n::LINK_V4,
            n::LINK_V5,
            n::CONFLUX_BASE,
        ]
        .into_iter()
        .collect::<Protocols>();
        assert_eq!(prs, "Link=3-5 HSDir=2 Conflux=1".parse().unwrap());
    }
    #[test]
    fn order_numbered_subvers() {
        // We rely on this sort order elsewhere in our protocol.
        assert!(NumberedSubver::new(5, 7) < NumberedSubver::new(7, 5));
        assert!(NumberedSubver::new(7, 5) < NumberedSubver::new(7, 6));
        assert!(NumberedSubver::new(7, 6) < NumberedSubver::new(8, 6));
    }
}