commit 3eb7836cbd96e49704586d90afaef814202d2360
Author: Damian Johnson <atagar(a)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):
"""