2017-10-27 15:54:54 +02:00
|
|
|
// Copyright (c) 2016-2017, The Tor Project, Inc. */
|
|
|
|
// See LICENSE for licensing information */
|
|
|
|
|
2018-03-21 02:18:02 +01:00
|
|
|
use std::collections::HashMap;
|
|
|
|
use std::collections::hash_map;
|
2018-02-08 23:08:11 +01:00
|
|
|
use std::ffi::CStr;
|
2017-09-27 21:48:07 +02:00
|
|
|
use std::fmt;
|
2018-03-21 02:18:02 +01:00
|
|
|
use std::str;
|
|
|
|
use std::str::FromStr;
|
2017-09-27 21:48:07 +02:00
|
|
|
use std::string::String;
|
|
|
|
|
2017-11-28 04:59:54 +01:00
|
|
|
use external::c_tor_version_as_new_as;
|
|
|
|
|
2018-03-21 01:24:46 +01:00
|
|
|
use errors::ProtoverError;
|
2018-03-21 01:43:55 +01:00
|
|
|
use protoset::Version;
|
2018-03-21 02:18:02 +01:00
|
|
|
use protoset::ProtoSet;
|
2018-03-21 01:24:46 +01:00
|
|
|
|
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.
|
2017-11-12 04:26:22 +01:00
|
|
|
///
|
|
|
|
/// 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
|
2017-11-12 04:26:22 +01:00
|
|
|
///
|
|
|
|
/// C_RUST_COUPLED: src/or/protover.c `MAX_PROTOCOLS_TO_EXPAND`
|
2018-03-21 01:43:55 +01:00
|
|
|
pub(crate) const MAX_PROTOCOLS_TO_EXPAND: usize = (1<<16);
|
2017-09-27 21:48:07 +02:00
|
|
|
|
|
|
|
/// Known subprotocols in Tor. Indicates which subprotocol a relay supports.
|
2017-11-12 04:26:22 +01:00
|
|
|
///
|
|
|
|
/// C_RUST_COUPLED: src/or/protover.h `protocol_type_t`
|
2018-03-21 02:07:18 +01:00
|
|
|
#[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,
|
|
|
|
}
|
|
|
|
|
2018-03-21 02:07:18 +01:00
|
|
|
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.
|
2017-11-12 04:26:22 +01:00
|
|
|
///
|
|
|
|
/// C_RUST_COUPLED: src/or/protover.c `PROTOCOL_NAMES`
|
2018-03-21 02:07:18 +01:00
|
|
|
impl FromStr for Protocol {
|
2018-03-21 01:24:46 +01:00
|
|
|
type Err = ProtoverError;
|
2017-09-27 21:48:07 +02:00
|
|
|
|
|
|
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
|
|
match s {
|
2018-03-21 02:07:18 +01:00
|
|
|
"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),
|
2018-03-21 01:24:46 +01:00
|
|
|
_ => Err(ProtoverError::UnknownProtocol),
|
2017-09-27 21:48:07 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-03-21 02:07:18 +01: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())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-08 23:08:11 +01:00
|
|
|
/// Get a CStr representation of current supported protocols, for
|
|
|
|
/// passing to C, or for converting to a `&str` for Rust.
|
2017-09-27 21:48:07 +02:00
|
|
|
///
|
|
|
|
/// # Returns
|
|
|
|
///
|
2018-02-08 23:08:11 +01:00
|
|
|
/// An `&'static CStr` whose value is the existing protocols supported by tor.
|
2017-09-27 21:48:07 +02:00
|
|
|
/// Returned data is in the format as follows:
|
|
|
|
///
|
|
|
|
/// "HSDir=1-1 LinkAuth=1"
|
|
|
|
///
|
2018-02-08 23:08:11 +01:00
|
|
|
/// # Note
|
|
|
|
///
|
|
|
|
/// Rust code can use the `&'static CStr` as a normal `&'a str` by
|
|
|
|
/// calling `protover::get_supported_protocols`.
|
|
|
|
///
|
|
|
|
// C_RUST_COUPLED: src/or/protover.c `protover_get_supported_protocols`
|
|
|
|
pub(crate) fn get_supported_protocols_cstr() -> &'static CStr {
|
|
|
|
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")
|
|
|
|
}
|
|
|
|
|
2018-03-21 02:18:02 +01: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>);
|
2017-09-27 21:48:07 +02:00
|
|
|
|
2018-03-21 02:18:02 +01:00
|
|
|
impl Default for ProtoEntry {
|
|
|
|
fn default() -> ProtoEntry {
|
|
|
|
ProtoEntry( HashMap::new() )
|
2017-09-27 21:48:07 +02:00
|
|
|
}
|
2018-03-21 02:18:02 +01:00
|
|
|
}
|
2017-09-27 21:48:07 +02:00
|
|
|
|
2018-03-21 02:18:02 +01: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()
|
2018-02-08 23:45:17 +01:00
|
|
|
}
|
2017-09-27 21:48:07 +02:00
|
|
|
|
2018-02-08 23:45:17 +01:00
|
|
|
/// Translate the supported tor versions from a string into a
|
2018-03-21 02:18:02 +01:00
|
|
|
/// ProtoEntry, which is useful when looking up a specific
|
2018-02-08 23:45:17 +01:00
|
|
|
/// subprotocol.
|
2018-03-21 02:18:02 +01:00
|
|
|
pub fn supported() -> Result<Self, ProtoverError> {
|
|
|
|
let supported_cstr: &'static CStr = get_supported_protocols_cstr();
|
|
|
|
let supported: &str = supported_cstr.to_str().unwrap_or("");
|
|
|
|
|
|
|
|
supported.parse()
|
2017-09-27 21:48:07 +02:00
|
|
|
}
|
|
|
|
|
2018-03-21 02:18:02 +01:00
|
|
|
pub fn get(&self, protocol: &Protocol) -> Option<&ProtoSet> {
|
|
|
|
self.0.get(protocol)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn insert(&mut self, key: Protocol, value: ProtoSet) {
|
|
|
|
self.0.insert(key, value);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn remove(&mut self, key: &Protocol) -> Option<ProtoSet> {
|
|
|
|
self.0.remove(key)
|
|
|
|
}
|
2017-09-27 21:48:07 +02:00
|
|
|
|
2018-03-21 02:18:02 +01:00
|
|
|
pub fn is_empty(&self) -> bool {
|
|
|
|
self.0.is_empty()
|
|
|
|
}
|
|
|
|
}
|
2017-09-27 21:48:07 +02:00
|
|
|
|
2018-03-21 02:18:02 +01:00
|
|
|
impl FromStr for ProtoEntry {
|
|
|
|
type Err = ProtoverError;
|
2017-09-27 21:48:07 +02:00
|
|
|
|
2018-03-21 02:18:02 +01:00
|
|
|
/// 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);
|
|
|
|
}
|
2017-09-27 21:48:07 +02:00
|
|
|
|
2018-03-21 02:18:02 +01:00
|
|
|
Ok(proto_entry)
|
|
|
|
}
|
2017-09-27 21:48:07 +02:00
|
|
|
}
|
|
|
|
|
2018-03-21 02:44:59 +01: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);
|
|
|
|
|
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>);
|
2017-09-27 21:48:07 +02:00
|
|
|
|
2018-03-21 02:29:36 +01:00
|
|
|
impl Default for UnvalidatedProtoEntry {
|
|
|
|
fn default() -> UnvalidatedProtoEntry {
|
|
|
|
UnvalidatedProtoEntry( HashMap::new() )
|
|
|
|
}
|
|
|
|
}
|
2017-09-27 21:48:07 +02:00
|
|
|
|
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
|
|
|
|
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
|
|
|
|
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
|
|
|
|
2018-03-21 02:29:36 +01:00
|
|
|
pub fn remove(&mut self, key: &UnknownProtocol) -> Option<ProtoSet> {
|
|
|
|
self.0.remove(key)
|
|
|
|
}
|
2017-09-27 21:48:07 +02:00
|
|
|
|
2018-03-21 02:29:36 +01:00
|
|
|
pub fn is_empty(&self) -> bool {
|
|
|
|
self.0.is_empty()
|
|
|
|
}
|
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
|
|
|
pub fn len(&self) -> usize {
|
|
|
|
let mut total: usize = 0;
|
|
|
|
|
|
|
|
for (_, versions) in self.iter() {
|
|
|
|
total += versions.len();
|
|
|
|
}
|
|
|
|
total
|
|
|
|
}
|
|
|
|
|
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-11-09 15:42:09 +01:00
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
2017-09-27 21:48:07 +02:00
|
|
|
|
2018-03-21 02:29:36 +01:00
|
|
|
if unsupported.is_empty() {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
Some(unsupported)
|
|
|
|
}
|
2017-09-27 21:48:07 +02:00
|
|
|
|
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,
|
2018-03-21 02:29:36 +01:00
|
|
|
None => return false,
|
2017-09-27 21:48:07 +02:00
|
|
|
};
|
2018-03-21 02:29:36 +01:00
|
|
|
supported_versions.contains(&vers)
|
|
|
|
}
|
2017-09-27 21:48:07 +02:00
|
|
|
|
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,
|
2018-03-21 02:29:36 +01:00
|
|
|
None => return false,
|
2017-09-27 21:48:07 +02:00
|
|
|
};
|
2018-03-21 02:29:36 +01:00
|
|
|
supported_versions.iter().any(|v| v.1 >= *vers)
|
|
|
|
}
|
|
|
|
}
|
2017-09-27 21:48:07 +02:00
|
|
|
|
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
|
|
|
|
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
|
|
|
|
/// ```
|
2018-03-21 03:09:04 +01:00
|
|
|
/// use protover::is_supported_here;
|
|
|
|
/// use protover::Protocol;
|
2017-09-27 21:48:07 +02:00
|
|
|
///
|
2018-03-21 03:09:04 +01:00
|
|
|
/// let is_supported = is_supported_here(&Protocol::Link, &10);
|
2017-09-27 21:48:07 +02:00
|
|
|
/// assert_eq!(false, is_supported);
|
|
|
|
///
|
2018-03-21 03:09:04 +01:00
|
|
|
/// let is_supported = is_supported_here(&Protocol::Link, &1);
|
2017-09-27 21:48:07 +02:00
|
|
|
/// assert_eq!(true, is_supported);
|
|
|
|
/// ```
|
2018-03-21 03:09:04 +01:00
|
|
|
pub fn is_supported_here(proto: &Protocol, vers: &Version) -> bool {
|
|
|
|
let currently_supported: ProtoEntry = match ProtoEntry::supported() {
|
|
|
|
Ok(result) => result,
|
2017-09-27 21:48:07 +02:00
|
|
|
Err(_) => return false,
|
2017-12-21 05:34:05 +01:00
|
|
|
};
|
2018-03-21 03:09:04 +01:00
|
|
|
let supported_versions = match currently_supported.get(proto) {
|
2017-09-27 21:48:07 +02:00
|
|
|
Some(n) => n,
|
|
|
|
None => return false,
|
|
|
|
};
|
2018-03-21 03:09:04 +01:00
|
|
|
supported_versions.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
|
|
|
|
///
|
2018-02-06 15:15:33 +01:00
|
|
|
/// * `version`, a string comprised of "[0-9a-z.-]"
|
2017-09-27 21:48:07 +02:00
|
|
|
///
|
|
|
|
/// # Returns
|
|
|
|
///
|
2018-02-10 02:52:26 +01:00
|
|
|
/// A `&'static CStr` encoding a list of protocol names and supported
|
2018-02-08 23:36:08 +01:00
|
|
|
/// 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.
|
|
|
|
///
|
2017-11-12 04:26:22 +01:00
|
|
|
/// C_RUST_COUPLED: src/rust/protover.c `compute_for_old_tor`
|
2018-02-10 02:52:26 +01:00
|
|
|
pub fn compute_for_old_tor(version: &str) -> &'static CStr {
|
|
|
|
let empty: &'static CStr = cstr!("");
|
|
|
|
|
2018-02-06 15:15:33 +01:00
|
|
|
if c_tor_version_as_new_as(version, FIRST_TOR_VERSION_TO_ADVERTISE_PROTOCOLS) {
|
2018-02-10 02:52:26 +01:00
|
|
|
return empty;
|
2017-09-27 21:48:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if c_tor_version_as_new_as(version, "0.2.9.1-alpha") {
|
2018-02-10 02:52:26 +01:00
|
|
|
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");
|
2017-09-27 21:48:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if c_tor_version_as_new_as(version, "0.2.7.5") {
|
2018-02-10 02:52:26 +01:00
|
|
|
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");
|
2017-09-27 21:48:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
if c_tor_version_as_new_as(version, "0.2.4.19") {
|
2018-02-10 02:52:26 +01:00
|
|
|
return cstr!("Cons=1 Desc=1 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 \
|
|
|
|
Link=1-4 LinkAuth=1 Microdesc=1 Relay=1-2");
|
2017-09-27 21:48:07 +02:00
|
|
|
}
|
2018-02-06 15:15:33 +01:00
|
|
|
|
2018-02-10 02:52:26 +01:00
|
|
|
empty
|
2017-09-27 21:48:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod test {
|
2018-03-21 01:43:55 +01:00
|
|
|
use std::str::FromStr;
|
|
|
|
use std::string::ToString;
|
|
|
|
|
|
|
|
use super::*;
|
2017-12-21 05:34:05 +01:00
|
|
|
|
2018-03-21 03:13:40 +01:00
|
|
|
macro_rules! assert_protoentry_is_parseable {
|
|
|
|
($e:expr) => (
|
|
|
|
let protoentry: Result<ProtoEntry, ProtoverError> = $e.parse();
|
|
|
|
|
|
|
|
assert!(protoentry.is_ok(), format!("{:?}", protoentry.err()));
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
macro_rules! assert_protoentry_is_unparseable {
|
|
|
|
($e:expr) => (
|
|
|
|
let protoentry: Result<ProtoEntry, ProtoverError> = $e.parse();
|
|
|
|
|
|
|
|
assert!(protoentry.is_err());
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
2017-09-27 21:48:07 +02:00
|
|
|
#[test]
|
2018-03-21 03:13:40 +01:00
|
|
|
fn test_protoentry_from_str_multiple_protocols_multiple_versions() {
|
|
|
|
assert_protoentry_is_parseable!("Cons=3-4 Link=1,3-5");
|
|
|
|
}
|
2017-09-27 21:48:07 +02:00
|
|
|
|
2018-03-21 03:13:40 +01:00
|
|
|
#[test]
|
|
|
|
fn test_protoentry_from_str_empty() {
|
|
|
|
assert_protoentry_is_unparseable!("");
|
|
|
|
}
|
2017-09-27 21:48:07 +02:00
|
|
|
|
2018-03-21 03:13:40 +01:00
|
|
|
#[test]
|
|
|
|
fn test_protoentry_from_str_single_protocol_single_version() {
|
|
|
|
assert_protoentry_is_parseable!("HSDir=1");
|
|
|
|
}
|
2017-09-27 21:48:07 +02:00
|
|
|
|
2018-03-21 03:13:40 +01:00
|
|
|
#[test]
|
|
|
|
fn test_protoentry_from_str_unknown_protocol() {
|
|
|
|
assert_protoentry_is_unparseable!("Ducks=5-7,8");
|
2017-09-27 21:48:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2018-03-21 03:13:40 +01:00
|
|
|
fn test_protoentry_from_str_too_many_versions() {
|
|
|
|
assert_protoentry_is_unparseable!("Desc=1-65537");
|
2017-09-27 21:48:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2018-03-21 03:13:40 +01:00
|
|
|
fn test_protoentry_from_str_() {
|
|
|
|
assert_protoentry_is_unparseable!("");
|
|
|
|
}
|
2017-09-27 21:48:07 +02:00
|
|
|
|
2018-03-21 03:13:40 +01:00
|
|
|
#[test]
|
|
|
|
fn test_protoentry_all_supported_single_protocol_single_version() {
|
|
|
|
let protocol: UnvalidatedProtoEntry = "Cons=1".parse().unwrap();
|
|
|
|
let unsupported: Option<UnvalidatedProtoEntry> = protocol.all_supported();
|
|
|
|
assert_eq!(true, unsupported.is_none());
|
2017-09-27 21:48:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2018-03-21 03:13:40 +01:00
|
|
|
fn test_protoentry_all_supported_multiple_protocol_multiple_versions() {
|
|
|
|
let protocols: UnvalidatedProtoEntry = "Link=3-4 Desc=2".parse().unwrap();
|
|
|
|
let unsupported: Option<UnvalidatedProtoEntry> = protocols.all_supported();
|
|
|
|
assert_eq!(true, unsupported.is_none());
|
2017-09-27 21:48:07 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
2018-03-21 03:13:40 +01:00
|
|
|
fn test_protoentry_all_supported_three_values() {
|
|
|
|
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());
|
|
|
|
}
|
2017-09-27 21:48:07 +02:00
|
|
|
|
2018-03-21 03:13:40 +01:00
|
|
|
#[test]
|
|
|
|
fn test_protoentry_all_supported_unknown_protocol() {
|
|
|
|
let protocols: UnvalidatedProtoEntry = "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());
|
|
|
|
}
|
2017-09-27 21:48:07 +02:00
|
|
|
|
2018-03-21 03:13:40 +01:00
|
|
|
#[test]
|
|
|
|
fn test_protoentry_all_supported_unsupported_high_version() {
|
|
|
|
let protocols: UnvalidatedProtoEntry = "HSDir=12-100".parse().unwrap();
|
|
|
|
let unsupported: Option<UnvalidatedProtoEntry> = protocols.all_supported();
|
|
|
|
assert_eq!(true, unsupported.is_some());
|
|
|
|
assert_eq!("HSDir=12-100", &unsupported.unwrap().to_string());
|
|
|
|
}
|
2017-09-27 21:48:07 +02:00
|
|
|
|
2018-03-21 03:13:40 +01:00
|
|
|
#[test]
|
|
|
|
fn test_protoentry_all_supported_unsupported_low_version() {
|
|
|
|
let protocols: UnvalidatedProtoEntry = "Cons=0-1".parse().unwrap();
|
|
|
|
let unsupported: Option<UnvalidatedProtoEntry> = protocols.all_supported();
|
|
|
|
assert_eq!(true, unsupported.is_some());
|
|
|
|
assert_eq!("Cons=0", &unsupported.unwrap().to_string());
|
|
|
|
}
|
2017-09-27 21:48:07 +02:00
|
|
|
|
2018-03-21 03:13:40 +01:00
|
|
|
#[test]
|
|
|
|
fn test_contract_protocol_list() {
|
|
|
|
let mut versions = "";
|
|
|
|
assert_eq!(String::from(versions), ProtoSet::from_str(&versions).unwrap().to_string());
|
2017-09-27 21:48:07 +02:00
|
|
|
|
2018-03-21 03:13:40 +01:00
|
|
|
versions = "1";
|
|
|
|
assert_eq!(String::from(versions), ProtoSet::from_str(&versions).unwrap().to_string());
|
2017-09-27 21:48:07 +02:00
|
|
|
|
2018-03-21 03:13:40 +01:00
|
|
|
versions = "1-2";
|
|
|
|
assert_eq!(String::from(versions), ProtoSet::from_str(&versions).unwrap().to_string());
|
2017-09-27 21:48:07 +02:00
|
|
|
|
2018-03-21 03:13:40 +01:00
|
|
|
versions = "1,3";
|
|
|
|
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());
|
|
|
|
|
|
|
|
versions = "1,3,5-7";
|
|
|
|
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());
|
2017-09-27 21:48:07 +02:00
|
|
|
}
|
|
|
|
}
|