[tor-commits] [stem/master] Bridge descriptor support
atagar at torproject.org
atagar at torproject.org
Sat Aug 17 20:44:27 UTC 2019
commit e14d74473ff7319298a920129db6037079ab3652
Author: Damian Johnson <atagar at torproject.org>
Date: Thu Aug 15 13:53:35 2019 -0700
Bridge descriptor support
Adding a 'bridge' argument to descriptor download methods to retrieve bridge
counterparts instead.
---
stem/descriptor/__init__.py | 2 +
stem/descriptor/collector.py | 43 +++++++++--------
stem/descriptor/networkstatus.py | 7 ++-
stem/descriptor/router_status_entry.py | 13 ++++++
test/unit/descriptor/collector.py | 52 ++++++++++++++++++++-
.../bridge-extra-infos-2019-03-cropped.tar | Bin 0 -> 15872 bytes
.../bridge-server-descriptors-2019-02-cropped.tar | Bin 0 -> 9216 bytes
.../collector/bridge-statuses-2019-05-cropped.tar | Bin 0 -> 467456 bytes
8 files changed, 93 insertions(+), 24 deletions(-)
diff --git a/stem/descriptor/__init__.py b/stem/descriptor/__init__.py
index 2044c7bf..c099ca86 100644
--- a/stem/descriptor/__init__.py
+++ b/stem/descriptor/__init__.py
@@ -920,6 +920,8 @@ class Descriptor(object):
:returns: :class:`~stem.descriptor.TypeAnnotation` with our type information
"""
+ # TODO: populate this from the archive instead if available (so we have correct version numbers)
+
if self.TYPE_ANNOTATION_NAME is not None:
return TypeAnnotation(self.TYPE_ANNOTATION_NAME, 1, 0)
else:
diff --git a/stem/descriptor/collector.py b/stem/descriptor/collector.py
index 99d02242..b7982426 100644
--- a/stem/descriptor/collector.py
+++ b/stem/descriptor/collector.py
@@ -146,25 +146,25 @@ def get_instance():
return SINGLETON_COLLECTOR
-def get_server_descriptors(start = None, end = None, cache_to = None, timeout = None, retries = 3):
+def get_server_descriptors(start = None, end = None, cache_to = None, bridge = False, timeout = None, retries = 3):
"""
Shorthand for
:func:`~stem.descriptor.collector.CollecTor.get_server_descriptors`
on our singleton instance.
"""
- for desc in get_instance().get_server_descriptors(start, end, cache_to, timeout, retries):
+ for desc in get_instance().get_server_descriptors(start, end, cache_to, bridge, timeout, retries):
yield desc
-def get_extrainfo_descriptors(start = None, end = None, cache_to = None, timeout = None, retries = 3):
+def get_extrainfo_descriptors(start = None, end = None, cache_to = None, bridge = False, timeout = None, retries = 3):
"""
Shorthand for
:func:`~stem.descriptor.collector.CollecTor.get_extrainfo_descriptors`
on our singleton instance.
"""
- for desc in get_instance().get_extrainfo_descriptors(start, end, cache_to, timeout, retries):
+ for desc in get_instance().get_extrainfo_descriptors(start, end, cache_to, bridge, timeout, retries):
yield desc
@@ -179,14 +179,14 @@ def get_microdescriptors(start = None, end = None, cache_to = None, timeout = No
yield desc
-def get_consensus(start = None, end = None, cache_to = None, document_handler = DocumentHandler.ENTRIES, version = 3, microdescriptor = False, timeout = None, retries = 3):
+def get_consensus(start = None, end = None, cache_to = None, document_handler = DocumentHandler.ENTRIES, version = 3, microdescriptor = False, bridge = False, timeout = None, retries = 3):
"""
Shorthand for
:func:`~stem.descriptor.collector.CollecTor.get_consensus`
on our singleton instance.
"""
- for desc in get_instance().get_consensus(start, end, cache_to, document_handler, version, microdescriptor, timeout, retries):
+ for desc in get_instance().get_consensus(start, end, cache_to, document_handler, version, microdescriptor, bridge, timeout, retries):
yield desc
@@ -413,7 +413,7 @@ class CollecTor(object):
self._cached_files = None
self._cached_index_at = 0
- def get_server_descriptors(self, start = None, end = None, cache_to = None, timeout = None, retries = 3):
+ def get_server_descriptors(self, start = None, end = None, cache_to = None, bridge = False, timeout = None, retries = 3):
"""
Provides server descriptors published during the given time range, sorted
oldest to newest.
@@ -422,6 +422,7 @@ class CollecTor(object):
:param datetime.datetime end: time range to end with
:param str cache_to: directory to cache archives into, if an archive is
available here it is not downloaded
+ :param bool bridge: standard descriptors if **False**, bridge if **True**
:param int timeout: timeout for downloading each individual archive when
the connection becomes idle, no timeout applied if **None**
:param int retires: maximum attempts to impose on a per-archive basis
@@ -433,13 +434,13 @@ class CollecTor(object):
:raises: :class:`~stem.DownloadFailed` if the download fails
"""
- # TODO: support bridge variants ('bridge-server-descriptor' type)
+ desc_type = 'server-descriptor' if not bridge else 'bridge-server-descriptor'
- for f in self.files('server-descriptor', start, end):
- for desc in f.read(cache_to, 'server-descriptor', timeout = timeout, retries = retries):
+ for f in self.files(desc_type, start, end):
+ for desc in f.read(cache_to, desc_type, timeout = timeout, retries = retries):
yield desc
- def get_extrainfo_descriptors(self, start = None, end = None, cache_to = None, timeout = None, retries = 3):
+ def get_extrainfo_descriptors(self, start = None, end = None, cache_to = None, bridge = False, timeout = None, retries = 3):
"""
Provides extrainfo descriptors published during the given time range,
sorted oldest to newest.
@@ -448,6 +449,7 @@ class CollecTor(object):
:param datetime.datetime end: time range to end with
:param str cache_to: directory to cache archives into, if an archive is
available here it is not downloaded
+ :param bool bridge: standard descriptors if **False**, bridge if **True**
:param int timeout: timeout for downloading each individual archive when
the connection becomes idle, no timeout applied if **None**
:param int retires: maximum attempts to impose on a per-archive basis
@@ -459,10 +461,10 @@ class CollecTor(object):
:raises: :class:`~stem.DownloadFailed` if the download fails
"""
- # TODO: support bridge variants ('bridge-extra-info' type)
+ desc_type = 'extra-info' if not bridge else 'bridge-extra-info'
- for f in self.files('extra-info', start, end):
- for desc in f.read(cache_to, 'extra-info', timeout = timeout, retries = retries):
+ for f in self.files(desc_type, start, end):
+ for desc in f.read(cache_to, desc_type, timeout = timeout, retries = retries):
yield desc
def get_microdescriptors(self, start = None, end = None, cache_to = None, timeout = None, retries = 3):
@@ -498,7 +500,7 @@ class CollecTor(object):
for desc in f.read(cache_to, 'microdescriptor', timeout = timeout, retries = retries):
yield desc
- def get_consensus(self, start = None, end = None, cache_to = None, document_handler = DocumentHandler.ENTRIES, version = 3, microdescriptor = False, timeout = None, retries = 3):
+ def get_consensus(self, start = None, end = None, cache_to = None, document_handler = DocumentHandler.ENTRIES, version = 3, microdescriptor = False, bridge = False, timeout = None, retries = 3):
"""
Provides consensus router status entries published during the given time
range, sorted oldest to newest.
@@ -512,6 +514,7 @@ class CollecTor(object):
:param int version: consensus variant to retrieve (versions 2 or 3)
:param bool microdescriptor: provides the microdescriptor consensus if
**True**, standard consensus otherwise
+ :param bool bridge: standard descriptors if **False**, bridge if **True**
:param int timeout: timeout for downloading each individual archive when
the connection becomes idle, no timeout applied if **None**
:param int retires: maximum attempts to impose on a per-archive basis
@@ -523,20 +526,20 @@ class CollecTor(object):
:raises: :class:`~stem.DownloadFailed` if the download fails
"""
- if version == 3 and not microdescriptor:
+ if version == 3 and not microdescriptor and not bridge:
desc_type = 'network-status-consensus-3'
- elif version == 3 and microdescriptor:
+ elif version == 3 and microdescriptor and not bridge:
desc_type = 'network-status-microdesc-consensus-3'
- elif version == 2 and not microdescriptor:
+ elif version == 2 and not microdescriptor and not bridge:
desc_type = 'network-status-2'
+ elif bridge:
+ desc_type = 'bridge-network-status'
else:
if microdescriptor and version != 3:
raise ValueError('Only v3 microdescriptors are available (not version %s)' % version)
else:
raise ValueError('Only v2 and v3 router status entries are available (not version %s)' % version)
- # TODO: support bridge variants ('bridge-network-status' type)
-
for f in self.files(desc_type, start, end):
for desc in f.read(cache_to, desc_type, document_handler, timeout = timeout, retries = retries):
yield desc
diff --git a/stem/descriptor/networkstatus.py b/stem/descriptor/networkstatus.py
index b0589f2a..5f542c2f 100644
--- a/stem/descriptor/networkstatus.py
+++ b/stem/descriptor/networkstatus.py
@@ -93,6 +93,7 @@ from stem.descriptor import (
from stem.descriptor.router_status_entry import (
RouterStatusEntryV2,
+ RouterStatusEntryBridgeV2,
RouterStatusEntryV3,
RouterStatusEntryMicroV3,
)
@@ -322,7 +323,7 @@ def _parse_file(document_file, document_type = None, validate = False, is_microd
elif document_type == NetworkStatusDocumentV3:
router_type = RouterStatusEntryMicroV3 if is_microdescriptor else RouterStatusEntryV3
elif document_type == BridgeNetworkStatusDocument:
- document_type, router_type = BridgeNetworkStatusDocument, RouterStatusEntryV2
+ document_type, router_type = BridgeNetworkStatusDocument, RouterStatusEntryBridgeV2
elif document_type == DetachedSignature:
yield document_type(document_file.read(), validate, **kwargs)
return
@@ -1228,7 +1229,9 @@ class NetworkStatusDocumentV3(NetworkStatusDocument):
self._footer(document_file, validate)
def type_annotation(self):
- if not self.is_microdescriptor:
+ if isinstance(self, BridgeNetworkStatusDocument):
+ return TypeAnnotation('bridge-network-status', 1, 0)
+ elif not self.is_microdescriptor:
return TypeAnnotation('network-status-consensus-3' if not self.is_vote else 'network-status-vote-3', 1, 0)
else:
# Directory authorities do not issue a 'microdescriptor consensus' vote,
diff --git a/stem/descriptor/router_status_entry.py b/stem/descriptor/router_status_entry.py
index 31913417..ce662b40 100644
--- a/stem/descriptor/router_status_entry.py
+++ b/stem/descriptor/router_status_entry.py
@@ -15,6 +15,8 @@ sources...
RouterStatusEntry - Common parent for router status entries
|- RouterStatusEntryV2 - Entry for a network status v2 document
+ | +- RouterStatusEntryBridgeV2 - Entry for a bridge flavored v2 document
+ |
|- RouterStatusEntryV3 - Entry for a network status v3 document
+- RouterStatusEntryMicroV3 - Entry for a microdescriptor flavored v3 document
"""
@@ -540,6 +542,17 @@ class RouterStatusEntryV2(RouterStatusEntry):
return ('r', 's', 'v')
+class RouterStatusEntryBridgeV2(RouterStatusEntryV2):
+ """
+ Information about an individual router stored within a bridge flavored
+ version 2 network status document.
+
+ .. versionadded:: 1.8.0
+ """
+
+ TYPE_ANNOTATION_NAME = 'bridge-network-status'
+
+
class RouterStatusEntryV3(RouterStatusEntry):
"""
Information about an individual router stored within a version 3 network
diff --git a/test/unit/descriptor/collector.py b/test/unit/descriptor/collector.py
index 7d1f0205..3ab06f03 100644
--- a/test/unit/descriptor/collector.py
+++ b/test/unit/descriptor/collector.py
@@ -212,6 +212,21 @@ class TestCollector(unittest.TestCase):
@patch('stem.util.connection.download')
@patch('stem.descriptor.collector.CollecTor.files')
+ def test_reading_bridge_server_descriptors(self, files_mock, download_mock):
+ with open(get_resource('collector/bridge-server-descriptors-2019-02-cropped.tar'), 'rb') as archive:
+ download_mock.return_value = archive.read()
+
+ files_mock.return_value = [stem.descriptor.collector.File('archive/bridge-descriptors/server-descriptors/bridge-server-descriptors-2019-02.tar', 12345, '2016-09-04 09:21')]
+
+ descriptors = list(stem.descriptor.collector.get_server_descriptors(bridge = True))
+ self.assertEqual(4, len(descriptors))
+
+ f = descriptors[0]
+ self.assertEqual('BridgeDescriptor', type(f).__name__)
+ self.assertEqual('E90D1DE12B930DEC3F3E1127AAA25E47430CD3F4', f.fingerprint)
+
+ @patch('stem.util.connection.download')
+ @patch('stem.descriptor.collector.CollecTor.files')
def test_reading_extrainfo_descriptors(self, files_mock, download_mock):
with open(get_resource('collector/extra-infos-2019-04-cropped.tar'), 'rb') as archive:
download_mock.return_value = archive.read()
@@ -227,6 +242,21 @@ class TestCollector(unittest.TestCase):
@patch('stem.util.connection.download')
@patch('stem.descriptor.collector.CollecTor.files')
+ def test_reading_bridge_extrainfo_descriptors(self, files_mock, download_mock):
+ with open(get_resource('collector/bridge-extra-infos-2019-03-cropped.tar'), 'rb') as archive:
+ download_mock.return_value = archive.read()
+
+ files_mock.return_value = [stem.descriptor.collector.File('archive/bridge-descriptors/extra-infos/bridge-extra-infos-2019-03.tar', 12345, '2016-09-04 09:21')]
+
+ descriptors = list(stem.descriptor.collector.get_extrainfo_descriptors(bridge = True))
+ self.assertEqual(6, len(descriptors))
+
+ f = descriptors[0]
+ self.assertEqual('BridgeExtraInfoDescriptor', type(f).__name__)
+ self.assertEqual('A0187027648A392C6AC413B66F7CD25DD001BF76', f.fingerprint)
+
+ @patch('stem.util.connection.download')
+ @patch('stem.descriptor.collector.CollecTor.files')
def test_reading_microdescriptors(self, files_mock, download_mock):
with open(get_resource('collector/microdescs-2019-05-cropped.tar'), 'rb') as archive:
download_mock.return_value = archive.read()
@@ -267,14 +297,32 @@ class TestCollector(unittest.TestCase):
self.assertEqual(0, len(list(stem.descriptor.collector.get_consensus(version = 2))))
self.assertEqual(0, len(list(stem.descriptor.collector.get_consensus(microdescriptor = True))))
- # but the microdescriptor archive *does* have microdescriptor consensuses
-
+ @patch('stem.util.connection.download')
+ @patch('stem.descriptor.collector.CollecTor.files')
+ def test_reading_microdescriptor_consensus(self, files_mock, download_mock):
with open(get_resource('collector/microdescs-2019-05-cropped.tar'), 'rb') as archive:
download_mock.return_value = archive.read()
+ files_mock.return_value = [stem.descriptor.collector.File('archive/relay-descriptors/microdescs/microdescs-2019-05.tar', 12345, '2016-09-04 09:21')]
+
descriptors = list(stem.descriptor.collector.get_consensus(microdescriptor = True))
self.assertEqual(556, len(descriptors))
f = descriptors[0]
self.assertEqual('RouterStatusEntryMicroV3', type(f).__name__)
self.assertEqual('000A10D43011EA4928A35F610405F92B4433B4DC', f.fingerprint)
+
+ @patch('stem.util.connection.download')
+ @patch('stem.descriptor.collector.CollecTor.files')
+ def test_reading_bridge_consensus(self, files_mock, download_mock):
+ with open(get_resource('collector/bridge-statuses-2019-05-cropped.tar'), 'rb') as archive:
+ download_mock.return_value = archive.read()
+
+ files_mock.return_value = [stem.descriptor.collector.File('archive/bridge-descriptors/microdescs/bridge-statuses-2019-05.tar', 12345, '2016-09-04 09:21')]
+
+ descriptors = list(stem.descriptor.collector.get_consensus(bridge = True))
+ self.assertEqual(2593, len(descriptors))
+
+ f = descriptors[0]
+ self.assertEqual('RouterStatusEntryBridgeV2', type(f).__name__)
+ self.assertEqual('0035EA2A61E28D395F080ACA2244539490E70950', f.fingerprint)
diff --git a/test/unit/descriptor/data/collector/bridge-extra-infos-2019-03-cropped.tar b/test/unit/descriptor/data/collector/bridge-extra-infos-2019-03-cropped.tar
new file mode 100644
index 00000000..db9f3f7b
Binary files /dev/null and b/test/unit/descriptor/data/collector/bridge-extra-infos-2019-03-cropped.tar differ
diff --git a/test/unit/descriptor/data/collector/bridge-server-descriptors-2019-02-cropped.tar b/test/unit/descriptor/data/collector/bridge-server-descriptors-2019-02-cropped.tar
new file mode 100644
index 00000000..4d0255f3
Binary files /dev/null and b/test/unit/descriptor/data/collector/bridge-server-descriptors-2019-02-cropped.tar differ
diff --git a/test/unit/descriptor/data/collector/bridge-statuses-2019-05-cropped.tar b/test/unit/descriptor/data/collector/bridge-statuses-2019-05-cropped.tar
new file mode 100644
index 00000000..953d2a86
Binary files /dev/null and b/test/unit/descriptor/data/collector/bridge-statuses-2019-05-cropped.tar differ
More information about the tor-commits
mailing list