
commit cb8661ef66a2355ab0ed37e8da5e806362c2ab9b Author: Damian Johnson <atagar@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)
participants (1)
-
atagar@torproject.org