Skip to main content

tor_protover/
lib.rs

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