[tor-commits] [stem/master] Stub initial BandwidthMetric class

atagar at torproject.org atagar at torproject.org
Mon Jan 21 01:52:11 UTC 2019


commit cb8661ef66a2355ab0ed37e8da5e806362c2ab9b
Author: Damian Johnson <atagar at torproject.org>
Date:   Wed Jan 16 14:26:59 2019 -0800

    Stub initial BandwidthMetric class
    
    Just stubbing out an initial class and test.
---
 stem/descriptor/__init__.py              |  13 +++-
 stem/descriptor/bandwidth_metric.py      | 104 +++++++++++++++++++++++++++++++
 test/settings.cfg                        |   8 ++-
 test/unit/descriptor/__init__.py         |   1 +
 test/unit/descriptor/bandwidth_metric.py |  21 +++++++
 5 files changed, 141 insertions(+), 6 deletions(-)

diff --git a/stem/descriptor/__init__.py b/stem/descriptor/__init__.py
index a47c1508..e587f9d1 100644
--- a/stem/descriptor/__init__.py
+++ b/stem/descriptor/__init__.py
@@ -108,6 +108,7 @@ except ImportError:
   from stem.util.ordereddict import OrderedDict
 
 __all__ = [
+  'bandwidth_metric',
   'export',
   'reader',
   'remote',
@@ -441,6 +442,11 @@ def _parse_metrics_file(descriptor_type, major_version, minor_version, descripto
 
     for desc in stem.descriptor.hidden_service_descriptor._parse_file(descriptor_file, validate = validate, **kwargs):
       yield desc
+  elif descriptor_type == stem.descriptor.bandwidth_metric.BandwidthMetric.TYPE_ANNOTATION_NAME and major_version == 1:
+    document_type = stem.descriptor.bandwidth_metric.BandwidthMetric
+
+    for desc in stem.descriptor.bandwidth_metric._parse_file(descriptor_file, validate = validate, **kwargs):
+      yield desc
   else:
     raise TypeError("Unrecognized metrics descriptor format. type: '%s', version: '%i.%i'" % (descriptor_type, major_version, minor_version))
 
@@ -1412,9 +1418,10 @@ def _descriptor_components(raw_contents, validate, extra_keywords = (), non_asci
 
 # importing at the end to avoid circular dependencies on our Descriptor class
 
-import stem.descriptor.server_descriptor
+import stem.descriptor.bandwidth_metric
 import stem.descriptor.extrainfo_descriptor
-import stem.descriptor.networkstatus
+import stem.descriptor.hidden_service_descriptor
 import stem.descriptor.microdescriptor
+import stem.descriptor.networkstatus
+import stem.descriptor.server_descriptor
 import stem.descriptor.tordnsel
-import stem.descriptor.hidden_service_descriptor
diff --git a/stem/descriptor/bandwidth_metric.py b/stem/descriptor/bandwidth_metric.py
new file mode 100644
index 00000000..bb732f4a
--- /dev/null
+++ b/stem/descriptor/bandwidth_metric.py
@@ -0,0 +1,104 @@
+# Copyright 2019, Damian Johnson and The Tor Project
+# See LICENSE for licensing information
+
+"""
+Parsing for Bandwidth Authority metrics as described in Tor's
+`bandwidth-file-spec <https://gitweb.torproject.org/torspec.git/tree/bandwidth-file-spec.txt>`_.
+
+**Module Overview:**
+
+::
+
+  BandwidthMetric - Tor bandwidth authority measurements.
+
+.. versionadded:: 1.8.0
+"""
+
+import datetime
+
+from stem.descriptor import (
+  Descriptor,
+)
+
+
+def _parse_file(descriptor_file, validate = False, **kwargs):
+  """
+  Iterates over the bandwidth authority metrics in a file.
+
+  :param file descriptor_file: file with descriptor content
+  :param bool validate: checks the validity of the descriptor's content if
+    **True**, skips these checks otherwise
+  :param dict kwargs: additional arguments for the descriptor constructor
+
+  :returns: :class:`stem.descriptor.bandwidth_file.BandwidthMetric` object
+
+  :raises:
+    * **ValueError** if the contents is malformed and validate is **True**
+    * **IOError** if the file can't be read
+  """
+
+  yield BandwidthMetric(descriptor_file.read(), validate, **kwargs)
+
+
+def _parse_header(descriptor, entries):
+  header = {}
+
+  for line in str(descriptor).split('\n'):
+    if line == '=====':
+      break
+    elif line.startswith('node_id='):
+      break  # version 1.0 measurement
+
+    if '=' in line:
+      key, value = line.split('=', 1)
+    elif line.isdigit() and 'timestamp' not in header:
+      key, value = 'timestamp', line
+    else:
+      raise ValueError("Header expected to be key=value pairs, but had '%s'" % line)
+
+    header[key] = value
+
+  descriptor.header = header
+
+
+def _parse_timestamp(descriptor, entries):
+  first_line = str(descriptor).split('\n', 1)[0]
+
+  if first_line.isdigit():
+    descriptor.timestamp = datetime.datetime.utcfromtimestamp(int(first_line))
+  else:
+    raise ValueError("First line should be a unix timestamp, but was '%s'" % first_line)
+
+
+def _header_attr(name):
+  def _parse(descriptor, entries):
+    val = descriptor.header.get(name, None)
+    setattr(descriptor, name, val)
+
+  return _parse
+
+
+class BandwidthMetric(Descriptor):
+  """
+  Tor bandwidth authroity measurements.
+
+  :var datetime timestamp: **\*** time when these metrics were published
+
+  :var dict header: **\*** header metadata attributes
+
+  **\*** attribute is either required when we're parsed with validation or has
+  a default value, others are left as **None** if undefined
+  """
+
+  TYPE_ANNOTATION_NAME = 'badnwidth-file'  # TODO: needs an official @type
+
+  ATTRIBUTES = {
+    'timestamp': (None, _parse_timestamp),
+    'header': ({}, _parse_header),
+  }
+
+  def __init__(self, raw_content, validate = False):
+    super(BandwidthMetric, self).__init__(raw_content, lazy_load = not validate)
+
+    if validate:
+      pass  # TODO: implement eager load
diff --git a/test/settings.cfg b/test/settings.cfg
index 2ecd34ea..a5e60e08 100644
--- a/test/settings.cfg
+++ b/test/settings.cfg
@@ -138,12 +138,13 @@ pycodestyle.ignore E131
 pycodestyle.ignore E722
 
 pycodestyle.ignore stem/__init__.py => E402: import stem.util.connection
-pycodestyle.ignore stem/descriptor/__init__.py => E402: import stem.descriptor.server_descriptor
+pycodestyle.ignore stem/descriptor/__init__.py => E402: import stem.descriptor.bandwidth_metric
 pycodestyle.ignore stem/descriptor/__init__.py => E402: import stem.descriptor.extrainfo_descriptor
-pycodestyle.ignore stem/descriptor/__init__.py => E402: import stem.descriptor.networkstatus
+pycodestyle.ignore stem/descriptor/__init__.py => E402: import stem.descriptor.hidden_service_descriptor
 pycodestyle.ignore stem/descriptor/__init__.py => E402: import stem.descriptor.microdescriptor
+pycodestyle.ignore stem/descriptor/__init__.py => E402: import stem.descriptor.networkstatus
+pycodestyle.ignore stem/descriptor/__init__.py => E402: import stem.descriptor.server_descriptor
 pycodestyle.ignore stem/descriptor/__init__.py => E402: import stem.descriptor.tordnsel
-pycodestyle.ignore stem/descriptor/__init__.py => E402: import stem.descriptor.hidden_service_descriptor
 pycodestyle.ignore test/unit/util/connection.py => W291: _tor     tor        15843   10 pipe 0x0 state:
 pycodestyle.ignore test/unit/util/connection.py => W291: _tor     tor        15843   11 pipe 0x0 state:
 
@@ -224,6 +225,7 @@ test.unit_tests
 |test.unit.descriptor.networkstatus.bridge_document.TestBridgeNetworkStatusDocument
 |test.unit.descriptor.hidden_service_descriptor.TestHiddenServiceDescriptor
 |test.unit.descriptor.certificate.TestEd25519Certificate
+|test.unit.descriptor.bandwidth_metric.TestBandwidthMetric
 |test.unit.exit_policy.rule.TestExitPolicyRule
 |test.unit.exit_policy.policy.TestExitPolicy
 |test.unit.endpoint.TestEndpoint
diff --git a/test/unit/descriptor/__init__.py b/test/unit/descriptor/__init__.py
index bce8d3d0..0fe107dd 100644
--- a/test/unit/descriptor/__init__.py
+++ b/test/unit/descriptor/__init__.py
@@ -5,6 +5,7 @@ Unit tests for stem.descriptor.
 import os
 
 __all__ = [
+  'bandwidth_metric',
   'export',
   'extrainfo_descriptor',
   'microdescriptor',
diff --git a/test/unit/descriptor/bandwidth_metric.py b/test/unit/descriptor/bandwidth_metric.py
new file mode 100644
index 00000000..489f4d41
--- /dev/null
+++ b/test/unit/descriptor/bandwidth_metric.py
@@ -0,0 +1,21 @@
+"""
+Unit tests for stem.descriptor.bandwidth_metric.
+"""
+
+import datetime
+import unittest
+
+import stem.descriptor
+import stem.descriptor.bandwidth_metric
+
+import test.unit.descriptor
+
+
+class TestBandwidthMetric(unittest.TestCase):
+  def test_format_v1_0(self):
+    """
+    Parse version 1.0 formatted metrics.
+    """
+
+    desc = list(stem.descriptor.parse_file(test.unit.descriptor.get_resource('bwauth_v1.0'), 'badnwidth-file 1.0'))[0]
+    self.assertEqual(datetime.datetime(2019, 1, 14, 17, 41, 29), desc.timestamp)





More information about the tor-commits mailing list