commit 3eb7836cbd96e49704586d90afaef814202d2360 Author: Damian Johnson atagar@torproject.org Date: Sun Jul 2 11:58:12 2017 -0700
Expand and test make_router_status_entry()
To start with make_router_status_entry() just covered what I needed for BridgeDB. Expanding it to cover all router status entry fields that are part of server descriptors, and adding test coverage. --- stem/descriptor/__init__.py | 12 +++++- stem/descriptor/router_status_entry.py | 5 +-- stem/descriptor/server_descriptor.py | 14 +++++- test/unit/descriptor/server_descriptor.py | 71 +++++++++++++++++++++++++++++++ 4 files changed, 97 insertions(+), 5 deletions(-)
diff --git a/stem/descriptor/__init__.py b/stem/descriptor/__init__.py index e883c55..bf039f8 100644 --- a/stem/descriptor/__init__.py +++ b/stem/descriptor/__init__.py @@ -405,6 +405,9 @@ def _descriptor_content(attr = None, exclude = (), header_template = (), footer_
if value is None: continue + elif isinstance(value, (tuple, list)): + for v in value: + content.append('%s %s' % (keyword, v)) elif value == '': content.append(keyword) elif value.startswith('\n'): @@ -413,7 +416,14 @@ def _descriptor_content(attr = None, exclude = (), header_template = (), footer_ else: content.append('%s %s' % (keyword, value))
- remainder = [('%s %s' % (k, v) if v else k) for k, v in attr.items()] + remainder = [] + + for k, v in attr.items(): + if isinstance(v, (tuple, list)): + remainder += ['%s %s' % (k, entry) for entry in v] + else: + remainder.append('%s %s' % (k, v)) + return stem.util.str_tools._to_bytes('\n'.join(header_content + remainder + footer_content))
diff --git a/stem/descriptor/router_status_entry.py b/stem/descriptor/router_status_entry.py index 81bc63d..398448c 100644 --- a/stem/descriptor/router_status_entry.py +++ b/stem/descriptor/router_status_entry.py @@ -292,9 +292,8 @@ def _parse_id_line(descriptor, entries): value = _value('id', entries)
if value: - if not (descriptor.document and descriptor.document.is_vote): - vote_status = 'vote' if descriptor.document else '<undefined document>' - raise ValueError("%s 'id' line should only appear in votes (appeared in a %s): id %s" % (descriptor._name(), vote_status, value)) + if descriptor.document and not descriptor.document.is_vote: + raise ValueError("%s 'id' line should only appear in votes: id %s" % (descriptor._name(), value))
value_comp = value.split()
diff --git a/stem/descriptor/server_descriptor.py b/stem/descriptor/server_descriptor.py index 8c11b6c..180fe53 100644 --- a/stem/descriptor/server_descriptor.py +++ b/stem/descriptor/server_descriptor.py @@ -838,6 +838,7 @@ class RelayDescriptor(ServerDescriptor): return _append_router_signature(content, signing_key.private) else: return _descriptor_content(attr, exclude, base_header, ( + ('router-sig-ed25519', None), ('router-signature', _random_crypto_blob('SIGNATURE')), ))
@@ -867,6 +868,9 @@ class RelayDescriptor(ServerDescriptor): that would be in the consensus """
+ if not self.fingerprint: + raise ValueError('Server descriptor lacks a fingerprint. This is an optional field, but required to make a router status entry.') + attr = { 'r': ' '.join([ self.nickname, @@ -877,10 +881,18 @@ class RelayDescriptor(ServerDescriptor): str(self.or_port), str(self.dir_port) if self.dir_port else '0', ]), + 'w': 'Bandwidth=%i' % self.average_bandwidth, + 'p': self.exit_policy.summary().replace(', ', ','), }
if self.tor_version: - attr['v'] = self.tor_version + attr['v'] = 'Tor %s' % self.tor_version + + if self.or_addresses: + attr['a'] = ['%s:%s' % (addr, port) for addr, port, _ in self.or_addresses] + + if self.certificate: + attr['id'] = 'ed25519 %s' % base64.b64encode(self.certificate.key).rstrip('=')
return RouterStatusEntryV3.create(attr)
diff --git a/test/unit/descriptor/server_descriptor.py b/test/unit/descriptor/server_descriptor.py index 8578726..4d36bd8 100644 --- a/test/unit/descriptor/server_descriptor.py +++ b/test/unit/descriptor/server_descriptor.py @@ -11,6 +11,7 @@ import time import unittest
import stem.descriptor +import stem.descriptor.router_status_entry import stem.descriptor.server_descriptor import stem.exit_policy import stem.prereq @@ -260,6 +261,76 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4= RelayDescriptor.create(sign = True) self.assertRaisesRegexp(NotImplementedError, 'Signing of BridgeDescriptor not implemented', BridgeDescriptor.create, sign = True)
+ def test_router_status_entry(self): + """ + Tests creation of router status entries. + """ + + desc_without_fingerprint = RelayDescriptor.create() + exc_msg = 'Server descriptor lacks a fingerprint. This is an optional field, but required to make a router status entry.' + self.assertRaisesRegexp(ValueError, exc_msg, desc_without_fingerprint.make_router_status_entry) + + desc = RelayDescriptor.create({ + 'router': 'caerSidi 71.35.133.197 9001 0 0', + 'published': '2012-02-29 04:03:19', + 'fingerprint': '4F0C 867D F0EF 6816 0568 C826 838F 482C EA7C FE44', + 'or-address': ['71.35.133.197:9001', '[12ab:2e19:3bcf::02:9970]:9001'], + 'onion-key': '\n-----BEGIN RSA PUBLIC KEY-----%s-----END RSA PUBLIC KEY-----' % stem.descriptor.CRYPTO_BLOB, + 'signing-key': '\n-----BEGIN RSA PUBLIC KEY-----%s-----END RSA PUBLIC KEY-----' % stem.descriptor.CRYPTO_BLOB, + }).make_router_status_entry() + + self.assertEqual(stem.descriptor.router_status_entry.RouterStatusEntryV3, type(desc)) + self.assertEqual('caerSidi', desc.nickname) + self.assertEqual('4F0C867DF0EF68160568C826838F482CEA7CFE44', desc.fingerprint) + self.assertEqual(datetime.datetime(2012, 2, 29, 4, 3, 19), desc.published) + self.assertEqual('71.35.133.197', desc.address) + self.assertEqual(9001, desc.or_port) + self.assertEqual(None, desc.dir_port) + self.assertEqual(['Fast', 'Named', 'Running', 'Stable', 'Valid'], desc.flags) + self.assertEqual(None, desc.version) + self.assertEqual(None, desc.version_line) + + self.assertEqual([(u'71.35.133.197', 9001, False), (u'12ab:2e19:3bcf::02:9970', 9001, True)], desc.or_addresses) + self.assertEqual(None, desc.identifier_type) + self.assertEqual(None, desc.identifier) + self.assertEqual('4F0069BF91C04581B7C3CA9272E2D3228D4EA571', desc.digest) + self.assertEqual(153600, desc.bandwidth) + self.assertEqual(None, desc.measured) + self.assertEqual(False, desc.is_unmeasured) + self.assertEqual([], desc.unrecognized_bandwidth_entries) + self.assertEqual(stem.exit_policy.MicroExitPolicy('reject 1-65535'), desc.exit_policy) + self.assertEqual([], desc.microdescriptor_hashes) + + def test_make_router_status_entry_with_live_descriptor(self): + """ + Tests creation of router status entries with a live server descriptor. + """ + + with open(get_resource('server_descriptor_with_ed25519'), 'rb') as descriptor_file: + desc = next(stem.descriptor.parse_file(descriptor_file, validate = True)).make_router_status_entry() + + self.assertEqual(stem.descriptor.router_status_entry.RouterStatusEntryV3, type(desc)) + self.assertEqual('destiny', desc.nickname) + self.assertEqual('F65E0196C94DFFF48AFBF2F5F9E3E19AAE583FD0', desc.fingerprint) + self.assertEqual(datetime.datetime(2015, 8, 22, 15, 21, 45), desc.published) + self.assertEqual('94.242.246.23', desc.address) + self.assertEqual(9001, desc.or_port) + self.assertEqual(443, desc.dir_port) + self.assertEqual(['Fast', 'Named', 'Running', 'Stable', 'Valid'], desc.flags) + self.assertEqual(stem.version.Version('0.2.7.2-alpha-dev'), desc.version) + self.assertEqual('Tor 0.2.7.2-alpha-dev', desc.version_line) + + self.assertEqual([('2a01:608:ffff:ff07::1:23', 9003, True)], desc.or_addresses) + self.assertEqual('ed25519', desc.identifier_type) + self.assertEqual('pbYagEQPUiNjcDp/oY2oESXkDzd8PZlr26kaR7nUkao', desc.identifier) + self.assertEqual('B5E441051D139CCD84BC765D130B01E44DAC29AD', desc.digest) + self.assertEqual(149715200, desc.bandwidth) + self.assertEqual(None, desc.measured) + self.assertEqual(False, desc.is_unmeasured) + self.assertEqual([], desc.unrecognized_bandwidth_entries) + self.assertEqual(stem.exit_policy.MicroExitPolicy('reject 25,465,587,10000,14464'), desc.exit_policy) + self.assertEqual([], desc.microdescriptor_hashes) + @patch('time.time', Mock(return_value = time.mktime(datetime.date(2010, 1, 1).timetuple()))) def test_with_ed25519(self): """