diff options
Diffstat (limited to 'src/rust/protover')
-rw-r--r-- | src/rust/protover/Cargo.toml | 11 | ||||
-rw-r--r-- | src/rust/protover/errors.rs | 43 | ||||
-rw-r--r-- | src/rust/protover/ffi.rs | 91 | ||||
-rw-r--r-- | src/rust/protover/lib.rs | 10 | ||||
-rw-r--r-- | src/rust/protover/protoset.rs | 141 | ||||
-rw-r--r-- | src/rust/protover/protover.rs | 246 | ||||
-rw-r--r-- | src/rust/protover/tests/protover.rs | 34 |
7 files changed, 322 insertions, 254 deletions
diff --git a/src/rust/protover/Cargo.toml b/src/rust/protover/Cargo.toml index 86301b8787..2f7783e76c 100644 --- a/src/rust/protover/Cargo.toml +++ b/src/rust/protover/Cargo.toml @@ -3,6 +3,13 @@ authors = ["The Tor Project"] version = "0.0.1" name = "protover" +[features] +# We have to define a feature here because doctests don't get cfg(test), +# and we need to disable some C dependencies when running the doctests +# because of the various linker issues. See +# https://github.com/rust-lang/rust/issues/45599 +test_linking_hack = [] + [dependencies] libc = "=0.2.39" @@ -18,8 +25,10 @@ path = "../tor_util" [dependencies.tor_allocate] path = "../tor_allocate" +[dependencies.tor_log] +path = "../tor_log" + [lib] name = "protover" path = "lib.rs" crate_type = ["rlib", "staticlib"] - diff --git a/src/rust/protover/errors.rs b/src/rust/protover/errors.rs index d9dc73381f..f26a48b019 100644 --- a/src/rust/protover/errors.rs +++ b/src/rust/protover/errors.rs @@ -25,22 +25,33 @@ pub enum ProtoverError { impl Display for ProtoverError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { - ProtoverError::Overlap - => write!(f, "Two or more (low, high) protover ranges would overlap once expanded."), - ProtoverError::LowGreaterThanHigh - => write!(f, "The low in a (low, high) protover range was greater than high."), - ProtoverError::Unparseable - => write!(f, "The protover string was unparseable."), - ProtoverError::ExceedsMax - => write!(f, "The high in a (low, high) protover range exceeds u32::MAX."), - ProtoverError::ExceedsExpansionLimit - => write!(f, "The protover string would exceed the maximum expansion limit."), - ProtoverError::UnknownProtocol - => write!(f, "A protocol in the protover string we attempted to parse is unknown."), - ProtoverError::ExceedsNameLimit - => write!(f, "An unrecognised protocol name was too long."), - ProtoverError::InvalidProtocol - => write!(f, "A protocol name includes invalid characters."), + ProtoverError::Overlap => write!( + f, + "Two or more (low, high) protover ranges would overlap once expanded." + ), + ProtoverError::LowGreaterThanHigh => write!( + f, + "The low in a (low, high) protover range was greater than high." + ), + ProtoverError::Unparseable => write!(f, "The protover string was unparseable."), + ProtoverError::ExceedsMax => write!( + f, + "The high in a (low, high) protover range exceeds u32::MAX." + ), + ProtoverError::ExceedsExpansionLimit => write!( + f, + "The protover string would exceed the maximum expansion limit." + ), + ProtoverError::UnknownProtocol => write!( + f, + "A protocol in the protover string we attempted to parse is unknown." + ), + ProtoverError::ExceedsNameLimit => { + write!(f, "An unrecognised protocol name was too long.") + } + ProtoverError::InvalidProtocol => { + write!(f, "A protocol name includes invalid characters.") + } } } } diff --git a/src/rust/protover/ffi.rs b/src/rust/protover/ffi.rs index f803bd0155..940f923327 100644 --- a/src/rust/protover/ffi.rs +++ b/src/rust/protover/ffi.rs @@ -1,9 +1,9 @@ -// Copyright (c) 2016-2017, The Tor Project, Inc. */ +// Copyright (c) 2016-2018, The Tor Project, Inc. */ // See LICENSE for licensing information */ //! FFI functions, only to be called from C. //! -//! Equivalent C versions of this api are in `src/or/protover.c` +//! Equivalent C versions of this api are in `protover.c` use libc::{c_char, c_int, uint32_t}; use std::ffi::CStr; @@ -11,9 +11,6 @@ use std::ffi::CString; use smartlist::*; use tor_allocate::allocate_and_copy_string; -use tor_util::strings::byte_slice_is_c_like; -use tor_util::strings::empty_static_cstr; - use errors::ProtoverError; use protover::*; @@ -21,7 +18,7 @@ use protover::*; /// Translate C enums to Rust Proto enums, using the integer value of the C /// enum to map to its associated Rust enum. /// -/// C_RUST_COUPLED: src/or/protover.h `protocol_type_t` +/// C_RUST_COUPLED: protover.h `protocol_type_t` fn translate_to_rust(c_proto: uint32_t) -> Result<Protocol, ProtoverError> { match c_proto { 0 => Ok(Protocol::Link), @@ -45,7 +42,6 @@ pub extern "C" fn protover_all_supported( c_relay_version: *const c_char, missing_out: *mut *mut c_char, ) -> c_int { - if c_relay_version.is_null() { return 1; } @@ -61,13 +57,11 @@ pub extern "C" fn protover_all_supported( let relay_proto_entry: UnvalidatedProtoEntry = match UnvalidatedProtoEntry::from_str_any_len(relay_version) { - Ok(n) => n, - Err(_) => return 1, - }; - let maybe_unsupported: Option<UnvalidatedProtoEntry> = relay_proto_entry.all_supported(); + Ok(n) => n, + Err(_) => return 1, + }; - if maybe_unsupported.is_some() { - let unsupported: UnvalidatedProtoEntry = maybe_unsupported.unwrap(); + if let Some(unsupported) = relay_proto_entry.all_supported() { let c_unsupported: CString = match CString::new(unsupported.to_string()) { Ok(n) => n, Err(_) => return 1, @@ -103,23 +97,22 @@ pub extern "C" fn protocol_list_supports_protocol( Err(_) => return 1, }; let proto_entry: UnvalidatedProtoEntry = match protocol_list.parse() { - Ok(n) => n, + Ok(n) => n, Err(_) => return 0, }; let protocol: UnknownProtocol = match translate_to_rust(c_protocol) { Ok(n) => n.into(), Err(_) => return 0, }; - match proto_entry.supports_protocol(&protocol, &version) { - false => return 0, - true => return 1, + if proto_entry.supports_protocol(&protocol, &version) { + 1 + } else { + 0 } } #[no_mangle] -pub extern "C" fn protover_contains_long_protocol_names_( - c_protocol_list: *const c_char -) -> c_int { +pub extern "C" fn protover_contains_long_protocol_names_(c_protocol_list: *const c_char) -> c_int { if c_protocol_list.is_null() { return 1; } @@ -130,13 +123,10 @@ pub extern "C" fn protover_contains_long_protocol_names_( let protocol_list = match c_str.to_str() { Ok(n) => n, - Err(_) => return 1 + Err(_) => return 1, }; - let protocol_entry : Result<UnvalidatedProtoEntry,_> = - protocol_list.parse(); - - match protocol_entry { + match protocol_list.parse::<UnvalidatedProtoEntry>() { Ok(_) => 0, Err(_) => 1, } @@ -169,7 +159,7 @@ pub extern "C" fn protocol_list_supports_protocol_or_later( }; let proto_entry: UnvalidatedProtoEntry = match protocol_list.parse() { - Ok(n) => n, + Ok(n) => n, Err(_) => return 1, }; @@ -185,18 +175,7 @@ pub extern "C" fn protocol_list_supports_protocol_or_later( pub extern "C" fn protover_get_supported_protocols() -> *const c_char { let supported: &'static CStr; - // If we're going to pass it to C, there cannot be any intermediate NUL - // bytes. An assert is okay here, since changing the const byte slice - // in protover.rs to contain a NUL byte somewhere in the middle would be a - // programming error. - assert!(byte_slice_is_c_like(SUPPORTED_PROTOCOLS)); - - // It's okay to unwrap the result of this function because - // we can see that the bytes we're passing into it 1) are valid UTF-8, - // 2) have no intermediate NUL bytes, and 3) are terminated with a NUL - // byte. - supported = CStr::from_bytes_with_nul(SUPPORTED_PROTOCOLS).unwrap(); - + supported = get_supported_protocols_cstr(); supported.as_ptr() } @@ -205,14 +184,9 @@ pub extern "C" fn protover_get_supported_protocols() -> *const c_char { // // Why is the threshold a signed integer? —isis #[no_mangle] -pub extern "C" fn protover_compute_vote( - list: *const Stringlist, - threshold: c_int -) -> *mut c_char { - +pub extern "C" fn protover_compute_vote(list: *const Stringlist, threshold: c_int) -> *mut c_char { if list.is_null() { - let empty = String::new(); - return allocate_and_copy_string(&empty); + return allocate_and_copy_string(""); } // Dereference of raw pointer requires an unsafe block. The pointer is @@ -223,8 +197,8 @@ pub extern "C" fn protover_compute_vote( for datum in data { let entry: UnvalidatedProtoEntry = match datum.parse() { - Ok(n) => n, - Err(_) => continue + Ok(n) => n, + Err(_) => continue, }; proto_entries.push(entry); } @@ -236,10 +210,7 @@ pub extern "C" fn protover_compute_vote( /// Provide an interface for C to translate arguments and return types for /// protover::is_supported_here #[no_mangle] -pub extern "C" fn protover_is_supported_here( - c_protocol: uint32_t, - version: uint32_t, -) -> c_int { +pub extern "C" fn protover_is_supported_here(c_protocol: uint32_t, version: uint32_t) -> c_int { let protocol = match translate_to_rust(c_protocol) { Ok(n) => n, Err(_) => return 0, @@ -255,10 +226,9 @@ pub extern "C" fn protover_is_supported_here( #[no_mangle] pub extern "C" fn protover_compute_for_old_tor(version: *const c_char) -> *const c_char { let supported: &'static CStr; - let elder_protocols: &'static [u8]; let empty: &'static CStr; - empty = empty_static_cstr(); + empty = cstr!(""); if version.is_null() { return empty.as_ptr(); @@ -273,19 +243,6 @@ pub extern "C" fn protover_compute_for_old_tor(version: *const c_char) -> *const Err(_) => return empty.as_ptr(), }; - elder_protocols = compute_for_old_tor_cstr(&version); - - // If we're going to pass it to C, there cannot be any intermediate NUL - // bytes. An assert is okay here, since changing the const byte slice - // in protover.rs to contain a NUL byte somewhere in the middle would be a - // programming error. - assert!(byte_slice_is_c_like(elder_protocols)); - - // It's okay to unwrap the result of this function because - // we can see that the bytes we're passing into it 1) are valid UTF-8, - // 2) have no intermediate NUL bytes, and 3) are terminated with a NUL - // byte. - supported = CStr::from_bytes_with_nul(elder_protocols).unwrap_or(empty); - + supported = compute_for_old_tor_cstr(&version); supported.as_ptr() } diff --git a/src/rust/protover/lib.rs b/src/rust/protover/lib.rs index 483260bca8..9625cb58ad 100644 --- a/src/rust/protover/lib.rs +++ b/src/rust/protover/lib.rs @@ -1,4 +1,4 @@ -//! Copyright (c) 2016-2017, The Tor Project, Inc. */ +//! Copyright (c) 2016-2018, The Tor Project, Inc. */ //! See LICENSE for licensing information */ //! Versioning information for different pieces of the Tor protocol. @@ -22,17 +22,19 @@ //! protocols to develop independently, without having to claim compatibility //! with specific versions of Tor. -#[deny(missing_docs)] +// XXX: add missing docs +//#![deny(missing_docs)] +extern crate external; extern crate libc; extern crate smartlist; -extern crate external; extern crate tor_allocate; +#[macro_use] extern crate tor_util; pub mod errors; +pub mod ffi; pub mod protoset; mod protover; -pub mod ffi; pub use protover::*; diff --git a/src/rust/protover/protoset.rs b/src/rust/protover/protoset.rs index b99e1a99f7..aa8d243bad 100644 --- a/src/rust/protover/protoset.rs +++ b/src/rust/protover/protoset.rs @@ -4,6 +4,8 @@ //! Sets for lazily storing ordered, non-overlapping ranges of integers. +use std::cmp; +use std::iter; use std::slice; use std::str::FromStr; use std::u32; @@ -53,7 +55,7 @@ impl Default for ProtoSet { fn default() -> Self { let pairs: Vec<(Version, Version)> = Vec::new(); - ProtoSet{ pairs } + ProtoSet { pairs } } } @@ -73,7 +75,7 @@ impl<'a> ProtoSet { pairs.sort_unstable(); pairs.dedup(); - ProtoSet{ pairs }.is_ok() + ProtoSet { pairs }.is_ok() } } @@ -240,8 +242,8 @@ impl ProtoSet { false } - /// Retain only the `Version`s in this `ProtoSet` for which the predicate - /// `F` returns `true`. + /// Returns all the `Version`s in `self` which are not also in the `other` + /// `ProtoSet`. /// /// # Examples /// @@ -250,24 +252,45 @@ impl ProtoSet { /// use protover::protoset::ProtoSet; /// /// # fn do_test() -> Result<bool, ProtoverError> { - /// let mut protoset: ProtoSet = "1,3-5,9".parse()?; + /// let protoset: ProtoSet = "1,3-6,10-12,15-16".parse()?; + /// let other: ProtoSet = "2,5-7,9-11,14-20".parse()?; /// - /// // Keep only versions less than or equal to 8: - /// protoset.retain(|x| x <= &8); + /// let subset: ProtoSet = protoset.and_not_in(&other); /// - /// assert_eq!(protoset.expand(), vec![1, 3, 4, 5]); + /// assert_eq!(subset.expand(), vec![1, 3, 4, 12]); /// # /// # Ok(true) /// # } /// # fn main() { do_test(); } // wrap the test so we can use the ? operator /// ``` - // XXX we could probably do something more efficient here. —isis - pub fn retain<F>(&mut self, f: F) - where F: FnMut(&Version) -> bool - { - let mut expanded: Vec<Version> = self.clone().expand(); - expanded.retain(f); - *self = expanded.into(); + pub fn and_not_in(&self, other: &Self) -> Self { + if self.is_empty() || other.is_empty() { + return self.clone(); + } + + let pairs = self.iter().flat_map(|&(lo, hi)| { + let the_end = (hi + 1, hi + 1); // special case to mark the end of the range. + let excluded_ranges = other + .iter() + .cloned() // have to be owned tuples, to match iter::once(the_end). + .skip_while(move|&(_, hi2)| hi2 < lo) // skip the non-overlapping ranges. + .take_while(move|&(lo2, _)| lo2 <= hi) // take all the overlapping ones. + .chain(iter::once(the_end)); + + let mut nextlo = lo; + excluded_ranges.filter_map(move |(excluded_lo, excluded_hi)| { + let pair = if nextlo < excluded_lo { + Some((nextlo, excluded_lo - 1)) + } else { + None + }; + nextlo = cmp::min(excluded_hi, u32::MAX - 1) + 1; + pair + }) + }); + + let pairs = pairs.collect(); + ProtoSet::is_ok(ProtoSet { pairs }).expect("should be already sorted") } } @@ -299,7 +322,7 @@ impl FromStr for ProtoSet { /// * there are greater than 2^16 version numbers to expand. /// /// # Examples - /// + /// /// ``` /// use std::str::FromStr; /// @@ -329,49 +352,41 @@ impl FromStr for ProtoSet { /// assert_eq!(Err(ProtoverError::Unparseable), ProtoSet::from_str("3-")); /// assert_eq!(Err(ProtoverError::Unparseable), ProtoSet::from_str("1-,4")); /// - /// // Things which would get parsed into an _empty_ `ProtoSet` are, - /// // however, legal, and result in an empty `ProtoSet`: + /// // An empty string is, however, legal, and results in an + /// // empty `ProtoSet`: /// assert_eq!(Ok(ProtoSet::default()), ProtoSet::from_str("")); - /// assert_eq!(Ok(ProtoSet::default()), ProtoSet::from_str(",,,")); /// # /// # Ok(protoset) /// # } /// # fn main() { do_test(); } // wrap the test so we can use the ? operator /// ``` fn from_str(version_string: &str) -> Result<Self, Self::Err> { + // If we were passed in an empty string, then return an empty ProtoSet. + if version_string.is_empty() { + return Ok(Self::default()); + } + let mut pairs: Vec<(Version, Version)> = Vec::new(); let pieces: ::std::str::Split<char> = version_string.split(','); for p in pieces { - if p.is_empty() { - continue; - } else if p.contains('-') { + if p.contains('-') { let mut pair = p.splitn(2, '-'); - let low = pair.next().ok_or(ProtoverError::Unparseable)?; + let low = pair.next().ok_or(ProtoverError::Unparseable)?; let high = pair.next().ok_or(ProtoverError::Unparseable)?; - let lo: Version = low.parse().or(Err(ProtoverError::Unparseable))?; + let lo: Version = low.parse().or(Err(ProtoverError::Unparseable))?; let hi: Version = high.parse().or(Err(ProtoverError::Unparseable))?; - if lo == u32::MAX || hi == u32::MAX { - return Err(ProtoverError::ExceedsMax); - } pairs.push((lo, hi)); } else { let v: u32 = p.parse().or(Err(ProtoverError::Unparseable))?; - if v == u32::MAX { - return Err(ProtoverError::ExceedsMax); - } pairs.push((v, v)); } } - // If we were passed in an empty string, or - // simply a comma, or a pile of commas, then return an empty ProtoSet. - if pairs.len() == 0 { - return Ok(ProtoSet::default()); - } + ProtoSet::from_slice(&pairs[..]) } } @@ -455,11 +470,11 @@ impl From<Vec<Version>> for ProtoSet { if has_range { let first: Version = match v.first() { Some(x) => *x, - None => continue, + None => continue, }; - let last: Version = match v.get(index) { + let last: Version = match v.get(index) { Some(x) => *x, - None => continue, + None => continue, }; debug_assert!(last == end, format!("last = {}, end = {}", last, end)); @@ -472,7 +487,7 @@ impl From<Vec<Version>> for ProtoSet { } else { let last: Version = match v.get(index) { Some(x) => *x, - None => continue, + None => continue, }; version_pairs.push((last, last)); v.remove(index); @@ -496,22 +511,22 @@ mod test { } macro_rules! assert_contains_each { - ($protoset:expr, $versions:expr) => ( + ($protoset:expr, $versions:expr) => { for version in $versions { assert!($protoset.contains(version)); } - ) + }; } macro_rules! test_protoset_contains_versions { - ($list:expr, $str:expr) => ( + ($list:expr, $str:expr) => { let versions: &[Version] = $list; let protoset: Result<ProtoSet, ProtoverError> = ProtoSet::from_str($str); assert!(protoset.is_ok()); let p = protoset.unwrap(); assert_contains_each!(p, versions); - ) + }; } #[test] @@ -536,6 +551,13 @@ mod test { } #[test] + fn test_versions_from_str_commas() { + assert_eq!(Err(ProtoverError::Unparseable), ProtoSet::from_str(",")); + assert_eq!(Err(ProtoverError::Unparseable), ProtoSet::from_str("1,,2")); + assert_eq!(Err(ProtoverError::Unparseable), ProtoSet::from_str("1,2,")); + } + + #[test] fn test_versions_from_str_hyphens() { assert_eq!(Err(ProtoverError::Unparseable), ProtoSet::from_str("--1")); assert_eq!(Err(ProtoverError::Unparseable), ProtoSet::from_str("-1-2")); @@ -571,26 +593,41 @@ mod test { #[test] fn test_versions_from_slice_overlap() { - assert_eq!(Err(ProtoverError::Overlap), ProtoSet::from_slice(&[(1, 3), (2, 4)])); + assert_eq!( + Err(ProtoverError::Overlap), + ProtoSet::from_slice(&[(1, 3), (2, 4)]) + ); } #[test] fn test_versions_from_str_max() { - assert_eq!(Err(ProtoverError::ExceedsMax), ProtoSet::from_str("4294967295")); + assert_eq!( + Err(ProtoverError::ExceedsMax), + ProtoSet::from_str("4294967295") + ); } #[test] fn test_versions_from_slice_max() { - assert_eq!(Err(ProtoverError::ExceedsMax), ProtoSet::from_slice(&[(4294967295, 4294967295)])); + assert_eq!( + Err(ProtoverError::ExceedsMax), + ProtoSet::from_slice(&[(4294967295, 4294967295)]) + ); } #[test] fn test_protoset_contains() { let protoset: ProtoSet = ProtoSet::from_slice(&[(1, 5), (7, 9), (13, 14)]).unwrap(); - for x in 1..6 { assert!(protoset.contains(&x), format!("should contain {}", x)); } - for x in 7..10 { assert!(protoset.contains(&x), format!("should contain {}", x)); } - for x in 13..15 { assert!(protoset.contains(&x), format!("should contain {}", x)); } + for x in 1..6 { + assert!(protoset.contains(&x), format!("should contain {}", x)); + } + for x in 7..10 { + assert!(protoset.contains(&x), format!("should contain {}", x)); + } + for x in 13..15 { + assert!(protoset.contains(&x), format!("should contain {}", x)); + } for x in [6, 10, 11, 12, 15, 42, 43, 44, 45, 1234584].iter() { assert!(!protoset.contains(&x), format!("should not contain {}", x)); @@ -601,7 +638,9 @@ mod test { fn test_protoset_contains_1_3() { let protoset: ProtoSet = ProtoSet::from_slice(&[(1, 3)]).unwrap(); - for x in 1..4 { assert!(protoset.contains(&x), format!("should contain {}", x)); } + for x in 1..4 { + assert!(protoset.contains(&x), format!("should contain {}", x)); + } } macro_rules! assert_protoset_from_vec_contains_all { @@ -627,7 +666,7 @@ mod test { #[test] fn test_protoset_from_vec_unordered() { - let v: Vec<Version> = vec!(2, 3, 8, 4, 3, 9, 7, 2); + let v: Vec<Version> = vec![2, 3, 8, 4, 3, 9, 7, 2]; let ps: ProtoSet = v.into(); assert_eq!(ps.to_string(), "2-4,7-9"); diff --git a/src/rust/protover/protover.rs b/src/rust/protover/protover.rs index b3563b0637..8624afeafa 100644 --- a/src/rust/protover/protover.rs +++ b/src/rust/protover/protover.rs @@ -1,63 +1,39 @@ -// Copyright (c) 2016-2017, The Tor Project, Inc. */ +// Copyright (c) 2016-2018, The Tor Project, Inc. */ // See LICENSE for licensing information */ -use std::collections::HashMap; use std::collections::hash_map; +use std::collections::HashMap; +use std::ffi::CStr; use std::fmt; use std::str; use std::str::FromStr; use std::string::String; -use tor_util::strings::NUL_BYTE; use external::c_tor_version_as_new_as; use errors::ProtoverError; -use protoset::Version; use protoset::ProtoSet; +use protoset::Version; /// The first version of Tor that included "proto" entries in its descriptors. /// Authorities should use this to decide whether to guess proto lines. /// /// C_RUST_COUPLED: -/// src/or/protover.h `FIRST_TOR_VERSION_TO_ADVERTISE_PROTOCOLS` +/// protover.h `FIRST_TOR_VERSION_TO_ADVERTISE_PROTOCOLS` const FIRST_TOR_VERSION_TO_ADVERTISE_PROTOCOLS: &'static str = "0.2.9.3-alpha"; /// The maximum number of subprotocol version numbers we will attempt to expand /// before concluding that someone is trying to DoS us /// -/// C_RUST_COUPLED: src/or/protover.c `MAX_PROTOCOLS_TO_EXPAND` -const MAX_PROTOCOLS_TO_EXPAND: usize = (1<<16); +/// C_RUST_COUPLED: protover.c `MAX_PROTOCOLS_TO_EXPAND` +const MAX_PROTOCOLS_TO_EXPAND: usize = (1 << 16); /// The maximum size an `UnknownProtocol`'s name may be. pub(crate) const MAX_PROTOCOL_NAME_LENGTH: usize = 100; -/// Currently supported protocols and their versions, as a byte-slice. -/// -/// # Warning -/// -/// This byte-slice ends in a NUL byte. This is so that we can directly convert -/// it to an `&'static CStr` in the FFI code, in order to hand the static string -/// to C in a way that is compatible with C static strings. -/// -/// Rust code which wishes to accesses this string should use -/// `protover::get_supported_protocols()` instead. -/// -/// C_RUST_COUPLED: src/or/protover.c `protover_get_supported_protocols` -pub(crate) const SUPPORTED_PROTOCOLS: &'static [u8] = - b"Cons=1-2 \ - Desc=1-2 \ - DirCache=1-2 \ - HSDir=1-2 \ - HSIntro=3-4 \ - HSRend=1-2 \ - Link=1-5 \ - LinkAuth=1,3 \ - Microdesc=1-2 \ - Relay=1-2\0"; - /// Known subprotocols in Tor. Indicates which subprotocol a relay supports. /// -/// C_RUST_COUPLED: src/or/protover.h `protocol_type_t` +/// C_RUST_COUPLED: protover.h `protocol_type_t` #[derive(Clone, Hash, Eq, PartialEq, Debug)] pub enum Protocol { Cons, @@ -81,7 +57,7 @@ impl fmt::Display for Protocol { /// Translates a string representation of a protocol into a Proto type. /// Error if the string is an unrecognized protocol name. /// -/// C_RUST_COUPLED: src/or/protover.c `PROTOCOL_NAMES` +/// C_RUST_COUPLED: protover.c `PROTOCOL_NAMES` impl FromStr for Protocol { type Err = ProtoverError; @@ -148,21 +124,61 @@ impl From<Protocol> for UnknownProtocol { } } -/// Get the string representation of current supported protocols +#[cfg(feature = "test_linking_hack")] +fn have_linkauth_v1() -> bool { + true +} + +#[cfg(not(feature = "test_linking_hack"))] +fn have_linkauth_v1() -> bool { + use external::c_tor_is_using_nss; + !c_tor_is_using_nss() +} + +/// Get a CStr representation of current supported protocols, for +/// passing to C, or for converting to a `&str` for Rust. /// /// # Returns /// -/// A `String` whose value is the existing protocols supported by tor. +/// An `&'static CStr` whose value is the existing protocols supported by tor. /// Returned data is in the format as follows: /// /// "HSDir=1-1 LinkAuth=1" /// -pub fn get_supported_protocols() -> &'static str { - // The `len() - 1` is to remove the NUL byte. - // The `unwrap` is safe becauase we SUPPORTED_PROTOCOLS is under - // our control. - str::from_utf8(&SUPPORTED_PROTOCOLS[..SUPPORTED_PROTOCOLS.len() - 1]) - .unwrap_or("") +/// # Note +/// +/// Rust code can use the `&'static CStr` as a normal `&'a str` by +/// calling `protover::get_supported_protocols`. +/// +// C_RUST_COUPLED: protover.c `protover_get_supported_protocols` +pub(crate) fn get_supported_protocols_cstr() -> &'static CStr { + if !have_linkauth_v1() { + cstr!( + "Cons=1-2 \ + Desc=1-2 \ + DirCache=1-2 \ + HSDir=1-2 \ + HSIntro=3-4 \ + HSRend=1-2 \ + Link=1-5 \ + LinkAuth=3 \ + Microdesc=1-2 \ + Relay=1-2" + ) + } else { + cstr!( + "Cons=1-2 \ + Desc=1-2 \ + DirCache=1-2 \ + HSDir=1-2 \ + HSIntro=3-4 \ + HSRend=1-2 \ + Link=1-5 \ + LinkAuth=1,3 \ + Microdesc=1-2 \ + Relay=1-2" + ) + } } /// A map of protocol names to the versions of them which are supported. @@ -171,7 +187,7 @@ pub struct ProtoEntry(HashMap<Protocol, ProtoSet>); impl Default for ProtoEntry { fn default() -> ProtoEntry { - ProtoEntry( HashMap::new() ) + ProtoEntry(HashMap::new()) } } @@ -185,7 +201,8 @@ impl ProtoEntry { /// ProtoEntry, which is useful when looking up a specific /// subprotocol. pub fn supported() -> Result<Self, ProtoverError> { - let supported: &'static str = get_supported_protocols(); + let supported_cstr: &'static CStr = get_supported_protocols_cstr(); + let supported: &str = supported_cstr.to_str().unwrap_or(""); supported.parse() } @@ -224,9 +241,7 @@ impl FromStr for ProtoEntry { /// /// # Returns /// - /// A `Result` whose `Ok` value is a `ProtoEntry`, where the - /// first element is the subprotocol type (see `protover::Protocol`) and the last - /// element is an ordered set of `(low, high)` unique version numbers which are supported. + /// A `Result` whose `Ok` value is a `ProtoEntry`. /// Otherwise, the `Err` value of this `Result` is a `ProtoverError`. fn from_str(protocol_entry: &str) -> Result<ProtoEntry, ProtoverError> { let mut proto_entry: ProtoEntry = ProtoEntry::default(); @@ -260,7 +275,7 @@ impl FromStr for ProtoEntry { /// Generate an implementation of `ToString` for either a `ProtoEntry` or an /// `UnvalidatedProtoEntry`. macro_rules! impl_to_string_for_proto_entry { - ($t:ty) => ( + ($t:ty) => { impl ToString for $t { fn to_string(&self) -> String { let mut parts: Vec<String> = Vec::new(); @@ -272,7 +287,7 @@ macro_rules! impl_to_string_for_proto_entry { parts.join(" ") } } - ) + }; } impl_to_string_for_proto_entry!(ProtoEntry); @@ -286,7 +301,7 @@ pub struct UnvalidatedProtoEntry(HashMap<UnknownProtocol, ProtoSet>); impl Default for UnvalidatedProtoEntry { fn default() -> UnvalidatedProtoEntry { - UnvalidatedProtoEntry( HashMap::new() ) + UnvalidatedProtoEntry(HashMap::new()) } } @@ -344,7 +359,7 @@ impl UnvalidatedProtoEntry { pub fn all_supported(&self) -> Option<UnvalidatedProtoEntry> { let mut unsupported: UnvalidatedProtoEntry = UnvalidatedProtoEntry::default(); let supported: ProtoEntry = match ProtoEntry::supported() { - Ok(x) => x, + Ok(x) => x, Err(_) => return None, }; @@ -365,7 +380,6 @@ impl UnvalidatedProtoEntry { let maybe_supported_versions: Option<&ProtoSet> = supported.get(&supported_protocol); let supported_versions: &ProtoSet; - let mut unsupported_versions: ProtoSet; // If the protocol wasn't in the map, then we don't know about it // and don't support any of its versions. Add its versions to the @@ -378,8 +392,7 @@ impl UnvalidatedProtoEntry { } else { supported_versions = maybe_supported_versions.unwrap(); } - unsupported_versions = versions.clone(); - unsupported_versions.retain(|x| !supported_versions.contains(x)); + let unsupported_versions = versions.and_not_in(supported_versions); if !unsupported_versions.is_empty() { unsupported.insert(protocol.clone(), unsupported_versions); @@ -472,11 +485,12 @@ impl UnvalidatedProtoEntry { /// following are true: /// /// * If a protocol name is an empty string, e.g. `"Cons=1,3 =3-5"`. - /// * If a protocol name cannot be parsed as utf-8. - /// * If the version numbers are an empty string, e.g. `"Cons="`. - fn parse_protocol_and_version_str<'a>(protocol_string: &'a str) - -> Result<Vec<(&'a str, &'a str)>, ProtoverError> - { + /// * If an entry has no equals sign, e.g. `"Cons=1,3 Desc"`. + /// * If there is leading or trailing whitespace, e.g. `" Cons=1,3 Link=3"`. + /// * If there is any other extra whitespice, e.g. `"Cons=1,3 Link=3"`. + fn parse_protocol_and_version_str<'a>( + protocol_string: &'a str, + ) -> Result<Vec<(&'a str, &'a str)>, ProtoverError> { let mut protovers: Vec<(&str, &str)> = Vec::new(); for subproto in protocol_string.split(' ') { @@ -512,11 +526,9 @@ impl FromStr for UnvalidatedProtoEntry { /// /// # Returns /// - /// A `Result` whose `Ok` value is a `ProtoSet` holding all of the - /// unique version numbers. + /// A `Result` whose `Ok` value is an `UnvalidatedProtoEntry`. /// - /// The returned `Result`'s `Err` value is an `ProtoverError` whose `Display` - /// impl has a description of the error. + /// The returned `Result`'s `Err` value is an `ProtoverError`. /// /// # Errors /// @@ -543,9 +555,9 @@ impl FromStr for UnvalidatedProtoEntry { impl UnvalidatedProtoEntry { /// Create an `UnknownProtocol`, ignoring whether or not it /// exceeds MAX_PROTOCOL_NAME_LENGTH. - pub(crate) fn from_str_any_len(protocol_string: &str) - -> Result<UnvalidatedProtoEntry, ProtoverError> - { + pub(crate) fn from_str_any_len( + protocol_string: &str, + ) -> Result<UnvalidatedProtoEntry, ProtoverError> { let mut parsed: UnvalidatedProtoEntry = UnvalidatedProtoEntry::default(); let parts: Vec<(&str, &str)> = UnvalidatedProtoEntry::parse_protocol_and_version_str(protocol_string)?; @@ -580,11 +592,11 @@ impl From<ProtoEntry> for UnvalidatedProtoEntry { /// The "protocols" are *not* guaranteed to be known/supported `Protocol`s, in /// order to allow new subprotocols to be introduced even if Directory /// Authorities don't yet know of them. -pub struct ProtoverVote( HashMap<UnknownProtocol, HashMap<Version, usize>> ); +pub struct ProtoverVote(HashMap<UnknownProtocol, HashMap<Version, usize>>); impl Default for ProtoverVote { fn default() -> ProtoverVote { - ProtoverVote( HashMap::new() ) + ProtoverVote(HashMap::new()) } } @@ -598,9 +610,10 @@ impl IntoIterator for ProtoverVote { } impl ProtoverVote { - pub fn entry(&mut self, key: UnknownProtocol) - -> hash_map::Entry<UnknownProtocol, HashMap<Version, usize>> - { + pub fn entry( + &mut self, + key: UnknownProtocol, + ) -> hash_map::Entry<UnknownProtocol, HashMap<Version, usize>> { self.0.entry(key) } @@ -621,8 +634,11 @@ impl ProtoverVote { /// let vote = ProtoverVote::compute(protos, &2); /// assert_eq!("Link=3", vote.to_string()); /// ``` - // C_RUST_COUPLED: /src/or/protover.c protover_compute_vote - pub fn compute(proto_entries: &[UnvalidatedProtoEntry], threshold: &usize) -> UnvalidatedProtoEntry { + // C_RUST_COUPLED: protover.c protover_compute_vote + pub fn compute( + proto_entries: &[UnvalidatedProtoEntry], + threshold: &usize, + ) -> UnvalidatedProtoEntry { let mut all_count: ProtoverVote = ProtoverVote::default(); let mut final_output: UnvalidatedProtoEntry = UnvalidatedProtoEntry::default(); @@ -648,8 +664,7 @@ impl ProtoverVote { all_count.entry(protocol.clone()).or_insert(HashMap::new()); for version in versions.clone().expand() { - let counter: &mut usize = - supported_vers.entry(version).or_insert(0); + let counter: &mut usize = supported_vers.entry(version).or_insert(0); *counter += 1; } } @@ -705,7 +720,7 @@ pub fn is_supported_here(proto: &Protocol, vers: &Version) -> bool { /// /// # Returns /// -/// A `&'static [u8]` encoding a list of protocol names and supported +/// A `&'static CStr` encoding a list of protocol names and supported /// versions. The string takes the following format: /// /// "HSDir=1-1 LinkAuth=1" @@ -721,24 +736,31 @@ pub fn is_supported_here(proto: &Protocol, vers: &Version) -> bool { /// like to use this code in Rust, please see `compute_for_old_tor()`. // // C_RUST_COUPLED: src/rust/protover.c `compute_for_old_tor` -pub(crate) fn compute_for_old_tor_cstr(version: &str) -> &'static [u8] { +pub(crate) fn compute_for_old_tor_cstr(version: &str) -> &'static CStr { + let empty: &'static CStr = cstr!(""); + if c_tor_version_as_new_as(version, FIRST_TOR_VERSION_TO_ADVERTISE_PROTOCOLS) { - return NUL_BYTE; + return empty; } if c_tor_version_as_new_as(version, "0.2.9.1-alpha") { - return b"Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1-2 \ - Link=1-4 LinkAuth=1 Microdesc=1-2 Relay=1-2\0"; + return cstr!( + "Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1-2 \ + Link=1-4 LinkAuth=1 Microdesc=1-2 Relay=1-2" + ); } if c_tor_version_as_new_as(version, "0.2.7.5") { - return b"Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 \ - Link=1-4 LinkAuth=1 Microdesc=1-2 Relay=1-2\0"; + return cstr!( + "Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 \ + Link=1-4 LinkAuth=1 Microdesc=1-2 Relay=1-2" + ); } if c_tor_version_as_new_as(version, "0.2.4.19") { - return b"Cons=1 Desc=1 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 \ - Link=1-4 LinkAuth=1 Microdesc=1 Relay=1-2\0"; + return cstr!( + "Cons=1 Desc=1 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 \ + Link=1-4 LinkAuth=1 Microdesc=1 Relay=1-2" + ); } - - NUL_BYTE + empty } /// Since older versions of Tor cannot infer their own subprotocols, @@ -769,14 +791,11 @@ pub(crate) fn compute_for_old_tor_cstr(version: &str) -> &'static [u8] { // // C_RUST_COUPLED: src/rust/protover.c `compute_for_old_tor` pub fn compute_for_old_tor(version: &str) -> Result<&'static str, ProtoverError> { - let mut computed: &'static [u8] = compute_for_old_tor_cstr(version); - - // Remove the NULL byte at the end. - computed = &computed[..computed.len() - 1]; - - // .from_utf8() fails with a Utf8Error if it couldn't validate the + // .to_str() fails with a Utf8Error if it couldn't validate the // utf-8, so convert that here into an Unparseable ProtoverError. - str::from_utf8(computed).or(Err(ProtoverError::Unparseable)) + compute_for_old_tor_cstr(version) + .to_str() + .or(Err(ProtoverError::Unparseable)) } #[cfg(test)] @@ -810,19 +829,19 @@ mod test { } macro_rules! assert_protoentry_is_parseable { - ($e:expr) => ( + ($e:expr) => { let protoentry: Result<ProtoEntry, ProtoverError> = $e.parse(); assert!(protoentry.is_ok(), format!("{:?}", protoentry.err())); - ) + }; } macro_rules! assert_protoentry_is_unparseable { - ($e:expr) => ( + ($e:expr) => { let protoentry: Result<ProtoEntry, ProtoverError> = $e.parse(); assert!(protoentry.is_err()); - ) + }; } #[test] @@ -908,24 +927,45 @@ mod test { #[test] fn test_contract_protocol_list() { let mut versions = ""; - assert_eq!(String::from(versions), ProtoSet::from_str(&versions).unwrap().to_string()); + assert_eq!( + String::from(versions), + ProtoSet::from_str(&versions).unwrap().to_string() + ); versions = "1"; - assert_eq!(String::from(versions), ProtoSet::from_str(&versions).unwrap().to_string()); + assert_eq!( + String::from(versions), + ProtoSet::from_str(&versions).unwrap().to_string() + ); versions = "1-2"; - assert_eq!(String::from(versions), ProtoSet::from_str(&versions).unwrap().to_string()); + assert_eq!( + String::from(versions), + ProtoSet::from_str(&versions).unwrap().to_string() + ); versions = "1,3"; - assert_eq!(String::from(versions), ProtoSet::from_str(&versions).unwrap().to_string()); + assert_eq!( + String::from(versions), + ProtoSet::from_str(&versions).unwrap().to_string() + ); versions = "1-4"; - assert_eq!(String::from(versions), ProtoSet::from_str(&versions).unwrap().to_string()); + assert_eq!( + String::from(versions), + ProtoSet::from_str(&versions).unwrap().to_string() + ); versions = "1,3,5-7"; - assert_eq!(String::from(versions), ProtoSet::from_str(&versions).unwrap().to_string()); + assert_eq!( + String::from(versions), + ProtoSet::from_str(&versions).unwrap().to_string() + ); versions = "1-3,500"; - assert_eq!(String::from(versions), ProtoSet::from_str(&versions).unwrap().to_string()); + assert_eq!( + String::from(versions), + ProtoSet::from_str(&versions).unwrap().to_string() + ); } } diff --git a/src/rust/protover/tests/protover.rs b/src/rust/protover/tests/protover.rs index 59a4b5a8a0..86e276cf73 100644 --- a/src/rust/protover/tests/protover.rs +++ b/src/rust/protover/tests/protover.rs @@ -1,12 +1,12 @@ -// Copyright (c) 2016-2017, The Tor Project, Inc. */ +// Copyright (c) 2016-2018, The Tor Project, Inc. */ // See LICENSE for licensing information */ extern crate protover; +use protover::errors::ProtoverError; use protover::ProtoEntry; use protover::ProtoverVote; use protover::UnvalidatedProtoEntry; -use protover::errors::ProtoverError; #[test] fn parse_protocol_with_single_proto_and_single_version() { @@ -179,14 +179,16 @@ fn protover_compute_vote_returns_two_protocols_for_two_matching() { #[test] fn protover_compute_vote_returns_one_protocol_when_one_out_of_two_matches() { - let protocols: &[UnvalidatedProtoEntry] = &["Cons=1 Link=2".parse().unwrap(), "Cons=1".parse().unwrap()]; + let protocols: &[UnvalidatedProtoEntry] = + &["Cons=1 Link=2".parse().unwrap(), "Cons=1".parse().unwrap()]; let listed = ProtoverVote::compute(protocols, &2); assert_eq!("Cons=1", listed.to_string()); } #[test] fn protover_compute_vote_returns_protocols_that_it_doesnt_currently_support() { - let protocols: &[UnvalidatedProtoEntry] = &["Foo=1 Cons=2".parse().unwrap(), "Bar=1".parse().unwrap()]; + let protocols: &[UnvalidatedProtoEntry] = + &["Foo=1 Cons=2".parse().unwrap(), "Bar=1".parse().unwrap()]; let listed = ProtoverVote::compute(protocols, &1); assert_eq!("Bar=1 Cons=2 Foo=1", listed.to_string()); } @@ -222,10 +224,12 @@ fn protover_compute_vote_returns_matching_for_longer_mix_with_threshold_two() { #[test] fn protover_compute_vote_handles_duplicated_versions() { - let protocols: &[UnvalidatedProtoEntry] = &["Cons=1".parse().unwrap(), "Cons=1".parse().unwrap()]; + let protocols: &[UnvalidatedProtoEntry] = + &["Cons=1".parse().unwrap(), "Cons=1".parse().unwrap()]; assert_eq!("Cons=1", ProtoverVote::compute(protocols, &2).to_string()); - let protocols: &[UnvalidatedProtoEntry] = &["Cons=1-2".parse().unwrap(), "Cons=1-2".parse().unwrap()]; + let protocols: &[UnvalidatedProtoEntry] = + &["Cons=1-2".parse().unwrap(), "Cons=1-2".parse().unwrap()]; assert_eq!("Cons=1-2", ProtoverVote::compute(protocols, &2).to_string()); } @@ -246,12 +250,18 @@ fn parse_protocol_with_single_protocol_and_two_nonsequential_versions() { #[test] fn protover_is_supported_here_returns_true_for_supported_protocol() { - assert_eq!(true, protover::is_supported_here(&protover::Protocol::Cons, &1)); + assert_eq!( + true, + protover::is_supported_here(&protover::Protocol::Cons, &1) + ); } #[test] fn protover_is_supported_here_returns_false_for_unsupported_protocol() { - assert_eq!(false, protover::is_supported_here(&protover::Protocol::Cons, &5)); + assert_eq!( + false, + protover::is_supported_here(&protover::Protocol::Cons, &5) + ); } #[test] @@ -354,18 +364,18 @@ fn protover_all_supported_should_exclude_some_versions_and_entire_protocols() { #[test] fn protover_all_supported_should_not_dos_anyones_computer() { - let proto: UnvalidatedProtoEntry = "Sleen=1-2147483648".parse().unwrap(); + let proto: UnvalidatedProtoEntry = "Link=1-2147483648".parse().unwrap(); let result: String = proto.all_supported().unwrap().to_string(); - assert_eq!(result, "Sleen=1-2147483648".to_string()); + assert_eq!(result, "Link=6-2147483648".to_string()); } #[test] fn protover_all_supported_should_not_dos_anyones_computer_max_versions() { - let proto: UnvalidatedProtoEntry = "Sleen=1-4294967294".parse().unwrap(); + let proto: UnvalidatedProtoEntry = "Link=1-4294967294".parse().unwrap(); let result: String = proto.all_supported().unwrap().to_string(); - assert_eq!(result, "Sleen=1-4294967294".to_string()); + assert_eq!(result, "Link=6-4294967294".to_string()); } #[test] |