[tor-commits] [stem/master] Support vote's new bandwidth-file fields

atagar at torproject.org atagar at torproject.org
Wed Jun 27 23:02:04 UTC 2018


commit 450390e0ce57680d0ae37853b26bec4e90e26d6b
Author: Damian Johnson <atagar at torproject.org>
Date:   Wed Jun 27 15:57:37 2018 -0700

    Support vote's new bandwidth-file fields
    
    Support for a newly added network status vote field...
    
      https://gitweb.torproject.org/torspec.git/commit/?id=84591df
    
    While whipping this up it crossed my mind that the spec is uncommonly loose for
    this one. Some examples of odd valid content are...
    
      * Empty lines: 'bandwidth-file'
      * Multiple equals: 'bandwidth-file timestamp=12=34'
      * Inclusion of these lines in the consensus.
    
    Pointed these out to juga. Also, imho 'headers' should be in the name somewhere
    since that's what these are (not a path or file content itself).
---
 docs/change_log.rst                               |  1 +
 stem/descriptor/networkstatus.py                  | 30 ++++++++++++++++++++++-
 test/unit/descriptor/networkstatus/document_v3.py | 26 ++++++++++++++++++++
 3 files changed, 56 insertions(+), 1 deletion(-)

diff --git a/docs/change_log.rst b/docs/change_log.rst
index e735d6b2..eeaf0362 100644
--- a/docs/change_log.rst
+++ b/docs/change_log.rst
@@ -66,6 +66,7 @@ The following are only available within Stem's `git repository
   * `Fallback directory v2 support <https://lists.torproject.org/pipermail/tor-dev/2017-December/012721.html>`_, which adds *nickname* and *extrainfo*
   * Added the *orport_v6* attribute to the :class:`~stem.directory.Authority` class
   * Added server descriptor's new is_hidden_service_dir attribute
+  * Added the network status vote's new bandwidth_file attribute (:spec:`84591df`)
   * Don't retry downloading descriptors when we've timed out
   * Don't download from tor26 and Bifroest, which are authorities that frequently timeout
   * `stem.descriptor.remote <api/descriptor/remote.html>`_  now consistently defaults **fall_back_to_authority** to false
