aboutsummaryrefslogtreecommitdiff
path: root/src/rust/protover
diff options
context:
space:
mode:
Diffstat (limited to 'src/rust/protover')
-rw-r--r--src/rust/protover/Cargo.toml11
-rw-r--r--src/rust/protover/errors.rs43
-rw-r--r--src/rust/protover/ffi.rs91
-rw-r--r--src/rust/protover/lib.rs10
-rw-r--r--src/rust/protover/protoset.rs141
-rw-r--r--src/rust/protover/protover.rs246
-rw-r--r--src/rust/protover/tests/protover.rs34
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]