tor/src/rust/protover/protover.rs
Isis Lovecruft 15e59a1fed
rust: Refactor protover tests with new methods; note altered behaviours.
Previously, the rust implementation of protover considered an empty string to be
a valid ProtoEntry, while the C version did not (it must have a "=" character).
Other differences include that unknown protocols must now be parsed as
`protover::UnknownProtocol`s, and hence their entries as
`protover::UnvalidatedProtoEntry`s, whereas before (nearly) all protoentries
could be parsed regardless of how erroneous they might be considered by the C
version.

My apologies for this somewhat messy and difficult to read commit, if any part
is frustrating to the reviewer, please feel free to ask me to split this into
smaller changes (possibly hard to do, since so much changed), or ask me to
comment on a specific line/change and clarify how/when the behaviours differ.

The tests here should more closely match the behaviours exhibited by the C
implementation, but I do not yet personally guarantee they match precisely.

 * REFACTOR unittests in protover::protover.
 * ADD new integration tests for previously untested behaviour.
 * FIXES part of #24031: https://bugs.torproject.org/24031.
2018-04-02 19:20:31 +00:00

768 lines
26 KiB
Rust

// Copyright (c) 2016-2017, The Tor Project, Inc. */
// See LICENSE for licensing information */
use std::collections::HashMap;
use std::collections::hash_map;
use std::ffi::CStr;
use std::fmt;
use std::str;
use std::str::FromStr;
use std::string::String;
use external::c_tor_version_as_new_as;
use errors::ProtoverError;
use protoset::Version;
use protoset::ProtoSet;
/// 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`
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);
/// 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 {
Cons,
Desc,
DirCache,
HSDir,
HSIntro,
HSRend,
Link,
LinkAuth,
Microdesc,
Relay,
}
impl fmt::Display for Protocol {
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;
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),
}
}
}
/// 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())
}
}
/// Get a CStr representation of current supported protocols, for
/// passing to C, or for converting to a `&str` for Rust.
///
/// # Returns
///
/// An `&'static CStr` whose value is the existing protocols supported by tor.
/// Returned data is in the format as follows:
///
/// "HSDir=1-1 LinkAuth=1"
///
/// # 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")
}
/// 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() )
}
}
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()
}
/// 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_cstr: &'static CStr = get_supported_protocols_cstr();
let supported: &str = supported_cstr.to_str().unwrap_or("");
supported.parse()
}
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)
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
}
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)
}
}
/// 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);
/// 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() )
}
}
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()
}
pub fn get(&self, protocol: &UnknownProtocol) -> Option<&ProtoSet> {
self.0.get(protocol)
}
pub fn insert(&mut self, key: UnknownProtocol, value: ProtoSet) {
self.0.insert(key, value);
}
pub fn remove(&mut self, key: &UnknownProtocol) -> Option<ProtoSet> {
self.0.remove(key)
}
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn len(&self) -> usize {
let mut total: usize = 0;
for (_, versions) in self.iter() {
total += versions.len();
}
total
}
/// 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,
};
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)
}
/// 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) {
Some(n) => n,
None => return false,
};
supported_versions.contains(&vers)
}
/// 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) {
Some(n) => n,
None => return false,
};
supported_versions.iter().any(|v| v.1 >= *vers)
}
}
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, '=');
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
}
}
/// A mapping of protocols to a count of how many times each of their `Version`s
/// were voted for or supported.
///
/// # Warning
///
/// 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() )
}
}
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()
}
}
impl ProtoverVote {
pub fn entry(&mut self, key: UnknownProtocol)
-> hash_map::Entry<UnknownProtocol, HashMap<Version, usize>>
{
self.0.entry(key)
}
/// 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;
}
// 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;
}
}
}
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);
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
}
}
/// Returns a boolean indicating whether the given protocol and version is
/// supported in any of the existing Tor protocols
///
/// # Examples
/// ```
/// use protover::is_supported_here;
/// use protover::Protocol;
///
/// let is_supported = is_supported_here(&Protocol::Link, &10);
/// assert_eq!(false, is_supported);
///
/// let is_supported = is_supported_here(&Protocol::Link, &1);
/// assert_eq!(true, is_supported);
/// ```
pub fn is_supported_here(proto: &Protocol, vers: &Version) -> bool {
let currently_supported: ProtoEntry = match ProtoEntry::supported() {
Ok(result) => result,
Err(_) => return false,
};
let supported_versions = match currently_supported.get(proto) {
Some(n) => n,
None => return false,
};
supported_versions.contains(vers)
}
/// 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.-]"
///
/// # Returns
///
/// A `&'static CStr` encoding a list of protocol names and supported
/// versions. The string takes the following format:
///
/// "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 CStr {
let empty: &'static CStr = cstr!("");
if c_tor_version_as_new_as(version, FIRST_TOR_VERSION_TO_ADVERTISE_PROTOCOLS) {
return empty;
}
if c_tor_version_as_new_as(version, "0.2.9.1-alpha") {
return cstr!("Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1-2 \
Link=1-4 LinkAuth=1 Microdesc=1-2 Relay=1-2");
}
if c_tor_version_as_new_as(version, "0.2.7.5") {
return cstr!("Cons=1-2 Desc=1-2 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 \
Link=1-4 LinkAuth=1 Microdesc=1-2 Relay=1-2");
}
if c_tor_version_as_new_as(version, "0.2.4.19") {
return cstr!("Cons=1 Desc=1 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 \
Link=1-4 LinkAuth=1 Microdesc=1 Relay=1-2");
}
empty
}
#[cfg(test)]
mod test {
use std::str::FromStr;
use std::string::ToString;
use super::*;
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());
)
}
#[test]
fn test_protoentry_from_str_multiple_protocols_multiple_versions() {
assert_protoentry_is_parseable!("Cons=3-4 Link=1,3-5");
}
#[test]
fn test_protoentry_from_str_empty() {
assert_protoentry_is_unparseable!("");
}
#[test]
fn test_protoentry_from_str_single_protocol_single_version() {
assert_protoentry_is_parseable!("HSDir=1");
}
#[test]
fn test_protoentry_from_str_unknown_protocol() {
assert_protoentry_is_unparseable!("Ducks=5-7,8");
}
#[test]
fn test_protoentry_from_str_too_many_versions() {
assert_protoentry_is_unparseable!("Desc=1-65537");
}
#[test]
fn test_protoentry_from_str_() {
assert_protoentry_is_unparseable!("");
}
#[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());
}
#[test]
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());
}
#[test]
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());
}
#[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());
}
#[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());
}
#[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());
}
#[test]
fn test_contract_protocol_list() {
let mut versions = "";
assert_eq!(String::from(versions), ProtoSet::from_str(&versions).unwrap().to_string());
versions = "1";
assert_eq!(String::from(versions), ProtoSet::from_str(&versions).unwrap().to_string());
versions = "1-2";
assert_eq!(String::from(versions), ProtoSet::from_str(&versions).unwrap().to_string());
versions = "1,3";
assert_eq!(String::from(versions), ProtoSet::from_str(&versions).unwrap().to_string());
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());
}
}