tor_protover/
lib.rs

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#![deny(clippy::mod_module_files)]
38#![allow(clippy::let_unit_value)] // This can reasonably be done for explicitness
39#![allow(clippy::uninlined_format_args)]
40#![allow(clippy::significant_drop_in_scrutinee)] // arti/-/merge_requests/588/#note_2812945
41#![allow(clippy::result_large_err)] // temporary workaround for arti#587
42#![allow(clippy::needless_raw_string_hashes)] // complained-about code is fine, often best
43#![allow(clippy::needless_lifetimes)] // See arti#1765
44#![allow(mismatched_lifetime_syntaxes)] // temporary workaround for arti#2060
45//! <!-- @@ end lint list maintained by maint/add_warning @@ -->
46
47#![allow(non_upper_case_globals)]
48#![allow(clippy::upper_case_acronyms)]
49
50use caret::caret_int;
51
52use thiserror::Error;
53
54pub mod named;
55
56caret_int! {
57    /// A recognized subprotocol.
58    ///
59    /// These names are kept in sync with the names used in consensus
60    /// documents; the values are kept in sync with the values in the
61    /// cbor document format in the walking onions proposal.
62    ///
63    /// For the full semantics of each subprotocol, see tor-spec.txt.
64    #[derive(Hash,Ord,PartialOrd)]
65    pub struct ProtoKind(u8) {
66        /// Initiating and receiving channels, and getting cells on them.
67        Link = 0,
68        /// Different kinds of authenticate cells
69        LinkAuth = 1,
70        /// CREATE cells, CREATED cells, and the encryption that they
71        /// create.
72        Relay = 2,
73        /// Serving and fetching network directory documents.
74        DirCache = 3,
75        /// Serving onion service descriptors
76        HSDir = 4,
77        /// Providing an onion service introduction point
78        HSIntro = 5,
79        /// Providing an onion service rendezvous point
80        HSRend = 6,
81        /// Describing a relay's functionality using router descriptors.
82        Desc = 7,
83        /// Describing a relay's functionality using microdescriptors.
84        Microdesc = 8,
85        /// Describing the network as a consensus directory document.
86        Cons = 9,
87        /// Sending and accepting circuit-level padding
88        Padding = 10,
89        /// Improved means of flow control on circuits.
90        FlowCtrl = 11,
91        /// Multi-path circuit support.
92        Conflux = 12,
93    }
94}
95
96/// How many recognized protocols are there?
97const N_RECOGNIZED: usize = 13;
98
99/// Maximum allowable value for a protocol's version field.
100const MAX_VER: usize = 63;
101
102/// A specific, named subversion of a protocol.
103#[derive(Eq, PartialEq, Copy, Clone, Debug)]
104pub struct NamedSubver {
105    /// The protocol in question
106    ///
107    /// Must be in-range for ProtoKind (0..N_RECOGNIZED).
108    kind: ProtoKind,
109    /// The version of the protocol
110    ///
111    /// Must be in 0..=MAX_VER
112    version: u8,
113}
114
115impl NamedSubver {
116    /// Create a new NamedSubver.
117    ///
118    /// # Panics
119    ///
120    /// Panics if `kind` is unrecognized or `version` is invalid.
121    const fn new(kind: ProtoKind, version: u8) -> Self {
122        assert!((kind.0 as usize) < N_RECOGNIZED);
123        assert!((version as usize) <= MAX_VER);
124        Self { kind, version }
125    }
126}
127
128/// A subprotocol capability as represented by a (kind, version) tuple.
129///
130/// Does not necessarily represent a real subprotocol capability;
131/// this type is meant for use in other pieces of the protocol.
132///
133/// # Ordering
134///
135/// Instances of `NumberedSubver` are sorted in lexicographic order by
136/// their (kind, version) tuples.
137//
138// TODO: As with most other types in the crate, we should decide how to rename them as as part
139// of #1934.
140#[derive(Clone, Copy, Debug, Ord, PartialOrd, Eq, PartialEq)]
141pub struct NumberedSubver {
142    /// The protocol in question
143    kind: ProtoKind,
144    /// The version of the protocol
145    version: u8,
146}
147
148impl NumberedSubver {
149    /// Construct a new [`NumberedSubver`]
150    pub fn new(kind: impl Into<ProtoKind>, version: u8) -> Self {
151        Self {
152            kind: kind.into(),
153            version,
154        }
155    }
156    /// Return the ProtoKind and version for this [`NumberedSubver`].
157    pub fn into_parts(self) -> (ProtoKind, u8) {
158        (self.kind, self.version)
159    }
160}
161impl From<NamedSubver> for NumberedSubver {
162    fn from(value: NamedSubver) -> Self {
163        Self {
164            kind: value.kind,
165            version: value.version,
166        }
167    }
168}
169
170#[cfg(feature = "tor-bytes")]
171impl tor_bytes::Readable for NumberedSubver {
172    fn take_from(b: &mut tor_bytes::Reader<'_>) -> tor_bytes::Result<Self> {
173        let kind = b.take_u8()?;
174        let version = b.take_u8()?;
175        Ok(Self::new(kind, version))
176    }
177}
178
179#[cfg(feature = "tor-bytes")]
180impl tor_bytes::Writeable for NumberedSubver {
181    fn write_onto<B: tor_bytes::Writer + ?Sized>(&self, b: &mut B) -> tor_bytes::EncodeResult<()> {
182        b.write_u8(self.kind.into());
183        b.write_u8(self.version);
184        Ok(())
185    }
186}
187
188/// Representation for a known or unknown protocol.
189#[derive(Eq, PartialEq, Clone, Debug, Hash, Ord, PartialOrd)]
190enum Protocol {
191    /// A known protocol; represented by one of ProtoKind.
192    ///
193    /// ProtoKind must always be in the range 0..N_RECOGNIZED.
194    Proto(ProtoKind),
195    /// An unknown protocol; represented by its name.
196    Unrecognized(String),
197}
198
199impl Protocol {
200    /// Return true iff `s` is the name of a protocol we do not recognize.
201    fn is_unrecognized(&self, s: &str) -> bool {
202        match self {
203            Protocol::Unrecognized(s2) => s2 == s,
204            _ => false,
205        }
206    }
207    /// Return a string representation of this protocol.
208    fn to_str(&self) -> &str {
209        match self {
210            Protocol::Proto(k) => k.to_str().unwrap_or("<bug>"),
211            Protocol::Unrecognized(s) => s,
212        }
213    }
214}
215
216/// Representation of a set of versions supported by a protocol.
217///
218/// For now, we only use this type for unrecognized protocols.
219#[derive(Debug, Clone, PartialEq, Eq, Hash, Ord, PartialOrd)]
220struct SubprotocolEntry {
221    /// Which protocol's versions does this describe?
222    proto: Protocol,
223    /// A bit-vector defining which versions are supported.  If bit
224    /// `(1<<i)` is set, then protocol version `i` is supported.
225    supported: u64,
226}
227
228/// A set of supported or required subprotocol versions.
229///
230/// This type supports both recognized subprotocols (listed in ProtoKind),
231/// and unrecognized subprotocols (stored by name).
232///
233/// To construct an instance, use the FromStr trait:
234/// ```
235/// use tor_protover::Protocols;
236/// let p: Result<Protocols,_> = "Link=1-3 LinkAuth=2-3 Relay=1-2".parse();
237/// ```
238#[derive(Debug, Clone, Default, Eq, PartialEq, Hash)]
239#[cfg_attr(
240    feature = "serde",
241    derive(serde_with::DeserializeFromStr, serde_with::SerializeDisplay)
242)]
243pub struct Protocols {
244    /// A mapping from protocols' integer encodings to bit-vectors.
245    recognized: [u64; N_RECOGNIZED],
246    /// A vector of unrecognized protocol versions,
247    /// in sorted order.
248    ///
249    /// Every entry in this list has supported != 0.
250    unrecognized: Vec<SubprotocolEntry>,
251}
252
253impl Protocols {
254    /// Return a new empty set of protocol versions.
255    pub fn new() -> Self {
256        Protocols::default()
257    }
258    /// Helper: return true iff this protocol set contains the
259    /// version `ver` of the protocol represented by the integer `proto`.
260    fn supports_recognized_ver(&self, proto: usize, ver: u8) -> bool {
261        if usize::from(ver) > MAX_VER {
262            return false;
263        }
264        if proto >= self.recognized.len() {
265            return false;
266        }
267        (self.recognized[proto] & (1 << ver)) != 0
268    }
269    /// Helper: return true iff this protocol set contains version
270    /// `ver` of the unrecognized protocol represented by the string
271    /// `proto`.
272    ///
273    /// Requires that `proto` is not the name of a recognized protocol.
274    fn supports_unrecognized_ver(&self, proto: &str, ver: u8) -> bool {
275        if usize::from(ver) > MAX_VER {
276            return false;
277        }
278        let ent = self
279            .unrecognized
280            .iter()
281            .find(|ent| ent.proto.is_unrecognized(proto));
282        match ent {
283            Some(e) => (e.supported & (1 << ver)) != 0,
284            None => false,
285        }
286    }
287
288    /// Return true if this list of protocols is empty.
289    pub fn is_empty(&self) -> bool {
290        self.recognized.iter().all(|v| *v == 0)
291            && self.unrecognized.iter().all(|p| p.supported == 0)
292    }
293
294    // TODO: Combine these next two functions into one by using a trait.
295    /// Check whether a known protocol version is supported.
296    ///
297    /// ```
298    /// use tor_protover::*;
299    /// let protos: Protocols = "Link=1-3 HSDir=2,4-5".parse().unwrap();
300    ///
301    /// assert!(protos.supports_known_subver(ProtoKind::Link, 2));
302    /// assert!(protos.supports_known_subver(ProtoKind::HSDir, 4));
303    /// assert!(! protos.supports_known_subver(ProtoKind::HSDir, 3));
304    /// assert!(! protos.supports_known_subver(ProtoKind::LinkAuth, 3));
305    /// ```
306    pub fn supports_known_subver(&self, proto: ProtoKind, ver: u8) -> bool {
307        self.supports_recognized_ver(proto.get() as usize, ver)
308    }
309    /// Check whether a protocol version identified by a string is supported.
310    ///
311    /// ```
312    /// use tor_protover::*;
313    /// let protos: Protocols = "Link=1-3 Foobar=7".parse().unwrap();
314    ///
315    /// assert!(protos.supports_subver("Link", 2));
316    /// assert!(protos.supports_subver("Foobar", 7));
317    /// assert!(! protos.supports_subver("Link", 5));
318    /// assert!(! protos.supports_subver("Foobar", 6));
319    /// assert!(! protos.supports_subver("Wombat", 3));
320    /// ```
321    pub fn supports_subver(&self, proto: &str, ver: u8) -> bool {
322        match ProtoKind::from_name(proto) {
323            Some(p) => self.supports_recognized_ver(p.get() as usize, ver),
324            None => self.supports_unrecognized_ver(proto, ver),
325        }
326    }
327
328    /// Check whether a protocol version is supported.
329    ///
330    /// ```
331    /// use tor_protover::*;
332    /// let protos: Protocols = "Link=1-5 Desc=2-4".parse().unwrap();
333    /// assert!(protos.supports_named_subver(named::DESC_FAMILY_IDS)); // Desc=4
334    /// assert!(! protos.supports_named_subver(named::CONFLUX_BASE)); // Conflux=1
335    /// ```
336    pub fn supports_named_subver(&self, protover: NamedSubver) -> bool {
337        self.supports_known_subver(protover.kind, protover.version)
338    }
339
340    /// Check whether a numbered subprotocol capability is supported.
341    ///
342    /// ```
343    /// use tor_protover::*;
344    /// let protos: Protocols = "Link=1-5 Desc=2-4".parse().unwrap();
345    /// assert!(protos.supports_numbered_subver(NumberedSubver::new(ProtoKind::Desc, 4)));
346    /// assert!(! protos.supports_numbered_subver(NumberedSubver::new(ProtoKind::Conflux, 1)));
347    /// ```
348    pub fn supports_numbered_subver(&self, protover: NumberedSubver) -> bool {
349        self.supports_known_subver(protover.kind, protover.version)
350    }
351
352    /// Return a Protocols holding every protocol flag that is present in `self`
353    /// but not `other`.
354    ///
355    /// ```
356    /// use tor_protover::*;
357    /// let protos: Protocols = "Desc=2-4 Microdesc=1-5".parse().unwrap();
358    /// let protos2: Protocols = "Desc=3 Microdesc=3".parse().unwrap();
359    /// assert_eq!(protos.difference(&protos2),
360    ///            "Desc=2,4 Microdesc=1-2,4-5".parse().unwrap());
361    /// ```
362    pub fn difference(&self, other: &Protocols) -> Protocols {
363        let mut r = Protocols::default();
364
365        for i in 0..N_RECOGNIZED {
366            r.recognized[i] = self.recognized[i] & !other.recognized[i];
367        }
368        // This is not super efficient, but we don't have to do it often.
369        for ent in self.unrecognized.iter() {
370            let mut ent = ent.clone();
371            if let Some(other_ent) = other.unrecognized.iter().find(|e| e.proto == ent.proto) {
372                ent.supported &= !other_ent.supported;
373            }
374            if ent.supported != 0 {
375                r.unrecognized.push(ent);
376            }
377        }
378        r
379    }
380
381    /// Return a Protocols holding every protocol flag that is present in `self`
382    /// or `other` or both.
383    ///
384    /// ```
385    /// use tor_protover::*;
386    /// let protos: Protocols = "Desc=2-4 Microdesc=1-5".parse().unwrap();
387    /// let protos2: Protocols = "Desc=3 Microdesc=10".parse().unwrap();
388    /// assert_eq!(protos.union(&protos2),
389    ///            "Desc=2-4 Microdesc=1-5,10".parse().unwrap());
390    /// ```
391    pub fn union(&self, other: &Protocols) -> Protocols {
392        let mut r = self.clone();
393        for i in 0..N_RECOGNIZED {
394            r.recognized[i] |= other.recognized[i];
395        }
396        for ent in other.unrecognized.iter() {
397            if let Some(my_ent) = r.unrecognized.iter_mut().find(|e| e.proto == ent.proto) {
398                my_ent.supported |= ent.supported;
399            } else {
400                r.unrecognized.push(ent.clone());
401            }
402        }
403        r.unrecognized.sort();
404        r
405    }
406
407    /// Return a Protocols holding every protocol flag that is present in both `self`
408    /// and `other`.
409    ///
410    /// ```
411    /// use tor_protover::*;
412    /// let protos: Protocols = "Desc=2-4 Microdesc=1-5".parse().unwrap();
413    /// let protos2: Protocols = "Desc=3 Microdesc=10".parse().unwrap();
414    /// assert_eq!(protos.intersection(&protos2),
415    ///            "Desc=3".parse().unwrap());
416    /// ```
417    pub fn intersection(&self, other: &Protocols) -> Protocols {
418        let mut r = Protocols::default();
419        for i in 0..N_RECOGNIZED {
420            r.recognized[i] = self.recognized[i] & other.recognized[i];
421        }
422        for ent in self.unrecognized.iter() {
423            if let Some(other_ent) = other.unrecognized.iter().find(|e| e.proto == ent.proto) {
424                let supported = ent.supported & other_ent.supported;
425                if supported != 0 {
426                    r.unrecognized.push(SubprotocolEntry {
427                        proto: ent.proto.clone(),
428                        supported,
429                    });
430                }
431            }
432        }
433        r.unrecognized.sort();
434        r
435    }
436
437    /// Parsing helper: Try to add a new entry `ent` to this set of protocols.
438    ///
439    /// Uses `foundmask`, a bit mask saying which recognized protocols
440    /// we've already found entries for.  Returns an error if `ent` is
441    /// for a protocol we've already added.
442    ///
443    /// Does not preserve sorting order; the caller must call `self.unrecognized.sort()` before returning.
444    fn add(&mut self, foundmask: &mut u64, ent: SubprotocolEntry) -> Result<(), ParseError> {
445        match ent.proto {
446            Protocol::Proto(k) => {
447                let idx = k.get() as usize;
448                assert!(idx < N_RECOGNIZED); // guaranteed by invariant on Protocol::Proto
449                let bit = 1 << u64::from(k.get());
450                if (*foundmask & bit) != 0 {
451                    return Err(ParseError::Duplicate);
452                }
453                *foundmask |= bit;
454                self.recognized[idx] = ent.supported;
455            }
456            Protocol::Unrecognized(ref s) => {
457                if self
458                    .unrecognized
459                    .iter()
460                    .any(|ent| ent.proto.is_unrecognized(s))
461                {
462                    return Err(ParseError::Duplicate);
463                }
464                if ent.supported != 0 {
465                    self.unrecognized.push(ent);
466                }
467            }
468        }
469        Ok(())
470    }
471}
472
473/// An error representing a failure to parse a set of protocol versions.
474#[derive(Error, Debug, PartialEq, Eq, Clone)]
475#[non_exhaustive]
476pub enum ParseError {
477    /// A protocol version was not in the range 0..=63.
478    #[error("Protocol version out of range")]
479    OutOfRange,
480    /// Some subprotocol or protocol version appeared more than once.
481    #[error("Duplicate protocol entry")]
482    Duplicate,
483    /// The list of protocol versions was malformed in some other way.
484    #[error("Malformed protocol entry")]
485    Malformed,
486}
487
488/// Helper: return a new u64 in which bits `lo` through `hi` inclusive
489/// are set to 1, and all the other bits are set to 0.
490///
491/// In other words, `bitrange(a,b)` is how we represent the range of
492/// versions `a-b` in a protocol version bitmask.
493///
494/// ```ignore
495/// # use tor_protover::bitrange;
496/// assert_eq!(bitrange(0, 5), 0b111111);
497/// assert_eq!(bitrange(2, 5), 0b111100);
498/// assert_eq!(bitrange(2, 7), 0b11111100);
499/// ```
500fn bitrange(lo: u64, hi: u64) -> u64 {
501    assert!(lo <= hi && lo <= 63 && hi <= 63);
502    let mut mask = !0;
503    mask <<= 63 - hi;
504    mask >>= 63 - hi + lo;
505    mask <<= lo;
506    mask
507}
508
509/// Helper: return true if the provided string is a valid "integer"
510/// in the form accepted by the protover spec.  This is stricter than
511/// rust's integer parsing format.
512fn is_good_number(n: &str) -> bool {
513    n.chars().all(|ch| ch.is_ascii_digit()) && !n.starts_with('0')
514}
515
516/// A single SubprotocolEntry is parsed from a string of the format
517/// Name=Versions, where Versions is a comma-separated list of
518/// integers or ranges of integers.
519impl std::str::FromStr for SubprotocolEntry {
520    type Err = ParseError;
521
522    fn from_str(s: &str) -> Result<Self, ParseError> {
523        // split the string on the =.
524        let (name, versions) = {
525            let eq_idx = s.find('=').ok_or(ParseError::Malformed)?;
526            (&s[..eq_idx], &s[eq_idx + 1..])
527        };
528        // Look up the protocol by name.
529        let proto = match ProtoKind::from_name(name) {
530            Some(p) => Protocol::Proto(p),
531            None => Protocol::Unrecognized(name.to_string()),
532        };
533        if versions.is_empty() {
534            // We need to handle this case specially, since otherwise
535            // it would be treated below as a single empty value, which
536            // would be rejected.
537            return Ok(SubprotocolEntry {
538                proto,
539                supported: 0,
540            });
541        }
542        // Construct a bitmask based on the comma-separated versions.
543        let mut supported = 0_u64;
544        for ent in versions.split(',') {
545            // Find and parse lo and hi for a single range of versions.
546            // (If this is not a range, but rather a single version v,
547            // treat it as if it were a range v-v.)
548            let (lo_s, hi_s) = {
549                match ent.find('-') {
550                    Some(pos) => (&ent[..pos], &ent[pos + 1..]),
551                    None => (ent, ent),
552                }
553            };
554            if !is_good_number(lo_s) {
555                return Err(ParseError::Malformed);
556            }
557            if !is_good_number(hi_s) {
558                return Err(ParseError::Malformed);
559            }
560            let lo: u64 = lo_s.parse().map_err(|_| ParseError::Malformed)?;
561            let hi: u64 = hi_s.parse().map_err(|_| ParseError::Malformed)?;
562            // Make sure that lo and hi are in-bounds and consistent.
563            if lo > (MAX_VER as u64) || hi > (MAX_VER as u64) {
564                return Err(ParseError::OutOfRange);
565            }
566            if lo > hi {
567                return Err(ParseError::Malformed);
568            }
569            let mask = bitrange(lo, hi);
570            // Make sure that no version is included twice.
571            if (supported & mask) != 0 {
572                return Err(ParseError::Duplicate);
573            }
574            // Add the appropriate bits to the mask.
575            supported |= mask;
576        }
577        Ok(SubprotocolEntry { proto, supported })
578    }
579}
580
581/// A Protocols set can be parsed from a string according to the
582/// format used in Tor consensus documents.
583///
584/// A protocols set is represented by a space-separated list of
585/// entries.  Each entry is of the form `Name=Versions`, where `Name`
586/// is the name of a protocol, and `Versions` is a comma-separated
587/// list of version numbers and version ranges.  Each version range is
588/// a pair of integers separated by `-`.
589///
590/// No protocol name may be listed twice.  No version may be listed
591/// twice for a single protocol.  All versions must be in range 0
592/// through 63 inclusive.
593impl std::str::FromStr for Protocols {
594    type Err = ParseError;
595
596    fn from_str(s: &str) -> Result<Self, ParseError> {
597        let mut result = Protocols::new();
598        let mut foundmask = 0_u64;
599        for ent in s.split(' ') {
600            if ent.is_empty() {
601                continue;
602            }
603
604            let s: SubprotocolEntry = ent.parse()?;
605            result.add(&mut foundmask, s)?;
606        }
607        result.unrecognized.sort();
608        Ok(result)
609    }
610}
611
612/// Given a bitmask, return a list of the bits set in the mask, as a
613/// String in the format expected by Tor consensus documents.
614///
615/// This implementation constructs ranges greedily.  For example, the
616/// bitmask `0b0111011` will be represented as `0-1,3-5`, and not
617/// `0,1,3,4,5` or `0,1,3-5`.
618///
619/// ```ignore
620/// # use tor_protover::dumpmask;
621/// assert_eq!(dumpmask(0b111111), "0-5");
622/// assert_eq!(dumpmask(0b111100), "2-5");
623/// assert_eq!(dumpmask(0b11111100), "2-7");
624/// ```
625fn dumpmask(mut mask: u64) -> String {
626    /// Helper: push a range (which may be a singleton) onto `v`.
627    fn append(v: &mut Vec<String>, lo: u32, hi: u32) {
628        if lo == hi {
629            v.push(lo.to_string());
630        } else {
631            v.push(format!("{}-{}", lo, hi));
632        }
633    }
634    // We'll be building up our result here, then joining it with
635    // commas.
636    let mut result = Vec::new();
637    // This implementation is a little tricky, but it should be more
638    // efficient than a raw search.  Basically, we're using the
639    // function u64::trailing_zeros to count how large each range of
640    // 1s or 0s is, and then shifting by that amount.
641
642    // How many bits have we already shifted `mask`?
643    let mut shift = 0;
644    while mask != 0 {
645        let zeros = mask.trailing_zeros();
646        mask >>= zeros;
647        shift += zeros;
648        let ones = mask.trailing_ones();
649        append(&mut result, shift, shift + ones - 1);
650        shift += ones;
651        if ones == 64 {
652            // We have to do this check to avoid overflow when formatting
653            // the range `0-63`.
654            break;
655        }
656        mask >>= ones;
657    }
658    result.join(",")
659}
660
661/// The Display trait formats a protocol set in the format expected by Tor
662/// consensus documents.
663///
664/// ```
665/// use tor_protover::*;
666/// let protos: Protocols = "Link=1,2,3 Foobar=7 Relay=2".parse().unwrap();
667/// assert_eq!(format!("{}", protos),
668///            "Foobar=7 Link=1-3 Relay=2");
669/// ```
670impl std::fmt::Display for Protocols {
671    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
672        let mut entries = Vec::new();
673        for (idx, mask) in self.recognized.iter().enumerate() {
674            if *mask != 0 {
675                let pk: ProtoKind = (idx as u8).into();
676                entries.push(format!("{}={}", pk, dumpmask(*mask)));
677            }
678        }
679        for ent in &self.unrecognized {
680            if ent.supported != 0 {
681                entries.push(format!(
682                    "{}={}",
683                    ent.proto.to_str(),
684                    dumpmask(ent.supported)
685                ));
686            }
687        }
688        // This sort is required.
689        entries.sort();
690        write!(f, "{}", entries.join(" "))
691    }
692}
693
694impl FromIterator<NamedSubver> for Protocols {
695    fn from_iter<T: IntoIterator<Item = NamedSubver>>(iter: T) -> Self {
696        let mut r = Protocols::new();
697        for named_subver in iter {
698            let proto_idx = usize::from(named_subver.kind.get());
699            let proto_ver = named_subver.version;
700
701            // These are guaranteed by invariants on NamedSubver.
702            assert!(proto_idx < N_RECOGNIZED);
703            assert!(usize::from(proto_ver) <= MAX_VER);
704            r.recognized[proto_idx] |= 1_u64 << proto_ver;
705        }
706        r
707    }
708}
709
710/// Documentation: when is a protocol "supported"?
711///
712/// Arti should consider itself to "support" a protocol if, _as built_,
713/// it implements the protocol completely.
714///
715/// Just having the protocol listed among the [`named`]
716/// protocols is not enough, and neither is an incomplete
717/// or uncompliant implementation.
718///
719/// Similarly, if the protocol is not compiled in,
720/// it is not technically _supported_.
721///
722/// When in doubt, ask yourself:
723/// - If another Tor implementation believed that we implemented this protocol,
724///   and began to speak it to us, would we be able to do so?
725/// - If the protocol were required,
726///   would this software as built actually meet that requirement?
727///
728/// If either answer is no, the protocol is not supported.
729pub mod doc_supported {}
730
731/// Documentation about changing lists of supported versions.
732///
733/// # Warning
734///
735/// You need to be extremely careful when removing
736/// _any_ entry from a list of supported protocols.
737///
738/// If you remove an entry while it still appears as "recommended" in the consensus,
739/// you'll cause all the instances without it to warn.
740///
741/// If you remove an entry while it still appears as "required" in the
742///  consensus, you'll cause all the instances without it to refuse to connect
743/// to the network, and shut down.
744///
745/// If you need to remove a version from a list of supported protocols,
746/// you need to make sure that it is not listed in the _current consensuses_:
747/// just removing it from the list that the authorities vote for is NOT ENOUGH.
748/// You need to remove it from the required list,
749/// and THEN let the authorities upgrade and vote on new
750/// consensuses without it. Only once those consensuses are out is it safe to
751/// remove from the list of required protocols.
752///
753/// ## Example
754///
755/// One concrete example of a very dangerous race that could occur:
756///
757/// Suppose that the client supports protocols "HsDir=1-2" and the consensus
758/// requires protocols "HsDir=1-2.  If the client supported protocol list is
759/// then changed to "HSDir=2", while the consensus stills lists "HSDir=1-2",
760/// then these clients, even very recent ones, will shut down because they
761/// don't support "HSDir=1".
762///
763/// And so, changes need to be done in strict sequence as described above.
764pub mod doc_changing {}
765
766#[cfg(test)]
767mod test {
768    // @@ begin test lint list maintained by maint/add_warning @@
769    #![allow(clippy::bool_assert_comparison)]
770    #![allow(clippy::clone_on_copy)]
771    #![allow(clippy::dbg_macro)]
772    #![allow(clippy::mixed_attributes_style)]
773    #![allow(clippy::print_stderr)]
774    #![allow(clippy::print_stdout)]
775    #![allow(clippy::single_char_pattern)]
776    #![allow(clippy::unwrap_used)]
777    #![allow(clippy::unchecked_duration_subtraction)]
778    #![allow(clippy::useless_vec)]
779    #![allow(clippy::needless_pass_by_value)]
780    //! <!-- @@ end test lint list maintained by maint/add_warning @@ -->
781    use std::str::FromStr;
782
783    use super::*;
784
785    #[test]
786    fn test_bitrange() {
787        assert_eq!(0b1, bitrange(0, 0));
788        assert_eq!(0b10, bitrange(1, 1));
789        assert_eq!(0b11, bitrange(0, 1));
790        assert_eq!(0b1111110000000, bitrange(7, 12));
791        assert_eq!(!0, bitrange(0, 63));
792    }
793
794    #[test]
795    fn test_dumpmask() {
796        assert_eq!("", dumpmask(0));
797        assert_eq!("0-5", dumpmask(0b111111));
798        assert_eq!("4-5", dumpmask(0b110000));
799        assert_eq!("1,4-5", dumpmask(0b110010));
800        assert_eq!("0-63", dumpmask(!0));
801    }
802
803    #[test]
804    fn test_canonical() -> Result<(), ParseError> {
805        fn t(orig: &str, canonical: &str) -> Result<(), ParseError> {
806            let protos: Protocols = orig.parse()?;
807            let enc = format!("{}", protos);
808            assert_eq!(enc, canonical);
809            Ok(())
810        }
811
812        t("", "")?;
813        t(" ", "")?;
814        t("Link=5,6,7,9 Relay=4-7,2", "Link=5-7,9 Relay=2,4-7")?;
815        t("FlowCtrl= Padding=8,7 Desc=1-5,6-8", "Desc=1-8 Padding=7-8")?;
816        t("Zelda=7 Gannon=3,6 Link=4", "Gannon=3,6 Link=4 Zelda=7")?;
817
818        Ok(())
819    }
820
821    #[test]
822    fn test_invalid() {
823        fn t(s: &str) -> ParseError {
824            let protos: Result<Protocols, ParseError> = s.parse();
825            assert!(protos.is_err());
826            protos.err().unwrap()
827        }
828
829        assert_eq!(t("Link=1-100"), ParseError::OutOfRange);
830        assert_eq!(t("Zelda=100"), ParseError::OutOfRange);
831        assert_eq!(t("Link=100-200"), ParseError::OutOfRange);
832
833        assert_eq!(t("Link=1,1"), ParseError::Duplicate);
834        assert_eq!(t("Link=1 Link=1"), ParseError::Duplicate);
835        assert_eq!(t("Link=1 Link=3"), ParseError::Duplicate);
836        assert_eq!(t("Zelda=1 Zelda=3"), ParseError::Duplicate);
837
838        assert_eq!(t("Link=Zelda"), ParseError::Malformed);
839        assert_eq!(t("Link=6-2"), ParseError::Malformed);
840        assert_eq!(t("Link=6-"), ParseError::Malformed);
841        assert_eq!(t("Link=6-,2"), ParseError::Malformed);
842        assert_eq!(t("Link=1,,2"), ParseError::Malformed);
843        assert_eq!(t("Link=6-frog"), ParseError::Malformed);
844        assert_eq!(t("Link=gannon-9"), ParseError::Malformed);
845        assert_eq!(t("Link Zelda"), ParseError::Malformed);
846
847        assert_eq!(t("Link=01"), ParseError::Malformed);
848        assert_eq!(t("Link=waffle"), ParseError::Malformed);
849        assert_eq!(t("Link=1_1"), ParseError::Malformed);
850    }
851
852    #[test]
853    fn test_supports() -> Result<(), ParseError> {
854        let p: Protocols = "Link=4,5-7 Padding=2 Lonk=1-3,5".parse()?;
855
856        assert!(p.supports_known_subver(ProtoKind::Padding, 2));
857        assert!(!p.supports_known_subver(ProtoKind::Padding, 1));
858        assert!(p.supports_known_subver(ProtoKind::Link, 6));
859        assert!(!p.supports_known_subver(ProtoKind::Link, 255));
860        assert!(!p.supports_known_subver(ProtoKind::Cons, 1));
861        assert!(!p.supports_known_subver(ProtoKind::Cons, 0));
862        assert!(p.supports_subver("Link", 6));
863        assert!(!p.supports_subver("link", 6));
864        assert!(!p.supports_subver("Cons", 0));
865        assert!(p.supports_subver("Lonk", 3));
866        assert!(!p.supports_subver("Lonk", 4));
867        assert!(!p.supports_subver("lonk", 3));
868        assert!(!p.supports_subver("Lonk", 64));
869
870        Ok(())
871    }
872
873    #[test]
874    fn test_difference() -> Result<(), ParseError> {
875        let p1: Protocols = "Link=1-10 Desc=5-10 Relay=1,3,5,7,9 Other=7-60 Mine=1-20".parse()?;
876        let p2: Protocols = "Link=3-4 Desc=1-6 Relay=2-6 Other=8 Theirs=20".parse()?;
877
878        assert_eq!(
879            p1.difference(&p2),
880            Protocols::from_str("Link=1-2,5-10 Desc=7-10 Relay=1,7,9 Other=7,9-60 Mine=1-20")?
881        );
882        assert_eq!(
883            p2.difference(&p1),
884            Protocols::from_str("Desc=1-4 Relay=2,4,6 Theirs=20")?,
885        );
886
887        let nil = Protocols::default();
888        assert_eq!(p1.difference(&nil), p1);
889        assert_eq!(p2.difference(&nil), p2);
890        assert_eq!(nil.difference(&p1), nil);
891        assert_eq!(nil.difference(&p2), nil);
892
893        Ok(())
894    }
895
896    #[test]
897    fn test_union() -> Result<(), ParseError> {
898        let p1: Protocols = "Link=1-10 Desc=5-10 Relay=1,3,5,7,9 Other=7-60 Mine=1-20".parse()?;
899        let p2: Protocols = "Link=3-4 Desc=1-6 Relay=2-6 Other=2,8 Theirs=20".parse()?;
900
901        assert_eq!(
902            p1.union(&p2),
903            Protocols::from_str(
904                "Link=1-10 Desc=1-10 Relay=1-7,9 Other=2,7-60 Theirs=20 Mine=1-20"
905            )?
906        );
907        assert_eq!(
908            p2.union(&p1),
909            Protocols::from_str(
910                "Link=1-10 Desc=1-10 Relay=1-7,9 Other=2,7-60 Theirs=20 Mine=1-20"
911            )?
912        );
913
914        let nil = Protocols::default();
915        assert_eq!(p1.union(&nil), p1);
916        assert_eq!(p2.union(&nil), p2);
917        assert_eq!(nil.union(&p1), p1);
918        assert_eq!(nil.union(&p2), p2);
919
920        Ok(())
921    }
922
923    #[test]
924    fn test_intersection() -> Result<(), ParseError> {
925        let p1: Protocols = "Link=1-10 Desc=5-10 Relay=1,3,5,7,9 Other=7-60 Mine=1-20".parse()?;
926        let p2: Protocols = "Link=3-4 Desc=1-6 Relay=2-6 Other=2,8 Theirs=20".parse()?;
927
928        assert_eq!(
929            p1.intersection(&p2),
930            Protocols::from_str("Link=3-4 Desc=5-6 Relay=3,5 Other=8")?
931        );
932        assert_eq!(
933            p2.intersection(&p1),
934            Protocols::from_str("Link=3-4 Desc=5-6 Relay=3,5 Other=8")?
935        );
936
937        let nil = Protocols::default();
938        assert_eq!(p1.intersection(&nil), nil);
939        assert_eq!(p2.intersection(&nil), nil);
940        assert_eq!(nil.intersection(&p1), nil);
941        assert_eq!(nil.intersection(&p2), nil);
942
943        Ok(())
944    }
945
946    #[test]
947    fn from_iter() {
948        use named as n;
949        let empty: [NamedSubver; 0] = [];
950        let prs: Protocols = empty.iter().copied().collect();
951        assert_eq!(prs, Protocols::default());
952        let prs: Protocols = empty.into_iter().collect();
953        assert_eq!(prs, Protocols::default());
954
955        let prs = [
956            n::LINK_V3,
957            n::HSDIR_V3,
958            n::LINK_V4,
959            n::LINK_V5,
960            n::CONFLUX_BASE,
961        ]
962        .into_iter()
963        .collect::<Protocols>();
964        assert_eq!(prs, "Link=3-5 HSDir=2 Conflux=1".parse().unwrap());
965    }
966
967    #[test]
968    fn order_numbered_subvers() {
969        // We rely on this sort order elsewhere in our protocol.
970        assert!(NumberedSubver::new(5, 7) < NumberedSubver::new(7, 5));
971        assert!(NumberedSubver::new(7, 5) < NumberedSubver::new(7, 6));
972        assert!(NumberedSubver::new(7, 6) < NumberedSubver::new(8, 6));
973    }
974}