tor/src/rust/protover/protover.rs

814 lines
27 KiB
Rust
Raw Normal View History

// Copyright (c) 2016-2017, The Tor Project, Inc. */
// See LICENSE for licensing information */
use std::collections::HashMap;
use std::collections::hash_map;
use std::fmt;
use std::str;
2017-09-27 21:48:07 +02:00
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;
2017-09-27 21:48:07 +02:00
/// 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`
2017-09-27 21:48:07 +02:00
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`
pub(crate) const MAX_PROTOCOLS_TO_EXPAND: usize = (1<<16);
2017-09-27 21:48:07 +02:00
/// 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";
2017-09-27 21:48:07 +02:00
/// Known subprotocols in Tor. Indicates which subprotocol a relay supports.
///
/// C_RUST_COUPLED: src/or/protover.h `protocol_type_t`
#[derive(Clone, Hash, Eq, PartialEq, Debug)]
pub enum Protocol {
2017-09-27 21:48:07 +02:00
Cons,
Desc,
DirCache,
HSDir,
HSIntro,
HSRend,
Link,
LinkAuth,
Microdesc,
Relay,
}
impl fmt::Display for Protocol {
2017-09-27 21:48:07 +02:00
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{:?}", self)
}
}
/// 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`
impl FromStr for Protocol {
type Err = ProtoverError;
2017-09-27 21:48:07 +02:00
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"Cons" => Ok(Protocol::Cons),
"Desc" => Ok(Protocol::Desc),
"DirCache" => Ok(Protocol::DirCache),
"HSDir" => Ok(Protocol::HSDir),
"HSIntro" => Ok(Protocol::HSIntro),
"HSRend" => Ok(Protocol::HSRend),
"Link" => Ok(Protocol::Link),
"LinkAuth" => Ok(Protocol::LinkAuth),
"Microdesc" => Ok(Protocol::Microdesc),
"Relay" => Ok(Protocol::Relay),
_ => Err(ProtoverError::UnknownProtocol),
2017-09-27 21:48:07 +02:00
}
}
}
/// A protocol string which is not one of the `Protocols` we currently know
/// about.
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
pub struct UnknownProtocol(String);
impl fmt::Display for UnknownProtocol {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
impl FromStr for UnknownProtocol {
type Err = ProtoverError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(UnknownProtocol(s.to_string()))
}
}
impl From<Protocol> for UnknownProtocol {
fn from(p: Protocol) -> UnknownProtocol {
UnknownProtocol(p.to_string())
}
}
2017-09-27 21:48:07 +02:00
/// Get the string representation of current supported protocols
///
/// # Returns
///
/// A `String` 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("")
2017-09-27 21:48:07 +02:00
}
/// A map of protocol names to the versions of them which are supported.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ProtoEntry(HashMap<Protocol, ProtoSet>);
impl Default for ProtoEntry {
fn default() -> ProtoEntry {
ProtoEntry( HashMap::new() )
2017-09-27 21:48:07 +02:00
}
}
2017-09-27 21:48:07 +02:00
impl ProtoEntry {
/// Get an iterator over the `Protocol`s and their `ProtoSet`s in this `ProtoEntry`.
pub fn iter(&self) -> hash_map::Iter<Protocol, ProtoSet> {
self.0.iter()
}
2017-09-27 21:48:07 +02:00
/// Translate the supported tor versions from a string into a
/// ProtoEntry, which is useful when looking up a specific
/// subprotocol.
pub fn supported() -> Result<Self, ProtoverError> {
let supported: &'static str = get_supported_protocols();
supported.parse()
2017-09-27 21:48:07 +02:00
}
pub fn get(&self, protocol: &Protocol) -> Option<&ProtoSet> {
self.0.get(protocol)
}
2017-09-27 21:48:07 +02:00
pub fn insert(&mut self, key: Protocol, value: ProtoSet) {
self.0.insert(key, value);
}
2017-09-27 21:48:07 +02:00
pub fn remove(&mut self, key: &Protocol) -> Option<ProtoSet> {
self.0.remove(key)
}
2017-09-27 21:48:07 +02:00
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
2017-09-27 21:48:07 +02:00
impl FromStr for ProtoEntry {
type Err = ProtoverError;
/// Parse a string of subprotocol types and their version numbers.
///
/// # Inputs
///
/// * A `protocol_entry` string, comprised of a keywords, an "=" sign, and
/// one or more version numbers, each separated by a space. For example,
/// `"Cons=3-4 HSDir=1"`.
///
/// # 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.
/// 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();
let entries = protocol_entry.split(' ');
for entry in entries {
let mut parts = entry.splitn(2, '=');
let proto = match parts.next() {
Some(n) => n,
None => return Err(ProtoverError::Unparseable),
};
let vers = match parts.next() {
Some(n) => n,
None => return Err(ProtoverError::Unparseable),
};
let versions: ProtoSet = vers.parse()?;
let proto_name: Protocol = proto.parse()?;
proto_entry.insert(proto_name, versions);
}
Ok(proto_entry)
}
2017-09-27 21:48:07 +02:00
}
/// Generate an implementation of `ToString` for either a `ProtoEntry` or an
/// `UnvalidatedProtoEntry`.
macro_rules! impl_to_string_for_proto_entry {
($t:ty) => (
impl ToString for $t {
fn to_string(&self) -> String {
let mut parts: Vec<String> = Vec::new();
for (protocol, versions) in self.iter() {
parts.push(format!("{}={}", protocol.to_string(), versions.to_string()));
}
parts.sort_unstable();
parts.join(" ")
}
}
)
}
impl_to_string_for_proto_entry!(ProtoEntry);
impl_to_string_for_proto_entry!(UnvalidatedProtoEntry);
rust: Add new protover::UnvalidatedProtoEntry type. This adds a new protover::UnvalidatedProtoEntry type, which is the UnknownProtocol variant of a ProtoEntry, and refactors several functions which should operate on this type into methods. This also fixes what was previously another difference to the C implementation: if you asked the C version of protovet_compute_vote() to compute a single vote containing "Fribble=", it would return NULL. However, the Rust version would return "Fribble=" since it didn't check if the versions were empty before constructing the string of differences. ("Fribble=" is technically a valid protover string.) This is now fixed, and the Rust version in that case will, analogous to (although safer than) C returning a NULL, return None. * REMOVE internal `contains_only_supported_protocols()` function. * REMOVE `all_supported()` function and refactor it into `UnvalidatedProtoEntry::all_supported()`. * REMOVE `parse_protocols_from_string_with_no_validation()` and refactor it into the more rusty implementation of `impl FromStr for UnvalidatedProtoEntry`. * REMOVE `protover_string_supports_protocol()` and refactor it into `UnvalidatedProtoEntry::supports_protocol()`. * REMOVE `protover_string_supports_protocol_or_later()` and refactor it into `UnvalidatedProtoEntry::supports_protocol_or_later()`. * FIXES part of #24031: https://bugs.torproject.org/24031 rust: Fix another C/Rust different in compute_vote(). This fixes the unittest from the prior commit by checking if the versions are empty before adding a protocol to a vote.
2018-03-21 02:29:36 +01:00
/// A `ProtoEntry`, but whose `Protocols` can be any `UnknownProtocol`, not just
/// the supported ones enumerated in `Protocols`. The protocol versions are
/// validated, however.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct UnvalidatedProtoEntry(HashMap<UnknownProtocol, ProtoSet>);
impl Default for UnvalidatedProtoEntry {
fn default() -> UnvalidatedProtoEntry {
UnvalidatedProtoEntry( HashMap::new() )
}
2017-09-27 21:48:07 +02:00
}
rust: Add new protover::UnvalidatedProtoEntry type. This adds a new protover::UnvalidatedProtoEntry type, which is the UnknownProtocol variant of a ProtoEntry, and refactors several functions which should operate on this type into methods. This also fixes what was previously another difference to the C implementation: if you asked the C version of protovet_compute_vote() to compute a single vote containing "Fribble=", it would return NULL. However, the Rust version would return "Fribble=" since it didn't check if the versions were empty before constructing the string of differences. ("Fribble=" is technically a valid protover string.) This is now fixed, and the Rust version in that case will, analogous to (although safer than) C returning a NULL, return None. * REMOVE internal `contains_only_supported_protocols()` function. * REMOVE `all_supported()` function and refactor it into `UnvalidatedProtoEntry::all_supported()`. * REMOVE `parse_protocols_from_string_with_no_validation()` and refactor it into the more rusty implementation of `impl FromStr for UnvalidatedProtoEntry`. * REMOVE `protover_string_supports_protocol()` and refactor it into `UnvalidatedProtoEntry::supports_protocol()`. * REMOVE `protover_string_supports_protocol_or_later()` and refactor it into `UnvalidatedProtoEntry::supports_protocol_or_later()`. * FIXES part of #24031: https://bugs.torproject.org/24031 rust: Fix another C/Rust different in compute_vote(). This fixes the unittest from the prior commit by checking if the versions are empty before adding a protocol to a vote.
2018-03-21 02:29:36 +01:00
impl UnvalidatedProtoEntry {
/// Get an iterator over the `Protocol`s and their `ProtoSet`s in this `ProtoEntry`.
pub fn iter(&self) -> hash_map::Iter<UnknownProtocol, ProtoSet> {
self.0.iter()
}
2017-09-27 21:48:07 +02:00
rust: Add new protover::UnvalidatedProtoEntry type. This adds a new protover::UnvalidatedProtoEntry type, which is the UnknownProtocol variant of a ProtoEntry, and refactors several functions which should operate on this type into methods. This also fixes what was previously another difference to the C implementation: if you asked the C version of protovet_compute_vote() to compute a single vote containing "Fribble=", it would return NULL. However, the Rust version would return "Fribble=" since it didn't check if the versions were empty before constructing the string of differences. ("Fribble=" is technically a valid protover string.) This is now fixed, and the Rust version in that case will, analogous to (although safer than) C returning a NULL, return None. * REMOVE internal `contains_only_supported_protocols()` function. * REMOVE `all_supported()` function and refactor it into `UnvalidatedProtoEntry::all_supported()`. * REMOVE `parse_protocols_from_string_with_no_validation()` and refactor it into the more rusty implementation of `impl FromStr for UnvalidatedProtoEntry`. * REMOVE `protover_string_supports_protocol()` and refactor it into `UnvalidatedProtoEntry::supports_protocol()`. * REMOVE `protover_string_supports_protocol_or_later()` and refactor it into `UnvalidatedProtoEntry::supports_protocol_or_later()`. * FIXES part of #24031: https://bugs.torproject.org/24031 rust: Fix another C/Rust different in compute_vote(). This fixes the unittest from the prior commit by checking if the versions are empty before adding a protocol to a vote.
2018-03-21 02:29:36 +01:00
pub fn get(&self, protocol: &UnknownProtocol) -> Option<&ProtoSet> {
self.0.get(protocol)
}
2017-09-27 21:48:07 +02:00
rust: Add new protover::UnvalidatedProtoEntry type. This adds a new protover::UnvalidatedProtoEntry type, which is the UnknownProtocol variant of a ProtoEntry, and refactors several functions which should operate on this type into methods. This also fixes what was previously another difference to the C implementation: if you asked the C version of protovet_compute_vote() to compute a single vote containing "Fribble=", it would return NULL. However, the Rust version would return "Fribble=" since it didn't check if the versions were empty before constructing the string of differences. ("Fribble=" is technically a valid protover string.) This is now fixed, and the Rust version in that case will, analogous to (although safer than) C returning a NULL, return None. * REMOVE internal `contains_only_supported_protocols()` function. * REMOVE `all_supported()` function and refactor it into `UnvalidatedProtoEntry::all_supported()`. * REMOVE `parse_protocols_from_string_with_no_validation()` and refactor it into the more rusty implementation of `impl FromStr for UnvalidatedProtoEntry`. * REMOVE `protover_string_supports_protocol()` and refactor it into `UnvalidatedProtoEntry::supports_protocol()`. * REMOVE `protover_string_supports_protocol_or_later()` and refactor it into `UnvalidatedProtoEntry::supports_protocol_or_later()`. * FIXES part of #24031: https://bugs.torproject.org/24031 rust: Fix another C/Rust different in compute_vote(). This fixes the unittest from the prior commit by checking if the versions are empty before adding a protocol to a vote.
2018-03-21 02:29:36 +01:00
pub fn insert(&mut self, key: UnknownProtocol, value: ProtoSet) {
self.0.insert(key, value);
}
2017-09-27 21:48:07 +02:00
rust: Add new protover::UnvalidatedProtoEntry type. This adds a new protover::UnvalidatedProtoEntry type, which is the UnknownProtocol variant of a ProtoEntry, and refactors several functions which should operate on this type into methods. This also fixes what was previously another difference to the C implementation: if you asked the C version of protovet_compute_vote() to compute a single vote containing "Fribble=", it would return NULL. However, the Rust version would return "Fribble=" since it didn't check if the versions were empty before constructing the string of differences. ("Fribble=" is technically a valid protover string.) This is now fixed, and the Rust version in that case will, analogous to (although safer than) C returning a NULL, return None. * REMOVE internal `contains_only_supported_protocols()` function. * REMOVE `all_supported()` function and refactor it into `UnvalidatedProtoEntry::all_supported()`. * REMOVE `parse_protocols_from_string_with_no_validation()` and refactor it into the more rusty implementation of `impl FromStr for UnvalidatedProtoEntry`. * REMOVE `protover_string_supports_protocol()` and refactor it into `UnvalidatedProtoEntry::supports_protocol()`. * REMOVE `protover_string_supports_protocol_or_later()` and refactor it into `UnvalidatedProtoEntry::supports_protocol_or_later()`. * FIXES part of #24031: https://bugs.torproject.org/24031 rust: Fix another C/Rust different in compute_vote(). This fixes the unittest from the prior commit by checking if the versions are empty before adding a protocol to a vote.
2018-03-21 02:29:36 +01:00
pub fn remove(&mut self, key: &UnknownProtocol) -> Option<ProtoSet> {
self.0.remove(key)
}
rust: Add new protover::UnvalidatedProtoEntry type. This adds a new protover::UnvalidatedProtoEntry type, which is the UnknownProtocol variant of a ProtoEntry, and refactors several functions which should operate on this type into methods. This also fixes what was previously another difference to the C implementation: if you asked the C version of protovet_compute_vote() to compute a single vote containing "Fribble=", it would return NULL. However, the Rust version would return "Fribble=" since it didn't check if the versions were empty before constructing the string of differences. ("Fribble=" is technically a valid protover string.) This is now fixed, and the Rust version in that case will, analogous to (although safer than) C returning a NULL, return None. * REMOVE internal `contains_only_supported_protocols()` function. * REMOVE `all_supported()` function and refactor it into `UnvalidatedProtoEntry::all_supported()`. * REMOVE `parse_protocols_from_string_with_no_validation()` and refactor it into the more rusty implementation of `impl FromStr for UnvalidatedProtoEntry`. * REMOVE `protover_string_supports_protocol()` and refactor it into `UnvalidatedProtoEntry::supports_protocol()`. * REMOVE `protover_string_supports_protocol_or_later()` and refactor it into `UnvalidatedProtoEntry::supports_protocol_or_later()`. * FIXES part of #24031: https://bugs.torproject.org/24031 rust: Fix another C/Rust different in compute_vote(). This fixes the unittest from the prior commit by checking if the versions are empty before adding a protocol to a vote.
2018-03-21 02:29:36 +01:00
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
rust: Add new ProtoverVote type and refactor functions to methods. This adds a new type for votes upon `protover::ProtoEntry`s (technically, on `protover::UnvalidatedProtoEntry`s, because the C code does not validate based upon currently known protocols when voting, in order to maintain future-compatibility), and converts several functions which would have operated on this datatype into methods for ease-of-use and readability. This also fixes a behavioural differentce to the C version of protover_compute_vote(). The C version of protover_compute_vote() calls expand_protocol_list() which checks if there would be too many subprotocols *or* expanded individual version numbers, i.e. more than MAX_PROTOCOLS_TO_EXPAND, and does this *per vote* (but only in compute_vote(), everywhere else in the C seems to only care about the number of subprotocols, not the number of individual versions). We need to match its behaviour in Rust and ensure we're not allowing more than it would to get the votes to match. * ADD new `protover::ProtoverVote` datatype. * REMOVE the `protover::compute_vote()` function and refactor it into an equivalent-in-behaviour albeit more memory-efficient voting algorithm based on the new underlying `protover::protoset::ProtoSet` datatype, as `ProtoverVote::compute()`. * REMOVE the `protover::write_vote_to_string()` function, since this functionality is now generated by the impl_to_string_for_proto_entry!() macro for both `ProtoEntry` and `UnvalidatedProtoEntry` (the latter of which is the correct type to return from a voting protocol instance, since the entity voting may not know of all protocols being voted upon or known about by other voting parties). * FIXES part of #24031: https://bugs.torproject.org/24031 rust: Fix a difference in compute_vote() behaviour to C version.
2018-03-21 02:52:41 +01:00
pub fn len(&self) -> usize {
let mut total: usize = 0;
for (_, versions) in self.iter() {
total += versions.len();
}
total
}
rust: Add new protover::UnvalidatedProtoEntry type. This adds a new protover::UnvalidatedProtoEntry type, which is the UnknownProtocol variant of a ProtoEntry, and refactors several functions which should operate on this type into methods. This also fixes what was previously another difference to the C implementation: if you asked the C version of protovet_compute_vote() to compute a single vote containing "Fribble=", it would return NULL. However, the Rust version would return "Fribble=" since it didn't check if the versions were empty before constructing the string of differences. ("Fribble=" is technically a valid protover string.) This is now fixed, and the Rust version in that case will, analogous to (although safer than) C returning a NULL, return None. * REMOVE internal `contains_only_supported_protocols()` function. * REMOVE `all_supported()` function and refactor it into `UnvalidatedProtoEntry::all_supported()`. * REMOVE `parse_protocols_from_string_with_no_validation()` and refactor it into the more rusty implementation of `impl FromStr for UnvalidatedProtoEntry`. * REMOVE `protover_string_supports_protocol()` and refactor it into `UnvalidatedProtoEntry::supports_protocol()`. * REMOVE `protover_string_supports_protocol_or_later()` and refactor it into `UnvalidatedProtoEntry::supports_protocol_or_later()`. * FIXES part of #24031: https://bugs.torproject.org/24031 rust: Fix another C/Rust different in compute_vote(). This fixes the unittest from the prior commit by checking if the versions are empty before adding a protocol to a vote.
2018-03-21 02:29:36 +01:00
/// Determine if we support every protocol a client supports, and if not,
/// determine which protocols we do not have support for.
///
/// # Returns
///
/// Optionally, return parameters which the client supports but which we do not.
///
/// # Examples
/// ```
/// use protover::UnvalidatedProtoEntry;
///
/// let protocols: UnvalidatedProtoEntry = "LinkAuth=1 Microdesc=1-2 Relay=2".parse().unwrap();
/// let unsupported: Option<UnvalidatedProtoEntry> = protocols.all_supported();
/// assert_eq!(true, unsupported.is_none());
///
/// let protocols: UnvalidatedProtoEntry = "Link=1-2 Wombat=9".parse().unwrap();
/// let unsupported: Option<UnvalidatedProtoEntry> = protocols.all_supported();
/// assert_eq!(true, unsupported.is_some());
/// assert_eq!("Wombat=9", &unsupported.unwrap().to_string());
/// ```
pub fn all_supported(&self) -> Option<UnvalidatedProtoEntry> {
let mut unsupported: UnvalidatedProtoEntry = UnvalidatedProtoEntry::default();
let supported: ProtoEntry = match ProtoEntry::supported() {
Ok(x) => x,
Err(_) => return None,
};
2017-09-27 21:48:07 +02:00
rust: Add new protover::UnvalidatedProtoEntry type. This adds a new protover::UnvalidatedProtoEntry type, which is the UnknownProtocol variant of a ProtoEntry, and refactors several functions which should operate on this type into methods. This also fixes what was previously another difference to the C implementation: if you asked the C version of protovet_compute_vote() to compute a single vote containing "Fribble=", it would return NULL. However, the Rust version would return "Fribble=" since it didn't check if the versions were empty before constructing the string of differences. ("Fribble=" is technically a valid protover string.) This is now fixed, and the Rust version in that case will, analogous to (although safer than) C returning a NULL, return None. * REMOVE internal `contains_only_supported_protocols()` function. * REMOVE `all_supported()` function and refactor it into `UnvalidatedProtoEntry::all_supported()`. * REMOVE `parse_protocols_from_string_with_no_validation()` and refactor it into the more rusty implementation of `impl FromStr for UnvalidatedProtoEntry`. * REMOVE `protover_string_supports_protocol()` and refactor it into `UnvalidatedProtoEntry::supports_protocol()`. * REMOVE `protover_string_supports_protocol_or_later()` and refactor it into `UnvalidatedProtoEntry::supports_protocol_or_later()`. * FIXES part of #24031: https://bugs.torproject.org/24031 rust: Fix another C/Rust different in compute_vote(). This fixes the unittest from the prior commit by checking if the versions are empty before adding a protocol to a vote.
2018-03-21 02:29:36 +01:00
for (protocol, versions) in self.iter() {
let is_supported: Result<Protocol, ProtoverError> = protocol.0.parse();
let supported_protocol: Protocol;
// If the protocol wasn't even in the enum, then we definitely don't
// know about it and don't support any of its versions.
if is_supported.is_err() {
if !versions.is_empty() {
unsupported.insert(protocol.clone(), versions.clone());
}
continue;
} else {
supported_protocol = is_supported.unwrap();
}
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
// map (if it has versions).
if maybe_supported_versions.is_none() {
if !versions.is_empty() {
unsupported.insert(protocol.clone(), versions.clone());
}
continue;
} else {
supported_versions = maybe_supported_versions.unwrap();
}
unsupported_versions = versions.clone();
unsupported_versions.retain(|x| !supported_versions.contains(x));
if !unsupported_versions.is_empty() {
unsupported.insert(protocol.clone(), unsupported_versions);
}
}
if unsupported.is_empty() {
return None;
}
Some(unsupported)
}
2017-09-27 21:48:07 +02:00
rust: Add new protover::UnvalidatedProtoEntry type. This adds a new protover::UnvalidatedProtoEntry type, which is the UnknownProtocol variant of a ProtoEntry, and refactors several functions which should operate on this type into methods. This also fixes what was previously another difference to the C implementation: if you asked the C version of protovet_compute_vote() to compute a single vote containing "Fribble=", it would return NULL. However, the Rust version would return "Fribble=" since it didn't check if the versions were empty before constructing the string of differences. ("Fribble=" is technically a valid protover string.) This is now fixed, and the Rust version in that case will, analogous to (although safer than) C returning a NULL, return None. * REMOVE internal `contains_only_supported_protocols()` function. * REMOVE `all_supported()` function and refactor it into `UnvalidatedProtoEntry::all_supported()`. * REMOVE `parse_protocols_from_string_with_no_validation()` and refactor it into the more rusty implementation of `impl FromStr for UnvalidatedProtoEntry`. * REMOVE `protover_string_supports_protocol()` and refactor it into `UnvalidatedProtoEntry::supports_protocol()`. * REMOVE `protover_string_supports_protocol_or_later()` and refactor it into `UnvalidatedProtoEntry::supports_protocol_or_later()`. * FIXES part of #24031: https://bugs.torproject.org/24031 rust: Fix another C/Rust different in compute_vote(). This fixes the unittest from the prior commit by checking if the versions are empty before adding a protocol to a vote.
2018-03-21 02:29:36 +01:00
/// Determine if we have support for some protocol and version.
///
/// # Inputs
///
/// * `proto`, an `UnknownProtocol` to test support for
/// * `vers`, a `Version` which we will go on to determine whether the
/// specified protocol supports.
///
/// # Return
///
/// Returns `true` iff this `UnvalidatedProtoEntry` includes support for the
/// indicated protocol and version, and `false` otherwise.
///
/// # Examples
///
/// ```
/// # use std::str::FromStr;
/// use protover::*;
/// # use protover::errors::ProtoverError;
///
/// # fn do_test () -> Result<UnvalidatedProtoEntry, ProtoverError> {
/// let proto: UnvalidatedProtoEntry = "Link=3-4 Cons=1 Doggo=3-5".parse()?;
/// assert_eq!(true, proto.supports_protocol(&Protocol::Cons.into(), &1));
/// assert_eq!(false, proto.supports_protocol(&Protocol::Cons.into(), &5));
/// assert_eq!(true, proto.supports_protocol(&UnknownProtocol::from_str("Doggo")?, &4));
/// # Ok(proto)
/// # } fn main () { do_test(); }
/// ```
pub fn supports_protocol(&self, proto: &UnknownProtocol, vers: &Version) -> bool {
let supported_versions: &ProtoSet = match self.get(proto) {
2017-09-27 21:48:07 +02:00
Some(n) => n,
rust: Add new protover::UnvalidatedProtoEntry type. This adds a new protover::UnvalidatedProtoEntry type, which is the UnknownProtocol variant of a ProtoEntry, and refactors several functions which should operate on this type into methods. This also fixes what was previously another difference to the C implementation: if you asked the C version of protovet_compute_vote() to compute a single vote containing "Fribble=", it would return NULL. However, the Rust version would return "Fribble=" since it didn't check if the versions were empty before constructing the string of differences. ("Fribble=" is technically a valid protover string.) This is now fixed, and the Rust version in that case will, analogous to (although safer than) C returning a NULL, return None. * REMOVE internal `contains_only_supported_protocols()` function. * REMOVE `all_supported()` function and refactor it into `UnvalidatedProtoEntry::all_supported()`. * REMOVE `parse_protocols_from_string_with_no_validation()` and refactor it into the more rusty implementation of `impl FromStr for UnvalidatedProtoEntry`. * REMOVE `protover_string_supports_protocol()` and refactor it into `UnvalidatedProtoEntry::supports_protocol()`. * REMOVE `protover_string_supports_protocol_or_later()` and refactor it into `UnvalidatedProtoEntry::supports_protocol_or_later()`. * FIXES part of #24031: https://bugs.torproject.org/24031 rust: Fix another C/Rust different in compute_vote(). This fixes the unittest from the prior commit by checking if the versions are empty before adding a protocol to a vote.
2018-03-21 02:29:36 +01:00
None => return false,
2017-09-27 21:48:07 +02:00
};
rust: Add new protover::UnvalidatedProtoEntry type. This adds a new protover::UnvalidatedProtoEntry type, which is the UnknownProtocol variant of a ProtoEntry, and refactors several functions which should operate on this type into methods. This also fixes what was previously another difference to the C implementation: if you asked the C version of protovet_compute_vote() to compute a single vote containing "Fribble=", it would return NULL. However, the Rust version would return "Fribble=" since it didn't check if the versions were empty before constructing the string of differences. ("Fribble=" is technically a valid protover string.) This is now fixed, and the Rust version in that case will, analogous to (although safer than) C returning a NULL, return None. * REMOVE internal `contains_only_supported_protocols()` function. * REMOVE `all_supported()` function and refactor it into `UnvalidatedProtoEntry::all_supported()`. * REMOVE `parse_protocols_from_string_with_no_validation()` and refactor it into the more rusty implementation of `impl FromStr for UnvalidatedProtoEntry`. * REMOVE `protover_string_supports_protocol()` and refactor it into `UnvalidatedProtoEntry::supports_protocol()`. * REMOVE `protover_string_supports_protocol_or_later()` and refactor it into `UnvalidatedProtoEntry::supports_protocol_or_later()`. * FIXES part of #24031: https://bugs.torproject.org/24031 rust: Fix another C/Rust different in compute_vote(). This fixes the unittest from the prior commit by checking if the versions are empty before adding a protocol to a vote.
2018-03-21 02:29:36 +01:00
supported_versions.contains(&vers)
}
2017-09-27 21:48:07 +02:00
rust: Add new protover::UnvalidatedProtoEntry type. This adds a new protover::UnvalidatedProtoEntry type, which is the UnknownProtocol variant of a ProtoEntry, and refactors several functions which should operate on this type into methods. This also fixes what was previously another difference to the C implementation: if you asked the C version of protovet_compute_vote() to compute a single vote containing "Fribble=", it would return NULL. However, the Rust version would return "Fribble=" since it didn't check if the versions were empty before constructing the string of differences. ("Fribble=" is technically a valid protover string.) This is now fixed, and the Rust version in that case will, analogous to (although safer than) C returning a NULL, return None. * REMOVE internal `contains_only_supported_protocols()` function. * REMOVE `all_supported()` function and refactor it into `UnvalidatedProtoEntry::all_supported()`. * REMOVE `parse_protocols_from_string_with_no_validation()` and refactor it into the more rusty implementation of `impl FromStr for UnvalidatedProtoEntry`. * REMOVE `protover_string_supports_protocol()` and refactor it into `UnvalidatedProtoEntry::supports_protocol()`. * REMOVE `protover_string_supports_protocol_or_later()` and refactor it into `UnvalidatedProtoEntry::supports_protocol_or_later()`. * FIXES part of #24031: https://bugs.torproject.org/24031 rust: Fix another C/Rust different in compute_vote(). This fixes the unittest from the prior commit by checking if the versions are empty before adding a protocol to a vote.
2018-03-21 02:29:36 +01:00
/// As `UnvalidatedProtoEntry::supports_protocol()`, but also returns `true`
/// if any later version of the protocol is supported.
///
/// # Examples
/// ```
/// use protover::*;
/// # use protover::errors::ProtoverError;
///
/// # fn do_test () -> Result<UnvalidatedProtoEntry, ProtoverError> {
/// let proto: UnvalidatedProtoEntry = "Link=3-4 Cons=5".parse()?;
///
/// assert_eq!(true, proto.supports_protocol_or_later(&Protocol::Cons.into(), &5));
/// assert_eq!(true, proto.supports_protocol_or_later(&Protocol::Cons.into(), &4));
/// assert_eq!(false, proto.supports_protocol_or_later(&Protocol::Cons.into(), &6));
/// # Ok(proto)
/// # } fn main () { do_test(); }
/// ```
pub fn supports_protocol_or_later(&self, proto: &UnknownProtocol, vers: &Version) -> bool {
let supported_versions: &ProtoSet = match self.get(&proto) {
2017-09-27 21:48:07 +02:00
Some(n) => n,
rust: Add new protover::UnvalidatedProtoEntry type. This adds a new protover::UnvalidatedProtoEntry type, which is the UnknownProtocol variant of a ProtoEntry, and refactors several functions which should operate on this type into methods. This also fixes what was previously another difference to the C implementation: if you asked the C version of protovet_compute_vote() to compute a single vote containing "Fribble=", it would return NULL. However, the Rust version would return "Fribble=" since it didn't check if the versions were empty before constructing the string of differences. ("Fribble=" is technically a valid protover string.) This is now fixed, and the Rust version in that case will, analogous to (although safer than) C returning a NULL, return None. * REMOVE internal `contains_only_supported_protocols()` function. * REMOVE `all_supported()` function and refactor it into `UnvalidatedProtoEntry::all_supported()`. * REMOVE `parse_protocols_from_string_with_no_validation()` and refactor it into the more rusty implementation of `impl FromStr for UnvalidatedProtoEntry`. * REMOVE `protover_string_supports_protocol()` and refactor it into `UnvalidatedProtoEntry::supports_protocol()`. * REMOVE `protover_string_supports_protocol_or_later()` and refactor it into `UnvalidatedProtoEntry::supports_protocol_or_later()`. * FIXES part of #24031: https://bugs.torproject.org/24031 rust: Fix another C/Rust different in compute_vote(). This fixes the unittest from the prior commit by checking if the versions are empty before adding a protocol to a vote.
2018-03-21 02:29:36 +01:00
None => return false,
2017-09-27 21:48:07 +02:00
};
rust: Add new protover::UnvalidatedProtoEntry type. This adds a new protover::UnvalidatedProtoEntry type, which is the UnknownProtocol variant of a ProtoEntry, and refactors several functions which should operate on this type into methods. This also fixes what was previously another difference to the C implementation: if you asked the C version of protovet_compute_vote() to compute a single vote containing "Fribble=", it would return NULL. However, the Rust version would return "Fribble=" since it didn't check if the versions were empty before constructing the string of differences. ("Fribble=" is technically a valid protover string.) This is now fixed, and the Rust version in that case will, analogous to (although safer than) C returning a NULL, return None. * REMOVE internal `contains_only_supported_protocols()` function. * REMOVE `all_supported()` function and refactor it into `UnvalidatedProtoEntry::all_supported()`. * REMOVE `parse_protocols_from_string_with_no_validation()` and refactor it into the more rusty implementation of `impl FromStr for UnvalidatedProtoEntry`. * REMOVE `protover_string_supports_protocol()` and refactor it into `UnvalidatedProtoEntry::supports_protocol()`. * REMOVE `protover_string_supports_protocol_or_later()` and refactor it into `UnvalidatedProtoEntry::supports_protocol_or_later()`. * FIXES part of #24031: https://bugs.torproject.org/24031 rust: Fix another C/Rust different in compute_vote(). This fixes the unittest from the prior commit by checking if the versions are empty before adding a protocol to a vote.
2018-03-21 02:29:36 +01:00
supported_versions.iter().any(|v| v.1 >= *vers)
}
}
2017-09-27 21:48:07 +02:00
rust: Add new protover::UnvalidatedProtoEntry type. This adds a new protover::UnvalidatedProtoEntry type, which is the UnknownProtocol variant of a ProtoEntry, and refactors several functions which should operate on this type into methods. This also fixes what was previously another difference to the C implementation: if you asked the C version of protovet_compute_vote() to compute a single vote containing "Fribble=", it would return NULL. However, the Rust version would return "Fribble=" since it didn't check if the versions were empty before constructing the string of differences. ("Fribble=" is technically a valid protover string.) This is now fixed, and the Rust version in that case will, analogous to (although safer than) C returning a NULL, return None. * REMOVE internal `contains_only_supported_protocols()` function. * REMOVE `all_supported()` function and refactor it into `UnvalidatedProtoEntry::all_supported()`. * REMOVE `parse_protocols_from_string_with_no_validation()` and refactor it into the more rusty implementation of `impl FromStr for UnvalidatedProtoEntry`. * REMOVE `protover_string_supports_protocol()` and refactor it into `UnvalidatedProtoEntry::supports_protocol()`. * REMOVE `protover_string_supports_protocol_or_later()` and refactor it into `UnvalidatedProtoEntry::supports_protocol_or_later()`. * FIXES part of #24031: https://bugs.torproject.org/24031 rust: Fix another C/Rust different in compute_vote(). This fixes the unittest from the prior commit by checking if the versions are empty before adding a protocol to a vote.
2018-03-21 02:29:36 +01:00
impl FromStr for UnvalidatedProtoEntry {
type Err = ProtoverError;
/// Parses a protocol list without validating the protocol names.
///
/// # Inputs
///
/// * `protocol_string`, a string comprised of keys and values, both which are
/// strings. The keys are the protocol names while values are a string
/// representation of the supported versions.
///
/// The input is _not_ expected to be a subset of the Protocol types
///
/// # Returns
///
/// A `Result` whose `Ok` value is a `ProtoSet` holding all of the
/// unique version numbers.
///
/// The returned `Result`'s `Err` value is an `ProtoverError` whose `Display`
/// impl has a description of the error.
///
/// # Errors
///
/// This function will error if:
///
/// * The protocol string does not follow the "protocol_name=version_list"
/// expected format, or
/// * If the version string is malformed. See `impl FromStr for ProtoSet`.
fn from_str(protocol_string: &str) -> Result<UnvalidatedProtoEntry, ProtoverError> {
let mut parsed: UnvalidatedProtoEntry = UnvalidatedProtoEntry::default();
for subproto in protocol_string.split(' ') {
let mut parts = subproto.splitn(2, '=');
2017-09-27 21:48:07 +02:00
rust: Add new protover::UnvalidatedProtoEntry type. This adds a new protover::UnvalidatedProtoEntry type, which is the UnknownProtocol variant of a ProtoEntry, and refactors several functions which should operate on this type into methods. This also fixes what was previously another difference to the C implementation: if you asked the C version of protovet_compute_vote() to compute a single vote containing "Fribble=", it would return NULL. However, the Rust version would return "Fribble=" since it didn't check if the versions were empty before constructing the string of differences. ("Fribble=" is technically a valid protover string.) This is now fixed, and the Rust version in that case will, analogous to (although safer than) C returning a NULL, return None. * REMOVE internal `contains_only_supported_protocols()` function. * REMOVE `all_supported()` function and refactor it into `UnvalidatedProtoEntry::all_supported()`. * REMOVE `parse_protocols_from_string_with_no_validation()` and refactor it into the more rusty implementation of `impl FromStr for UnvalidatedProtoEntry`. * REMOVE `protover_string_supports_protocol()` and refactor it into `UnvalidatedProtoEntry::supports_protocol()`. * REMOVE `protover_string_supports_protocol_or_later()` and refactor it into `UnvalidatedProtoEntry::supports_protocol_or_later()`. * FIXES part of #24031: https://bugs.torproject.org/24031 rust: Fix another C/Rust different in compute_vote(). This fixes the unittest from the prior commit by checking if the versions are empty before adding a protocol to a vote.
2018-03-21 02:29:36 +01:00
let name = match parts.next() {
Some("") => return Err(ProtoverError::Unparseable),
Some(n) => n,
None => return Err(ProtoverError::Unparseable),
};
let vers = match parts.next() {
Some(n) => n,
None => return Err(ProtoverError::Unparseable),
};
let versions = ProtoSet::from_str(vers)?;
let protocol = UnknownProtocol::from_str(name)?;
parsed.insert(protocol, versions);
}
Ok(parsed)
}
}
/// Pretend a `ProtoEntry` is actually an `UnvalidatedProtoEntry`.
impl From<ProtoEntry> for UnvalidatedProtoEntry {
fn from(proto_entry: ProtoEntry) -> UnvalidatedProtoEntry {
let mut unvalidated: UnvalidatedProtoEntry = UnvalidatedProtoEntry::default();
for (protocol, versions) in proto_entry.iter() {
unvalidated.insert(UnknownProtocol::from(protocol.clone()), versions.clone());
}
unvalidated
2017-09-27 21:48:07 +02:00
}
}
rust: Add new ProtoverVote type and refactor functions to methods. This adds a new type for votes upon `protover::ProtoEntry`s (technically, on `protover::UnvalidatedProtoEntry`s, because the C code does not validate based upon currently known protocols when voting, in order to maintain future-compatibility), and converts several functions which would have operated on this datatype into methods for ease-of-use and readability. This also fixes a behavioural differentce to the C version of protover_compute_vote(). The C version of protover_compute_vote() calls expand_protocol_list() which checks if there would be too many subprotocols *or* expanded individual version numbers, i.e. more than MAX_PROTOCOLS_TO_EXPAND, and does this *per vote* (but only in compute_vote(), everywhere else in the C seems to only care about the number of subprotocols, not the number of individual versions). We need to match its behaviour in Rust and ensure we're not allowing more than it would to get the votes to match. * ADD new `protover::ProtoverVote` datatype. * REMOVE the `protover::compute_vote()` function and refactor it into an equivalent-in-behaviour albeit more memory-efficient voting algorithm based on the new underlying `protover::protoset::ProtoSet` datatype, as `ProtoverVote::compute()`. * REMOVE the `protover::write_vote_to_string()` function, since this functionality is now generated by the impl_to_string_for_proto_entry!() macro for both `ProtoEntry` and `UnvalidatedProtoEntry` (the latter of which is the correct type to return from a voting protocol instance, since the entity voting may not know of all protocols being voted upon or known about by other voting parties). * FIXES part of #24031: https://bugs.torproject.org/24031 rust: Fix a difference in compute_vote() behaviour to C version.
2018-03-21 02:52:41 +01:00
/// A mapping of protocols to a count of how many times each of their `Version`s
/// were voted for or supported.
2017-09-27 21:48:07 +02:00
///
rust: Add new ProtoverVote type and refactor functions to methods. This adds a new type for votes upon `protover::ProtoEntry`s (technically, on `protover::UnvalidatedProtoEntry`s, because the C code does not validate based upon currently known protocols when voting, in order to maintain future-compatibility), and converts several functions which would have operated on this datatype into methods for ease-of-use and readability. This also fixes a behavioural differentce to the C version of protover_compute_vote(). The C version of protover_compute_vote() calls expand_protocol_list() which checks if there would be too many subprotocols *or* expanded individual version numbers, i.e. more than MAX_PROTOCOLS_TO_EXPAND, and does this *per vote* (but only in compute_vote(), everywhere else in the C seems to only care about the number of subprotocols, not the number of individual versions). We need to match its behaviour in Rust and ensure we're not allowing more than it would to get the votes to match. * ADD new `protover::ProtoverVote` datatype. * REMOVE the `protover::compute_vote()` function and refactor it into an equivalent-in-behaviour albeit more memory-efficient voting algorithm based on the new underlying `protover::protoset::ProtoSet` datatype, as `ProtoverVote::compute()`. * REMOVE the `protover::write_vote_to_string()` function, since this functionality is now generated by the impl_to_string_for_proto_entry!() macro for both `ProtoEntry` and `UnvalidatedProtoEntry` (the latter of which is the correct type to return from a voting protocol instance, since the entity voting may not know of all protocols being voted upon or known about by other voting parties). * FIXES part of #24031: https://bugs.torproject.org/24031 rust: Fix a difference in compute_vote() behaviour to C version.
2018-03-21 02:52:41 +01:00
/// # Warning
2017-09-27 21:48:07 +02:00
///
rust: Add new ProtoverVote type and refactor functions to methods. This adds a new type for votes upon `protover::ProtoEntry`s (technically, on `protover::UnvalidatedProtoEntry`s, because the C code does not validate based upon currently known protocols when voting, in order to maintain future-compatibility), and converts several functions which would have operated on this datatype into methods for ease-of-use and readability. This also fixes a behavioural differentce to the C version of protover_compute_vote(). The C version of protover_compute_vote() calls expand_protocol_list() which checks if there would be too many subprotocols *or* expanded individual version numbers, i.e. more than MAX_PROTOCOLS_TO_EXPAND, and does this *per vote* (but only in compute_vote(), everywhere else in the C seems to only care about the number of subprotocols, not the number of individual versions). We need to match its behaviour in Rust and ensure we're not allowing more than it would to get the votes to match. * ADD new `protover::ProtoverVote` datatype. * REMOVE the `protover::compute_vote()` function and refactor it into an equivalent-in-behaviour albeit more memory-efficient voting algorithm based on the new underlying `protover::protoset::ProtoSet` datatype, as `ProtoverVote::compute()`. * REMOVE the `protover::write_vote_to_string()` function, since this functionality is now generated by the impl_to_string_for_proto_entry!() macro for both `ProtoEntry` and `UnvalidatedProtoEntry` (the latter of which is the correct type to return from a voting protocol instance, since the entity voting may not know of all protocols being voted upon or known about by other voting parties). * FIXES part of #24031: https://bugs.torproject.org/24031 rust: Fix a difference in compute_vote() behaviour to C version.
2018-03-21 02:52:41 +01:00
/// 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>> );
impl Default for ProtoverVote {
fn default() -> ProtoverVote {
ProtoverVote( HashMap::new() )
2017-09-27 21:48:07 +02:00
}
rust: Add new ProtoverVote type and refactor functions to methods. This adds a new type for votes upon `protover::ProtoEntry`s (technically, on `protover::UnvalidatedProtoEntry`s, because the C code does not validate based upon currently known protocols when voting, in order to maintain future-compatibility), and converts several functions which would have operated on this datatype into methods for ease-of-use and readability. This also fixes a behavioural differentce to the C version of protover_compute_vote(). The C version of protover_compute_vote() calls expand_protocol_list() which checks if there would be too many subprotocols *or* expanded individual version numbers, i.e. more than MAX_PROTOCOLS_TO_EXPAND, and does this *per vote* (but only in compute_vote(), everywhere else in the C seems to only care about the number of subprotocols, not the number of individual versions). We need to match its behaviour in Rust and ensure we're not allowing more than it would to get the votes to match. * ADD new `protover::ProtoverVote` datatype. * REMOVE the `protover::compute_vote()` function and refactor it into an equivalent-in-behaviour albeit more memory-efficient voting algorithm based on the new underlying `protover::protoset::ProtoSet` datatype, as `ProtoverVote::compute()`. * REMOVE the `protover::write_vote_to_string()` function, since this functionality is now generated by the impl_to_string_for_proto_entry!() macro for both `ProtoEntry` and `UnvalidatedProtoEntry` (the latter of which is the correct type to return from a voting protocol instance, since the entity voting may not know of all protocols being voted upon or known about by other voting parties). * FIXES part of #24031: https://bugs.torproject.org/24031 rust: Fix a difference in compute_vote() behaviour to C version.
2018-03-21 02:52:41 +01:00
}
2017-09-27 21:48:07 +02:00
rust: Add new ProtoverVote type and refactor functions to methods. This adds a new type for votes upon `protover::ProtoEntry`s (technically, on `protover::UnvalidatedProtoEntry`s, because the C code does not validate based upon currently known protocols when voting, in order to maintain future-compatibility), and converts several functions which would have operated on this datatype into methods for ease-of-use and readability. This also fixes a behavioural differentce to the C version of protover_compute_vote(). The C version of protover_compute_vote() calls expand_protocol_list() which checks if there would be too many subprotocols *or* expanded individual version numbers, i.e. more than MAX_PROTOCOLS_TO_EXPAND, and does this *per vote* (but only in compute_vote(), everywhere else in the C seems to only care about the number of subprotocols, not the number of individual versions). We need to match its behaviour in Rust and ensure we're not allowing more than it would to get the votes to match. * ADD new `protover::ProtoverVote` datatype. * REMOVE the `protover::compute_vote()` function and refactor it into an equivalent-in-behaviour albeit more memory-efficient voting algorithm based on the new underlying `protover::protoset::ProtoSet` datatype, as `ProtoverVote::compute()`. * REMOVE the `protover::write_vote_to_string()` function, since this functionality is now generated by the impl_to_string_for_proto_entry!() macro for both `ProtoEntry` and `UnvalidatedProtoEntry` (the latter of which is the correct type to return from a voting protocol instance, since the entity voting may not know of all protocols being voted upon or known about by other voting parties). * FIXES part of #24031: https://bugs.torproject.org/24031 rust: Fix a difference in compute_vote() behaviour to C version.
2018-03-21 02:52:41 +01:00
impl IntoIterator for ProtoverVote {
type Item = (UnknownProtocol, HashMap<Version, usize>);
type IntoIter = hash_map::IntoIter<UnknownProtocol, HashMap<Version, usize>>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
2017-09-27 21:48:07 +02:00
}
rust: Add new ProtoverVote type and refactor functions to methods. This adds a new type for votes upon `protover::ProtoEntry`s (technically, on `protover::UnvalidatedProtoEntry`s, because the C code does not validate based upon currently known protocols when voting, in order to maintain future-compatibility), and converts several functions which would have operated on this datatype into methods for ease-of-use and readability. This also fixes a behavioural differentce to the C version of protover_compute_vote(). The C version of protover_compute_vote() calls expand_protocol_list() which checks if there would be too many subprotocols *or* expanded individual version numbers, i.e. more than MAX_PROTOCOLS_TO_EXPAND, and does this *per vote* (but only in compute_vote(), everywhere else in the C seems to only care about the number of subprotocols, not the number of individual versions). We need to match its behaviour in Rust and ensure we're not allowing more than it would to get the votes to match. * ADD new `protover::ProtoverVote` datatype. * REMOVE the `protover::compute_vote()` function and refactor it into an equivalent-in-behaviour albeit more memory-efficient voting algorithm based on the new underlying `protover::protoset::ProtoSet` datatype, as `ProtoverVote::compute()`. * REMOVE the `protover::write_vote_to_string()` function, since this functionality is now generated by the impl_to_string_for_proto_entry!() macro for both `ProtoEntry` and `UnvalidatedProtoEntry` (the latter of which is the correct type to return from a voting protocol instance, since the entity voting may not know of all protocols being voted upon or known about by other voting parties). * FIXES part of #24031: https://bugs.torproject.org/24031 rust: Fix a difference in compute_vote() behaviour to C version.
2018-03-21 02:52:41 +01:00
}
2017-09-27 21:48:07 +02:00
rust: Add new ProtoverVote type and refactor functions to methods. This adds a new type for votes upon `protover::ProtoEntry`s (technically, on `protover::UnvalidatedProtoEntry`s, because the C code does not validate based upon currently known protocols when voting, in order to maintain future-compatibility), and converts several functions which would have operated on this datatype into methods for ease-of-use and readability. This also fixes a behavioural differentce to the C version of protover_compute_vote(). The C version of protover_compute_vote() calls expand_protocol_list() which checks if there would be too many subprotocols *or* expanded individual version numbers, i.e. more than MAX_PROTOCOLS_TO_EXPAND, and does this *per vote* (but only in compute_vote(), everywhere else in the C seems to only care about the number of subprotocols, not the number of individual versions). We need to match its behaviour in Rust and ensure we're not allowing more than it would to get the votes to match. * ADD new `protover::ProtoverVote` datatype. * REMOVE the `protover::compute_vote()` function and refactor it into an equivalent-in-behaviour albeit more memory-efficient voting algorithm based on the new underlying `protover::protoset::ProtoSet` datatype, as `ProtoverVote::compute()`. * REMOVE the `protover::write_vote_to_string()` function, since this functionality is now generated by the impl_to_string_for_proto_entry!() macro for both `ProtoEntry` and `UnvalidatedProtoEntry` (the latter of which is the correct type to return from a voting protocol instance, since the entity voting may not know of all protocols being voted upon or known about by other voting parties). * FIXES part of #24031: https://bugs.torproject.org/24031 rust: Fix a difference in compute_vote() behaviour to C version.
2018-03-21 02:52:41 +01:00
impl ProtoverVote {
pub fn entry(&mut self, key: UnknownProtocol)
-> hash_map::Entry<UnknownProtocol, HashMap<Version, usize>>
{
self.0.entry(key)
}
2017-09-27 21:48:07 +02:00
rust: Add new ProtoverVote type and refactor functions to methods. This adds a new type for votes upon `protover::ProtoEntry`s (technically, on `protover::UnvalidatedProtoEntry`s, because the C code does not validate based upon currently known protocols when voting, in order to maintain future-compatibility), and converts several functions which would have operated on this datatype into methods for ease-of-use and readability. This also fixes a behavioural differentce to the C version of protover_compute_vote(). The C version of protover_compute_vote() calls expand_protocol_list() which checks if there would be too many subprotocols *or* expanded individual version numbers, i.e. more than MAX_PROTOCOLS_TO_EXPAND, and does this *per vote* (but only in compute_vote(), everywhere else in the C seems to only care about the number of subprotocols, not the number of individual versions). We need to match its behaviour in Rust and ensure we're not allowing more than it would to get the votes to match. * ADD new `protover::ProtoverVote` datatype. * REMOVE the `protover::compute_vote()` function and refactor it into an equivalent-in-behaviour albeit more memory-efficient voting algorithm based on the new underlying `protover::protoset::ProtoSet` datatype, as `ProtoverVote::compute()`. * REMOVE the `protover::write_vote_to_string()` function, since this functionality is now generated by the impl_to_string_for_proto_entry!() macro for both `ProtoEntry` and `UnvalidatedProtoEntry` (the latter of which is the correct type to return from a voting protocol instance, since the entity voting may not know of all protocols being voted upon or known about by other voting parties). * FIXES part of #24031: https://bugs.torproject.org/24031 rust: Fix a difference in compute_vote() behaviour to C version.
2018-03-21 02:52:41 +01:00
/// Protocol voting implementation.
///
/// Given a slice of `UnvalidatedProtoEntry`s and a vote `threshold`, return
/// a new `UnvalidatedProtoEntry` encoding all of the protocols that are
/// listed by at least `threshold` of the inputs.
///
/// # Examples
///
/// ```
/// use protover::ProtoverVote;
/// use protover::UnvalidatedProtoEntry;
///
/// let protos: &[UnvalidatedProtoEntry] = &["Link=3-4".parse().unwrap(),
/// "Link=3".parse().unwrap()];
/// 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 {
let mut all_count: ProtoverVote = ProtoverVote::default();
let mut final_output: UnvalidatedProtoEntry = UnvalidatedProtoEntry::default();
if proto_entries.is_empty() {
return final_output;
2017-09-27 21:48:07 +02:00
}
rust: Add new ProtoverVote type and refactor functions to methods. This adds a new type for votes upon `protover::ProtoEntry`s (technically, on `protover::UnvalidatedProtoEntry`s, because the C code does not validate based upon currently known protocols when voting, in order to maintain future-compatibility), and converts several functions which would have operated on this datatype into methods for ease-of-use and readability. This also fixes a behavioural differentce to the C version of protover_compute_vote(). The C version of protover_compute_vote() calls expand_protocol_list() which checks if there would be too many subprotocols *or* expanded individual version numbers, i.e. more than MAX_PROTOCOLS_TO_EXPAND, and does this *per vote* (but only in compute_vote(), everywhere else in the C seems to only care about the number of subprotocols, not the number of individual versions). We need to match its behaviour in Rust and ensure we're not allowing more than it would to get the votes to match. * ADD new `protover::ProtoverVote` datatype. * REMOVE the `protover::compute_vote()` function and refactor it into an equivalent-in-behaviour albeit more memory-efficient voting algorithm based on the new underlying `protover::protoset::ProtoSet` datatype, as `ProtoverVote::compute()`. * REMOVE the `protover::write_vote_to_string()` function, since this functionality is now generated by the impl_to_string_for_proto_entry!() macro for both `ProtoEntry` and `UnvalidatedProtoEntry` (the latter of which is the correct type to return from a voting protocol instance, since the entity voting may not know of all protocols being voted upon or known about by other voting parties). * FIXES part of #24031: https://bugs.torproject.org/24031 rust: Fix a difference in compute_vote() behaviour to C version.
2018-03-21 02:52:41 +01:00
// parse and collect all of the protos and their versions and collect them
for vote in proto_entries {
// C_RUST_DIFFERS: This doesn't actually differ, bu this check on
// the total is here to make it match. Because the C version calls
// expand_protocol_list() which checks if there would be too many
// subprotocols *or* individual version numbers, i.e. more than
// MAX_PROTOCOLS_TO_EXPAND, and does this *per vote*, we need to
// match it's behaviour and ensure we're not allowing more than it
// would.
if vote.len() > MAX_PROTOCOLS_TO_EXPAND {
continue;
}
for (protocol, versions) in vote.iter() {
let supported_vers: &mut HashMap<Version, usize> =
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);
*counter += 1;
}
}
2017-09-27 21:48:07 +02:00
}
rust: Add new ProtoverVote type and refactor functions to methods. This adds a new type for votes upon `protover::ProtoEntry`s (technically, on `protover::UnvalidatedProtoEntry`s, because the C code does not validate based upon currently known protocols when voting, in order to maintain future-compatibility), and converts several functions which would have operated on this datatype into methods for ease-of-use and readability. This also fixes a behavioural differentce to the C version of protover_compute_vote(). The C version of protover_compute_vote() calls expand_protocol_list() which checks if there would be too many subprotocols *or* expanded individual version numbers, i.e. more than MAX_PROTOCOLS_TO_EXPAND, and does this *per vote* (but only in compute_vote(), everywhere else in the C seems to only care about the number of subprotocols, not the number of individual versions). We need to match its behaviour in Rust and ensure we're not allowing more than it would to get the votes to match. * ADD new `protover::ProtoverVote` datatype. * REMOVE the `protover::compute_vote()` function and refactor it into an equivalent-in-behaviour albeit more memory-efficient voting algorithm based on the new underlying `protover::protoset::ProtoSet` datatype, as `ProtoverVote::compute()`. * REMOVE the `protover::write_vote_to_string()` function, since this functionality is now generated by the impl_to_string_for_proto_entry!() macro for both `ProtoEntry` and `UnvalidatedProtoEntry` (the latter of which is the correct type to return from a voting protocol instance, since the entity voting may not know of all protocols being voted upon or known about by other voting parties). * FIXES part of #24031: https://bugs.torproject.org/24031 rust: Fix a difference in compute_vote() behaviour to C version.
2018-03-21 02:52:41 +01:00
for (protocol, mut versions) in all_count {
// Go through and remove versions that are less than the threshold
versions.retain(|_, count| *count as usize >= *threshold);
2017-09-27 21:48:07 +02:00
rust: Add new ProtoverVote type and refactor functions to methods. This adds a new type for votes upon `protover::ProtoEntry`s (technically, on `protover::UnvalidatedProtoEntry`s, because the C code does not validate based upon currently known protocols when voting, in order to maintain future-compatibility), and converts several functions which would have operated on this datatype into methods for ease-of-use and readability. This also fixes a behavioural differentce to the C version of protover_compute_vote(). The C version of protover_compute_vote() calls expand_protocol_list() which checks if there would be too many subprotocols *or* expanded individual version numbers, i.e. more than MAX_PROTOCOLS_TO_EXPAND, and does this *per vote* (but only in compute_vote(), everywhere else in the C seems to only care about the number of subprotocols, not the number of individual versions). We need to match its behaviour in Rust and ensure we're not allowing more than it would to get the votes to match. * ADD new `protover::ProtoverVote` datatype. * REMOVE the `protover::compute_vote()` function and refactor it into an equivalent-in-behaviour albeit more memory-efficient voting algorithm based on the new underlying `protover::protoset::ProtoSet` datatype, as `ProtoverVote::compute()`. * REMOVE the `protover::write_vote_to_string()` function, since this functionality is now generated by the impl_to_string_for_proto_entry!() macro for both `ProtoEntry` and `UnvalidatedProtoEntry` (the latter of which is the correct type to return from a voting protocol instance, since the entity voting may not know of all protocols being voted upon or known about by other voting parties). * FIXES part of #24031: https://bugs.torproject.org/24031 rust: Fix a difference in compute_vote() behaviour to C version.
2018-03-21 02:52:41 +01:00
if versions.len() > 0 {
let voted_versions: Vec<Version> = versions.keys().cloned().collect();
let voted_protoset: ProtoSet = ProtoSet::from(voted_versions);
final_output.insert(protocol, voted_protoset);
}
}
final_output
2017-09-27 21:48:07 +02:00
}
}
/// Returns a boolean indicating whether the given protocol and version is
/// supported in any of the existing Tor protocols
///
/// # Examples
/// ```
/// use protover::*;
///
/// let is_supported = is_supported_here(Proto::Link, 10);
2017-09-27 21:48:07 +02:00
/// assert_eq!(false, is_supported);
///
/// let is_supported = is_supported_here(Proto::Link, 1);
/// assert_eq!(true, is_supported);
/// ```
pub fn is_supported_here(proto: Proto, vers: Version) -> bool {
let currently_supported = match SupportedProtocols::tor_supported() {
Ok(result) => result.0,
2017-09-27 21:48:07 +02:00
Err(_) => return false,
};
2017-09-27 21:48:07 +02:00
let supported_versions = match currently_supported.get(&proto) {
Some(n) => n,
None => return false,
};
supported_versions.0.contains(&vers)
2017-09-27 21:48:07 +02:00
}
/// Older versions of Tor cannot infer their own subprotocols
/// Used to determine which subprotocols are supported by older Tor versions.
///
/// # Inputs
///
/// * `version`, a string comprised of "[0-9a-z.-]"
2017-09-27 21:48:07 +02:00
///
/// # Returns
///
/// A `&'static [u8]` encoding a list of protocol names and supported
/// versions. The string takes the following format:
2017-09-27 21:48:07 +02:00
///
/// "HSDir=1-1 LinkAuth=1"
///
/// This function returns the protocols that are supported by the version input,
/// only for tor versions older than FIRST_TOR_VERSION_TO_ADVERTISE_PROTOCOLS.
///
/// C_RUST_COUPLED: src/rust/protover.c `compute_for_old_tor`
pub fn compute_for_old_tor(version: &str) -> &'static [u8] {
if c_tor_version_as_new_as(version, FIRST_TOR_VERSION_TO_ADVERTISE_PROTOCOLS) {
return NUL_BYTE;
2017-09-27 21:48:07 +02:00
}
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";
2017-09-27 21:48:07 +02:00
}
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";
2017-09-27 21:48:07 +02:00
}
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";
2017-09-27 21:48:07 +02:00
}
NUL_BYTE
2017-09-27 21:48:07 +02:00
}
#[cfg(test)]
mod test {
use std::str::FromStr;
use std::string::ToString;
use super::*;
2017-09-27 21:48:07 +02:00
#[test]
fn test_versions_from_version_string() {
2017-09-27 21:48:07 +02:00
use std::collections::HashSet;
use super::Versions;
2017-09-27 21:48:07 +02:00
assert_eq!(Err("invalid protocol entry"), Versions::from_version_string("a,b"));
assert_eq!(Err("invalid protocol entry"), Versions::from_version_string("1,!"));
2017-09-27 21:48:07 +02:00
{
let mut versions: HashSet<Version> = HashSet::new();
2017-09-27 21:48:07 +02:00
versions.insert(1);
assert_eq!(versions, Versions::from_version_string("1").unwrap().0);
2017-09-27 21:48:07 +02:00
}
{
let mut versions: HashSet<Version> = HashSet::new();
2017-09-27 21:48:07 +02:00
versions.insert(1);
versions.insert(2);
assert_eq!(versions, Versions::from_version_string("1,2").unwrap().0);
2017-09-27 21:48:07 +02:00
}
{
let mut versions: HashSet<Version> = HashSet::new();
2017-09-27 21:48:07 +02:00
versions.insert(1);
versions.insert(2);
versions.insert(3);
assert_eq!(versions, Versions::from_version_string("1-3").unwrap().0);
2017-09-27 21:48:07 +02:00
}
{
let mut versions: HashSet<Version> = HashSet::new();
2017-09-27 21:48:07 +02:00
versions.insert(1);
versions.insert(2);
versions.insert(5);
assert_eq!(versions, Versions::from_version_string("1-2,5").unwrap().0);
2017-09-27 21:48:07 +02:00
}
{
let mut versions: HashSet<Version> = HashSet::new();
2017-09-27 21:48:07 +02:00
versions.insert(1);
versions.insert(3);
versions.insert(4);
versions.insert(5);
assert_eq!(versions, Versions::from_version_string("1,3-5").unwrap().0);
2017-09-27 21:48:07 +02:00
}
}
#[test]
fn test_contains_only_supported_protocols() {
use super::contains_only_supported_protocols;
assert_eq!(false, contains_only_supported_protocols(""));
assert_eq!(true, contains_only_supported_protocols("Cons="));
2017-09-27 21:48:07 +02:00
assert_eq!(true, contains_only_supported_protocols("Cons=1"));
assert_eq!(false, contains_only_supported_protocols("Cons=0"));
assert_eq!(false, contains_only_supported_protocols("Cons=0-1"));
assert_eq!(false, contains_only_supported_protocols("Cons=5"));
assert_eq!(false, contains_only_supported_protocols("Cons=1-5"));
assert_eq!(false, contains_only_supported_protocols("Cons=1,5"));
assert_eq!(false, contains_only_supported_protocols("Cons=5,6"));
assert_eq!(false, contains_only_supported_protocols("Cons=1,5,6"));
assert_eq!(true, contains_only_supported_protocols("Cons=1,2"));
assert_eq!(true, contains_only_supported_protocols("Cons=1-2"));
}
#[test]
fn test_find_range() {
use super::find_range;
assert_eq!((false, 0), find_range(&vec![]));
assert_eq!((false, 1), find_range(&vec![1]));
assert_eq!((true, 2), find_range(&vec![1, 2]));
assert_eq!((true, 3), find_range(&vec![1, 2, 3]));
assert_eq!((true, 3), find_range(&vec![1, 2, 3, 5]));
}
#[test]
fn test_expand_version_range() {
use super::expand_version_range;
assert_eq!(Err("version string empty"), expand_version_range(""));
assert_eq!(Ok(1..3), expand_version_range("1-2"));
assert_eq!(Ok(1..5), expand_version_range("1-4"));
2017-09-27 21:48:07 +02:00
assert_eq!(
Err("cannot parse protocol range lower bound"),
expand_version_range("a")
);
assert_eq!(
Err("cannot parse protocol range upper bound"),
expand_version_range("1-a")
);
assert_eq!(Ok(1000..66536), expand_version_range("1000-66535"));
assert_eq!(Err("Too many protocols in expanded range"),
expand_version_range("1000-66536"));
2017-09-27 21:48:07 +02:00
}
#[test]
fn test_contract_protocol_list() {
use std::collections::HashSet;
use super::contract_protocol_list;
{
let mut versions = HashSet::<Version>::new();
2017-09-27 21:48:07 +02:00
assert_eq!(String::from(""), contract_protocol_list(&versions));
versions.insert(1);
assert_eq!(String::from("1"), contract_protocol_list(&versions));
versions.insert(2);
assert_eq!(String::from("1-2"), contract_protocol_list(&versions));
}
{
let mut versions = HashSet::<Version>::new();
2017-09-27 21:48:07 +02:00
versions.insert(1);
versions.insert(3);
assert_eq!(String::from("1,3"), contract_protocol_list(&versions));
}
{
let mut versions = HashSet::<Version>::new();
2017-09-27 21:48:07 +02:00
versions.insert(1);
versions.insert(2);
versions.insert(3);
versions.insert(4);
assert_eq!(String::from("1-4"), contract_protocol_list(&versions));
}
{
let mut versions = HashSet::<Version>::new();
2017-09-27 21:48:07 +02:00
versions.insert(1);
versions.insert(3);
versions.insert(5);
versions.insert(6);
versions.insert(7);
assert_eq!(
String::from("1,3,5-7"),
contract_protocol_list(&versions)
);
}
{
let mut versions = HashSet::<Version>::new();
2017-09-27 21:48:07 +02:00
versions.insert(1);
versions.insert(2);
versions.insert(3);
versions.insert(500);
assert_eq!(
String::from("1-3,500"),
contract_protocol_list(&versions)
);
}
}
}