1
#![cfg_attr(docsrs, feature(doc_auto_cfg, 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
#![warn(clippy::cognitive_complexity)]
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
#![deny(clippy::print_stderr)]
28
#![deny(clippy::print_stdout)]
29
#![warn(clippy::rc_buffer)]
30
#![deny(clippy::ref_option_ref)]
31
#![warn(clippy::semicolon_if_nothing_returned)]
32
#![warn(clippy::trait_duplication_in_bounds)]
33
#![deny(clippy::unchecked_duration_subtraction)]
34
#![deny(clippy::unnecessary_wraps)]
35
#![warn(clippy::unseparated_literal_suffix)]
36
#![deny(clippy::unwrap_used)]
37
#![allow(clippy::let_unit_value)] // This can reasonably be done for explicitness
38
#![allow(clippy::uninlined_format_args)]
39
#![allow(clippy::significant_drop_in_scrutinee)] // arti/-/merge_requests/588/#note_2812945
40
#![allow(clippy::result_large_err)] // temporary workaround for arti#587
41
#![allow(clippy::needless_raw_string_hashes)] // complained-about code is fine, often best
42
//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
43

            
44
#![allow(non_upper_case_globals)]
45
#![allow(clippy::upper_case_acronyms)]
46

            
47
use caret::caret_int;
48

            
49
use thiserror::Error;
50

            
51
caret_int! {
52
    /// A recognized subprotocol.
53
    ///
54
    /// These names are kept in sync with the names used in consensus
55
    /// documents; the values are kept in sync with the values in the
56
    /// cbor document format in the walking onions proposal.
57
    ///
58
    /// For the full semantics of each subprotocol, see tor-spec.txt.
59
    #[derive(Hash,Ord,PartialOrd)]
60
    pub struct ProtoKind(u16) {
61
        /// Initiating and receiving channels, and getting cells on them.
62
        Link = 0,
63
        /// Different kinds of authenticate cells
64
        LinkAuth = 1,
65
        /// CREATE cells, CREATED cells, and the encryption that they
66
        /// create.
67
        Relay = 2,
68
        /// Serving and fetching network directory documents.
69
        DirCache = 3,
70
        /// Serving onion service descriptors
71
        HSDir = 4,
72
        /// Providing an onion service introduction point
73
        HSIntro = 5,
74
        /// Providing an onion service rendezvous point
75
        HSRend = 6,
76
        /// Describing a relay's functionality using router descriptors.
77
        Desc = 7,
78
        /// Describing a relay's functionality using microdescriptors.
79
        MicroDesc = 8,
80
        /// Describing the network as a consensus directory document.
81
        Cons = 9,
82
        /// Sending and accepting circuit-level padding
83
        Padding = 10,
84
        /// Improved means of flow control on circuits.
85
        FlowCtrl = 11,
86
    }
87
}
88

            
89
/// How many recognized protocols are there?
90
const N_RECOGNIZED: usize = 12;
91

            
92
/// Representation for a known or unknown protocol.
93
#[derive(Eq, PartialEq, Clone, Debug, Hash, Ord, PartialOrd)]
94
enum Protocol {
95
    /// A known protocol; represented by one of ProtoKind.
96
    Proto(ProtoKind),
97
    /// An unknown protocol; represented by its name.
98
    Unrecognized(String),
99
}
100

            
101
impl Protocol {
102
    /// Return true iff `s` is the name of a protocol we do not recognize.
103
63
    fn is_unrecognized(&self, s: &str) -> bool {
104
63
        match self {
105
63
            Protocol::Unrecognized(s2) => s2 == s,
106
            _ => false,
107
        }
108
63
    }
109
    /// Return a string representation of this protocol.
110
55
    fn to_str(&self) -> &str {
111
55
        match self {
112
            Protocol::Proto(k) => k.to_str().unwrap_or("<bug>"),
113
55
            Protocol::Unrecognized(s) => s,
114
        }
115
55
    }
116
}
117

            
118
/// Representation of a set of versions supported by a protocol.
119
///
120
/// For now, we only use this type for unrecognized protocols.
121
#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
122
struct SubprotocolEntry {
123
    /// Which protocol's versions does this describe?
124
    proto: Protocol,
125
    /// A bit-vector defining which versions are supported.  If bit
126
    /// `(1<<i)` is set, then protocol version `i` is supported.
127
    supported: u64,
128
}
129

            
130
/// A set of supported or required subprotocol versions.
131
///
132
/// This type supports both recognized subprotocols (listed in ProtoKind),
133
/// and unrecognized subprotocols (stored by name).
134
///
135
/// To construct an instance, use the FromStr trait:
136
/// ```
137
/// use tor_protover::Protocols;
138
/// let p: Result<Protocols,_> = "Link=1-3 LinkAuth=2-3 Relay=1-2".parse();
139
/// ```
140
#[derive(Debug, Clone, Default, Eq, PartialEq, Hash)]
141
pub struct Protocols {
142
    /// A mapping from protocols' integer encodings to bit-vectors.
143
    recognized: [u64; N_RECOGNIZED],
144
    /// A vector of unrecognized protocol versions.
145
    unrecognized: Vec<SubprotocolEntry>,
146
}
147

            
148
impl Protocols {
149
    /// Return a new empty set of protocol versions.
150
380661
    pub fn new() -> Self {
151
380661
        Protocols::default()
152
380661
    }
153
    /// Helper: return true iff this protocol set contains the
154
    /// version `ver` of the protocol represented by the integer `proto`.
155
1122628
    fn supports_recognized_ver(&self, proto: usize, ver: u8) -> bool {
156
1122628
        if ver > 63 {
157
2
            return false;
158
1122626
        }
159
1122626
        if proto >= self.recognized.len() {
160
            return false;
161
1122626
        }
162
1122626
        (self.recognized[proto] & (1 << ver)) != 0
163
1122628
    }
164
    /// Helper: return true iff this protocol set contains version
165
    /// `ver` of the unrecognized protocol represented by the string
166
    /// `proto`.
167
    ///
168
    /// Requires that `proto` is not the name of a recognized protocol.
169
10
    fn supports_unrecognized_ver(&self, proto: &str, ver: u8) -> bool {
170
10
        if ver > 63 {
171
2
            return false;
172
8
        }
173
8
        let ent = self
174
8
            .unrecognized
175
8
            .iter()
176
12
            .find(|ent| ent.proto.is_unrecognized(proto));
177
8
        match ent {
178
4
            Some(e) => (e.supported & (1 << ver)) != 0,
179
4
            None => false,
180
        }
181
10
    }
182
    // TODO: Combine these next two functions into one by using a trait.
183
    /// Check whether a known protocol version is supported.
184
    ///
185
    /// ```
186
    /// use tor_protover::*;
187
    /// let protos: Protocols = "Link=1-3 HSDir=2,4-5".parse().unwrap();
188
    ///
189
    /// assert!(protos.supports_known_subver(ProtoKind::Link, 2));
190
    /// assert!(protos.supports_known_subver(ProtoKind::HSDir, 4));
191
    /// assert!(! protos.supports_known_subver(ProtoKind::HSDir, 3));
192
    /// assert!(! protos.supports_known_subver(ProtoKind::LinkAuth, 3));
193
    /// ```
194
1122522
    pub fn supports_known_subver(&self, proto: ProtoKind, ver: u8) -> bool {
195
1122522
        self.supports_recognized_ver(proto.get() as usize, ver)
196
1122522
    }
197
    /// Check whether a protocol version identified by a string is supported.
198
    ///
199
    /// ```
200
    /// use tor_protover::*;
201
    /// let protos: Protocols = "Link=1-3 Foobar=7".parse().unwrap();
202
    ///
203
    /// assert!(protos.supports_subver("Link", 2));
204
    /// assert!(protos.supports_subver("Foobar", 7));
205
    /// assert!(! protos.supports_subver("Link", 5));
206
    /// assert!(! protos.supports_subver("Foobar", 6));
207
    /// assert!(! protos.supports_subver("Wombat", 3));
208
    /// ```
209
116
    pub fn supports_subver(&self, proto: &str, ver: u8) -> bool {
210
116
        match ProtoKind::from_name(proto) {
211
106
            Some(p) => self.supports_recognized_ver(p.get() as usize, ver),
212
10
            None => self.supports_unrecognized_ver(proto, ver),
213
        }
214
116
    }
215

            
216
    /// Parsing helper: Try to add a new entry `ent` to this set of protocols.
217
    ///
218
    /// Uses `foundmask`, a bit mask saying which recognized protocols
219
    /// we've already found entries for.  Returns an error if `ent` is
220
    /// for a protocol we've already added.
221
278647
    fn add(&mut self, foundmask: &mut u64, ent: SubprotocolEntry) -> Result<(), ParseError> {
222
278647
        match ent.proto {
223
269712
            Protocol::Proto(k) => {
224
269712
                let idx = k.get() as usize;
225
269712
                let bit = 1 << u64::from(k.get());
226
269712
                if (*foundmask & bit) != 0 {
227
4
                    return Err(ParseError::Duplicate);
228
269708
                }
229
269708
                *foundmask |= bit;
230
269708
                self.recognized[idx] = ent.supported;
231
            }
232
8935
            Protocol::Unrecognized(ref s) => {
233
8935
                if self
234
8935
                    .unrecognized
235
8935
                    .iter()
236
8938
                    .any(|ent| ent.proto.is_unrecognized(s))
237
                {
238
2
                    return Err(ParseError::Duplicate);
239
8933
                }
240
8933
                self.unrecognized.push(ent);
241
            }
242
        }
243
278641
        Ok(())
244
278647
    }
245
}
246

            
247
/// An error representing a failure to parse a set of protocol versions.
248
#[derive(Error, Debug, PartialEq, Eq, Clone)]
249
#[non_exhaustive]
250
pub enum ParseError {
251
    /// A protocol version was not in the range 0..=63.
252
    #[error("Protocol version out of range")]
253
    OutOfRange,
254
    /// Some subprotocol or protocol version appeared more than once.
255
    #[error("Duplicate protocol entry")]
256
    Duplicate,
257
    /// The list of protocol versions was malformed in some other way.
258
    #[error("Malformed protocol entry")]
259
    Malformed,
260
}
261

            
262
/// Helper: return a new u64 in which bits `lo` through `hi` inclusive
263
/// are set to 1, and all the other bits are set to 0.
264
///
265
/// In other words, `bitrange(a,b)` is how we represent the range of
266
/// versions `a-b` in a protocol version bitmask.
267
///
268
/// ```ignore
269
/// # use tor_protover::bitrange;
270
/// assert_eq!(bitrange(0, 5), 0b111111);
271
/// assert_eq!(bitrange(2, 5), 0b111100);
272
/// assert_eq!(bitrange(2, 7), 0b11111100);
273
/// ```
274
284697
fn bitrange(lo: u64, hi: u64) -> u64 {
275
284697
    assert!(lo <= hi && lo <= 63 && hi <= 63);
276
284697
    let mut mask = !0;
277
284697
    mask <<= 63 - hi;
278
284697
    mask >>= 63 - hi + lo;
279
284697
    mask <<= lo;
280
284697
    mask
281
284697
}
282

            
283
/// Helper: return true if the provided string is a valid "integer"
284
/// in the form accepted by the protover spec.  This is stricter than
285
/// rust's integer parsing format.
286
569416
fn is_good_number(n: &str) -> bool {
287
580671
    n.chars().all(|ch| ch.is_ascii_digit()) && !n.starts_with('0')
288
569416
}
289

            
290
/// A single SubprotocolEntry is parsed from a string of the format
291
/// Name=Versions, where Versions is a comma-separated list of
292
/// integers or ranges of integers.
293
impl std::str::FromStr for SubprotocolEntry {
294
    type Err = ParseError;
295

            
296
278677
    fn from_str(s: &str) -> Result<Self, ParseError> {
297
        // split the string on the =.
298
278675
        let (name, versions) = {
299
278677
            let eq_idx = s.find('=').ok_or(ParseError::Malformed)?;
300
278675
            (&s[..eq_idx], &s[eq_idx + 1..])
301
        };
302
        // Look up the protocol by name.
303
278675
        let proto = match ProtoKind::from_name(name) {
304
269738
            Some(p) => Protocol::Proto(p),
305
8937
            None => Protocol::Unrecognized(name.to_string()),
306
        };
307
278675
        if versions.is_empty() {
308
            // We need to handle this case specially, since otherwise
309
            // it would be treated below as a single empty value, which
310
            // would be rejected.
311
2
            return Ok(SubprotocolEntry {
312
2
                proto,
313
2
                supported: 0,
314
2
            });
315
278673
        }
316
278673
        // Construct a bitmask based on the comma-separated versions.
317
278673
        let mut supported = 0_u64;
318
284713
        for ent in versions.split(',') {
319
            // Find and parse lo and hi for a single range of versions.
320
            // (If this is not a range, but rather a single version v,
321
            // treat it as if it were a range v-v.)
322
284713
            let (lo_s, hi_s) = {
323
284713
                match ent.find('-') {
324
59847
                    Some(pos) => (&ent[..pos], &ent[pos + 1..]),
325
224866
                    None => (ent, ent),
326
                }
327
            };
328
284713
            if !is_good_number(lo_s) {
329
10
                return Err(ParseError::Malformed);
330
284703
            }
331
284703
            if !is_good_number(hi_s) {
332
2
                return Err(ParseError::Malformed);
333
284701
            }
334
284702
            let lo: u64 = lo_s.parse().map_err(|_| ParseError::Malformed)?;
335
284701
            let hi: u64 = hi_s.parse().map_err(|_| ParseError::Malformed)?;
336
            // Make sure that lo and hi are in-bounds and consistent.
337
284695
            if lo > 63 || hi > 63 {
338
6
                return Err(ParseError::OutOfRange);
339
284689
            }
340
284689
            if lo > hi {
341
2
                return Err(ParseError::Malformed);
342
284687
            }
343
284687
            let mask = bitrange(lo, hi);
344
284687
            // Make sure that no version is included twice.
345
284687
            if (supported & mask) != 0 {
346
2
                return Err(ParseError::Duplicate);
347
284685
            }
348
284685
            // Add the appropriate bits to the mask.
349
284685
            supported |= mask;
350
        }
351
278645
        Ok(SubprotocolEntry { proto, supported })
352
278677
    }
353
}
354

            
355
/// A Protocols set can be parsed from a string according to the
356
/// format used in Tor consensus documents.
357
///
358
/// A protocols set is represented by a space-separated list of
359
/// entries.  Each entry is of the form `Name=Versions`, where `Name`
360
/// is the name of a protocol, and `Versions` is a comma-separated
361
/// list of version numbers and version ranges.  Each version range is
362
/// a pair of integers separated by `-`.
363
///
364
/// No protocol name may be listed twice.  No version may be listed
365
/// twice for a single protocol.  All versions must be in range 0
366
/// through 63 inclusive.
367
impl std::str::FromStr for Protocols {
368
    type Err = ParseError;
369

            
370
380661
    fn from_str(s: &str) -> Result<Self, ParseError> {
371
380661
        let mut result = Protocols::new();
372
380661
        let mut foundmask = 0_u64;
373
464017
        for ent in s.split(' ') {
374
464017
            if ent.is_empty() {
375
185340
                continue;
376
278677
            }
377

            
378
278677
            let s: SubprotocolEntry = ent.parse()?;
379
278647
            result.add(&mut foundmask, s)?;
380
        }
381
380625
        result.unrecognized.sort();
382
380625
        Ok(result)
383
380661
    }
384
}
385

            
386
/// Given a bitmask, return a list of the bits set in the mask, as a
387
/// String in the format expected by Tor consensus documents.
388
///
389
/// This implementation constructs ranges greedily.  For example, the
390
/// bitmask `0b0111011` will be represented as `0-1,3-5`, and not
391
/// `0,1,3,4,5` or `0,1,3-5`.
392
///
393
/// ```ignore
394
/// # use tor_protover::dumpmask;
395
/// assert_eq!(dumpmask(0b111111), "0-5");
396
/// assert_eq!(dumpmask(0b111100), "2-5");
397
/// assert_eq!(dumpmask(0b11111100), "2-7");
398
/// ```
399
687
fn dumpmask(mut mask: u64) -> String {
400
687
    /// Helper: push a range (which may be a singleton) onto `v`.
401
744
    fn append(v: &mut Vec<String>, lo: u32, hi: u32) {
402
744
        if lo == hi {
403
320
            v.push(lo.to_string());
404
424
        } else {
405
424
            v.push(format!("{}-{}", lo, hi));
406
424
        }
407
744
    }
408
687
    // We'll be building up our result here, then joining it with
409
687
    // commas.
410
687
    let mut result = Vec::new();
411
687
    // This implementation is a little tricky, but it should be more
412
687
    // efficient than a raw search.  Basically, we're using the
413
687
    // function u64::trailing_zeros to count how large each range of
414
687
    // 1s or 0s is, and then shifting by that amount.
415
687

            
416
687
    // How many bits have we already shifted `mask`?
417
687
    let mut shift = 0;
418
1429
    while mask != 0 {
419
744
        let zeros = mask.trailing_zeros();
420
744
        mask >>= zeros;
421
744
        shift += zeros;
422
744
        let ones = mask.trailing_ones();
423
744
        append(&mut result, shift, shift + ones - 1);
424
744
        shift += ones;
425
744
        if ones == 64 {
426
            // We have to do this check to avoid overflow when formatting
427
            // the range `0-63`.
428
2
            break;
429
742
        }
430
742
        mask >>= ones;
431
    }
432
687
    result.join(",")
433
687
}
434

            
435
/// The Display trait formats a protocol set in the format expected by Tor
436
/// consensus documents.
437
///
438
/// ```
439
/// use tor_protover::*;
440
/// let protos: Protocols = "Link=1,2,3 Foobar=7 Relay=2".parse().unwrap();
441
/// assert_eq!(format!("{}", protos),
442
///            "Foobar=7 Link=1-3 Relay=2");
443
/// ```
444
impl std::fmt::Display for Protocols {
445
112
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
446
112
        let mut entries = Vec::new();
447
1344
        for (idx, mask) in self.recognized.iter().enumerate() {
448
1344
            if *mask != 0 {
449
622
                let pk: ProtoKind = (idx as u16).into();
450
622
                entries.push(format!("{}={}", pk, dumpmask(*mask)));
451
722
            }
452
        }
453
167
        for ent in &self.unrecognized {
454
55
            if ent.supported != 0 {
455
55
                entries.push(format!(
456
55
                    "{}={}",
457
55
                    ent.proto.to_str(),
458
55
                    dumpmask(ent.supported)
459
55
                ));
460
55
            }
461
        }
462
        // This sort is required.
463
112
        entries.sort();
464
112
        write!(f, "{}", entries.join(" "))
465
112
    }
