[tor-commits] [tor/master] rust: Add new ProtoverVote type and refactor functions to methods.

nickm at torproject.org nickm at torproject.org
Tue Apr 3 23:16:05 UTC 2018


commit 9abbd23df7104f1f46a172c2eaa62bec0afc6d9c
Author: Isis Lovecruft <isis at torproject.org>
Date:   Wed Mar 21 01:52:41 2018 +0000

    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.
---
 src/rust/protover/protover.rs | 189 ++++++++++++++++++++----------------------
 1 file changed, 90 insertions(+), 99 deletions(-)

diff --git a/src/rust/protover/protover.rs b/src/rust/protover/protover.rs
index 9ec29a26d..6f234c61a 100644
--- a/src/rust/protover/protover.rs
+++ b/src/rust/protover/protover.rs
@@ -270,6 +270,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.
     ///
@@ -467,120 +476,102 @@ impl From<ProtoEntry> for UnvalidatedProtoEntry {
     }
 }
 
-/// Protocol voting implementation.
-///
-/// 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
+/// A mapping of protocols to a count of how many times each of their `Version`s
+/// were voted for or supported.
 ///
-/// # 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("");
+/// 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>> );
 
-    if list_of_proto_strings.is_empty() {
-        return empty;
+impl Default for ProtoverVote {
+    fn default() -> ProtoverVote {
+        ProtoverVote( HashMap::new() )
     }
+}
 
-    // 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;
-            }
-        }
+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()
     }
+}
 
-    let mut final_output: HashMap<String, String> =
-        HashMap::with_capacity(get_supported_protocols().split(" ").count());
+impl ProtoverVote {
+    pub fn entry(&mut self, key: UnknownProtocol)
+        -> hash_map::Entry<UnknownProtocol, HashMap<Version, usize>>
+    {
+        self.0.entry(key)
+    }
 
-    // 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);
-            }
+    /// 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





More information about the tor-commits mailing list