diff --git a/stem/descriptor/networkstatus.py b/stem/descriptor/networkstatus.py
index 1be802f5..0e1af80b 100644
--- a/stem/descriptor/networkstatus.py
+++ b/stem/descriptor/networkstatus.py
@@ -130,6 +130,7 @@ HEADER_STATUS_DOCUMENT_FIELDS = (
   ('shared-rand-commit', True, False, False),
   ('shared-rand-previous-value', True, True, False),
   ('shared-rand-current-value', True, True, False),
+  ('bandwidth-file', True, False, False),
   ('recommended-client-protocols', True, True, False),
   ('recommended-relay-protocols', True, True, False),
   ('required-client-protocols', True, True, False),
@@ -783,6 +784,27 @@ def _parse_shared_rand_current_value(descriptor, entries):
     raise ValueError("A network status document's 'shared-rand-current-value' line must be a pair of values, the first an integer but was '%s'" % value)
 
 
+def _parse_bandwidth_file(descriptor, entries):
+  # "bandwidth-file" KeyValues
+  # Value ::= any printing ASCII character except NL and SP.
+  # KeyValue ::= Keyword '=' Value
+  # KeyValues ::= KeyValue | KeyValues SP KeyValue
+
+  value = _value('bandwidth-file', entries)
+  results = {}
+
+  for entry in value.split(' '):
+    if not entry:
+      continue
+    elif '=' not in entry:
+      raise ValueError("'bandwidth-file' lines must be a series of 'key=value' pairs: %s" % value)
+
+    k, v = entry.split('=', 1)
+    results[k] = v
+
+  descriptor.bandwidth_file = results
+
+
 _parse_header_valid_after_line = _parse_timestamp_line('valid-after', 'valid_after')
 _parse_header_fresh_until_line = _parse_timestamp_line('fresh-until', 'fresh_until')
 _parse_header_valid_until_line = _parse_timestamp_line('valid-until', 'valid_until')
@@ -852,6 +874,7 @@ class NetworkStatusDocumentV3(NetworkStatusDocument):
   :var dict recommended_relay_protocols: recommended protocols for relays
   :var dict required_client_protocols: required protocols for clients
   :var dict required_relay_protocols: required protocols for relays
+  :var dict bandwidth_file: bandwidth authority headers that generated this vote
 
   **\*** attribute is either required when we're parsed with validation or has
   a default value, others are left as None if undefined
@@ -868,7 +891,7 @@ class NetworkStatusDocumentV3(NetworkStatusDocument):
 
   .. versionchanged:: 1.6.0
      Added the recommended_client_protocols, recommended_relay_protocols,
-     required_client_protocols, and required_relay_protocols.
+     required_client_protocols, and required_relay_protocols attributes.
 
   .. versionchanged:: 1.6.0
      The is_shared_randomness_participate and shared_randomness_commitments
@@ -880,6 +903,9 @@ class NetworkStatusDocumentV3(NetworkStatusDocument):
      shared_randomness_previous_reveal_count attributes were undocumented and
      not provided properly if retrieved before their shred_randomness_*_value
      counterpart.
+
+  .. versionchanged:: 1.7.0
+     Added the bandwidth_file attributbute.
   """
 
   ATTRIBUTES = {
@@ -910,6 +936,7 @@ class NetworkStatusDocumentV3(NetworkStatusDocument):
     'shared_randomness_previous_value': (None, _parse_shared_rand_previous_value),
     'shared_randomness_current_reveal_count': (None, _parse_shared_rand_current_value),
     'shared_randomness_current_value': (None, _parse_shared_rand_current_value),
+    'bandwidth_file': ({}, _parse_bandwidth_file),
 
     'signatures': ([], _parse_footer_directory_signature_line),
     'bandwidth_weights': ({}, _parse_footer_bandwidth_weights_line),
@@ -937,6 +964,7 @@ class NetworkStatusDocumentV3(NetworkStatusDocument):
     'params': _parse_header_parameters_line,
     'shared-rand-previous-value': _parse_shared_rand_previous_value,
     'shared-rand-current-value': _parse_shared_rand_current_value,
+    'bandwidth-file': _parse_bandwidth_file,
   }
 
   FOOTER_PARSER_FOR_LINE = {
diff --git a/test/unit/descriptor/networkstatus/document_v3.py b/test/unit/descriptor/networkstatus/document_v3.py
index 20b38563..6b11fe6c 100644
--- a/test/unit/descriptor/networkstatus/document_v3.py
+++ b/test/unit/descriptor/networkstatus/document_v3.py
@@ -1255,6 +1255,32 @@ DnN5aFtYKiTc19qIC7Nmo+afPdDEf0MlJvEOP5EWl3w=
       self.assertEqual(None, authority.shared_randomness_current_reveal_count)
       self.assertEqual(None, authority.shared_randomness_current_value)
 
+  def test_bandwidth_file(self):
+    """
+    Parses a 'bandwidth-file' line of votes.
+    """
+
+    test_values = {
+      '': {},
+      'timestamp=': {'timestamp': ''},
+      'timestamp=12=34': {'timestamp': '12=34'},
+      'timestamp=123': {'timestamp': '123'},
+      'timestamp=123 version=1.0': {'timestamp': '123', 'version': '1.0'},
+      'timestamp=123 version=1.0 software=neat_thingy': {'timestamp': '123', 'version': '1.0', 'software': 'neat_thingy'},
+    }
+
+    for test_value, expected_value in test_values.items():
+      document = NetworkStatusDocumentV3.create({'vote-status': 'vote', 'bandwidth-file': test_value})
+      self.assertEqual(expected_value, document.bandwidth_file)
+
+    # there really isn't much that *isn't* valid :P
+
+    content = NetworkStatusDocumentV3.content({'vote-status': 'vote', 'bandwidth-file': 'key_without_value'})
+    self.assertRaises(ValueError, NetworkStatusDocumentV3, content, True)
+
+    document = NetworkStatusDocumentV3(content, False)
+    self.assertEqual(DEFAULT_PARAMS, document.params)
+
   def test_with_legacy_directory_authorities(self):
     """
     Includes both normal authorities and those following the '-legacy' format.



More information about the tor-commits mailing list