466
}
467

            
468
#[cfg(test)]
469
mod test {
470
    // @@ begin test lint list maintained by maint/add_warning @@
471
    #![allow(clippy::bool_assert_comparison)]
472
    #![allow(clippy::clone_on_copy)]
473
    #![allow(clippy::dbg_macro)]
474
    #![allow(clippy::mixed_attributes_style)]
475
    #![allow(clippy::print_stderr)]
476
    #![allow(clippy::print_stdout)]
477
    #![allow(clippy::single_char_pattern)]
478
    #![allow(clippy::unwrap_used)]
479
    #![allow(clippy::unchecked_duration_subtraction)]
480
    #![allow(clippy::useless_vec)]
481
    #![allow(clippy::needless_pass_by_value)]
482
    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
483
    use super::*;
484

            
485
    #[test]
486
    fn test_bitrange() {
487
        assert_eq!(0b1, bitrange(0, 0));
488
        assert_eq!(0b10, bitrange(1, 1));
489
        assert_eq!(0b11, bitrange(0, 1));
490
        assert_eq!(0b1111110000000, bitrange(7, 12));
491
        assert_eq!(!0, bitrange(0, 63));
492
    }
493

            
494
    #[test]
495
    fn test_dumpmask() {
496
        assert_eq!("", dumpmask(0));
497
        assert_eq!("0-5", dumpmask(0b111111));
498
        assert_eq!("4-5", dumpmask(0b110000));
499
        assert_eq!("1,4-5", dumpmask(0b110010));
500
        assert_eq!("0-63", dumpmask(!0));
501
    }
502

            
503
    #[test]
504
    fn test_canonical() -> Result<(), ParseError> {
505
        fn t(orig: &str, canonical: &str) -> Result<(), ParseError> {
506
            let protos: Protocols = orig.parse()?;
507
            let enc = format!("{}", protos);
508
            assert_eq!(enc, canonical);
509
            Ok(())
510
        }
511

            
512
        t("", "")?;
513
        t(" ", "")?;
514
        t("Link=5,6,7,9 Relay=4-7,2", "Link=5-7,9 Relay=2,4-7")?;
515
        t("FlowCtrl= Padding=8,7 Desc=1-5,6-8", "Desc=1-8 Padding=7-8")?;
516
        t("Zelda=7 Gannon=3,6 Link=4", "Gannon=3,6 Link=4 Zelda=7")?;
517

            
518
        Ok(())
519
    }
520

            
521
    #[test]
522
    fn test_invalid() {
523
        fn t(s: &str) -> ParseError {
524
            let protos: Result<Protocols, ParseError> = s.parse();
525
            assert!(protos.is_err());
526
            protos.err().unwrap()
527
        }
528

            
529
        assert_eq!(t("Link=1-100"), ParseError::OutOfRange);
530
        assert_eq!(t("Zelda=100"), ParseError::OutOfRange);
531
        assert_eq!(t("Link=100-200"), ParseError::OutOfRange);
532

            
533
        assert_eq!(t("Link=1,1"), ParseError::Duplicate);
534
        assert_eq!(t("Link=1 Link=1"), ParseError::Duplicate);
535
        assert_eq!(t("Link=1 Link=3"), ParseError::Duplicate);
536
        assert_eq!(t("Zelda=1 Zelda=3"), ParseError::Duplicate);
537

            
538
        assert_eq!(t("Link=Zelda"), ParseError::Malformed);
539
        assert_eq!(t("Link=6-2"), ParseError::Malformed);
540
        assert_eq!(t("Link=6-"), ParseError::Malformed);
541
        assert_eq!(t("Link=6-,2"), ParseError::Malformed);
542
        assert_eq!(t("Link=1,,2"), ParseError::Malformed);
543
        assert_eq!(t("Link=6-frog"), ParseError::Malformed);
544
        assert_eq!(t("Link=gannon-9"), ParseError::Malformed);
545
        assert_eq!(t("Link Zelda"), ParseError::Malformed);
546

            
547
        assert_eq!(t("Link=01"), ParseError::Malformed);
548
        assert_eq!(t("Link=waffle"), ParseError::Malformed);
549
        assert_eq!(t("Link=1_1"), ParseError::Malformed);
550
    }
551

            
552
    #[test]
553
    fn test_supports() -> Result<(), ParseError> {
554
        let p: Protocols = "Link=4,5-7 Padding=2 Lonk=1-3,5".parse()?;
555

            
556
        assert!(p.supports_known_subver(ProtoKind::Padding, 2));
557
        assert!(!p.supports_known_subver(ProtoKind::Padding, 1));
558
        assert!(p.supports_known_subver(ProtoKind::Link, 6));
559
        assert!(!p.supports_known_subver(ProtoKind::Link, 255));
560
        assert!(!p.supports_known_subver(ProtoKind::Cons, 1));
561
        assert!(!p.supports_known_subver(ProtoKind::Cons, 0));
562
        assert!(p.supports_subver("Link", 6));
563
        assert!(!p.supports_subver("link", 6));
564
        assert!(!p.supports_subver("Cons", 0));
565
        assert!(p.supports_subver("Lonk", 3));
566
        assert!(!p.supports_subver("Lonk", 4));
567
        assert!(!p.supports_subver("lonk", 3));
568
        assert!(!p.supports_subver("Lonk", 64));
569

            
570
        Ok(())
571
    }
572
}