summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/rust/protover/protover.rs195
1 files changed, 93 insertions, 102 deletions
diff --git a/src/rust/protover/protover.rs b/src/rust/protover/protover.rs
index 2a1a5df9e9..6f1ad768e4 100644
--- a/src/rust/protover/protover.rs
+++ b/src/rust/protover/protover.rs
@@ -281,6 +281,15 @@ impl UnvalidatedProtoEntry {
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.
///
@@ -478,120 +487,102 @@ impl From<ProtoEntry> for UnvalidatedProtoEntry {
}
}
-/// Protocol voting implementation.
+/// A mapping of protocols to a count of how many times each of their `Version`s
+/// were voted for or supported.
///
-/// Given a list of strings describing protocol versions, return a new
-/// string encoding all of the protocols that are listed by at
-/// least threshold of the inputs.
-///
-/// The string is sorted according to the following conventions:
-/// - Protocols names are alphabetized
-/// - Protocols are in order low to high
-/// - Individual and ranges are listed together. For example,
-/// "3, 5-10,13"
-/// - All entries are unique
-///
-/// # Examples
-/// ```
-/// use protover::compute_vote;
+/// # Warning
///
-/// let protos = vec![String::from("Link=3-4"), String::from("Link=3")];
-/// let vote = compute_vote(protos, 2);
-/// assert_eq!("Link=3", vote)
-/// ```
-pub fn compute_vote(
- list_of_proto_strings: Vec<String>,
- threshold: i32,
-) -> String {
- let empty = String::from("");
-
- if list_of_proto_strings.is_empty() {
- return empty;
- }
-
- // all_count is a structure to represent the count of the number of
- // supported versions for a specific protocol. For example, in JSON format:
- // {
- // "FirstSupportedProtocol": {
- // "1": "3",
- // "2": "1"
- // }
- // }
- // means that FirstSupportedProtocol has three votes which support version
- // 1, and one vote that supports version 2
- let mut all_count: HashMap<String, HashMap<Version, usize>> =
- HashMap::new();
-
- // parse and collect all of the protos and their versions and collect them
- for vote in list_of_proto_strings {
- let this_vote: HashMap<String, Versions> =
- match parse_protocols_from_string_with_no_validation(&vote) {
- Ok(result) => result,
- Err(_) => continue,
- };
- for (protocol, versions) in this_vote {
- let supported_vers: &mut HashMap<Version, usize> =
- all_count.entry(protocol).or_insert(HashMap::new());
-
- for version in versions.0 {
- let counter: &mut usize =
- supported_vers.entry(version).or_insert(0);
- *counter += 1;
- }
- }
+/// 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() )
}
+}
- let mut final_output: HashMap<String, String> =
- HashMap::with_capacity(get_supported_protocols().split(" ").count());
+impl IntoIterator for ProtoverVote {
+ type Item = (UnknownProtocol, HashMap<Version, usize>);
+ type IntoIter = hash_map::IntoIter<UnknownProtocol, HashMap<Version, usize>>;
- // Go through and remove verstions that are less than the threshold
- for (protocol, versions) in all_count {
- let mut meets_threshold = HashSet::new();
- for (version, count) in versions {
- if count >= threshold as usize {
- meets_threshold.insert(version);
- }
+ 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;
}
- // For each protocol, compress its version list into the expected
- // protocol version string format
- let contracted = contract_protocol_list(&meets_threshold);
- if !contracted.is_empty() {
- final_output.insert(protocol, contracted);
+ // 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;
+ }
+ }
}
- }
- write_vote_to_string(&final_output)
-}
+ 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);
-/// Return a String comprised of protocol entries in alphabetical order
-///
-/// # Inputs
-///
-/// * `vote`, a `HashMap` 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.
-///
-/// # Returns
-///
-/// A `String` whose value is series of pairs, comprising of the protocol name
-/// and versions that it supports. The string takes the following format:
-///
-/// "first_protocol_name=1,2-5, second_protocol_name=4,5"
-///
-/// Sorts the keys in alphabetical order and creates the expected subprotocol
-/// entry format.
-///
-fn write_vote_to_string(vote: &HashMap<String, String>) -> String {
- let mut keys: Vec<&String> = vote.keys().collect();
- keys.sort();
+ if versions.len() > 0 {
+ let voted_versions: Vec<Version> = versions.keys().cloned().collect();
+ let voted_protoset: ProtoSet = ProtoSet::from(voted_versions);
- let mut output = Vec::new();
- for key in keys {
- // TODO error in indexing here?
- output.push(format!("{}={}", key, vote[key]));
+ final_output.insert(protocol, voted_protoset);
+ }
+ }
+ final_output
}
- output.join(" ")
}
/// Returns a boolean indicating whether the given protocol and version is