commit dafb4a6a96c0c1eb138a7c7d9499c5577864dc67 Author: Damian Johnson atagar@torproject.org Date: Sun Apr 30 16:37:16 2017 -0700
RelayDescriptor creation
Implementing a function for creating our first descriptor type (relay server descriptors). This replacing our mocking util's get_relay_server_descriptor() with RelayDescriptor.create() and RelayDescriptor.content().
This is a little different from the API I was previously planning on but think I like this better. --- stem/descriptor/__init__.py | 154 ++++++++++++++++++++---------- stem/descriptor/server_descriptor.py | 30 +++++- test/mocking.py | 41 -------- test/unit/descriptor/export.py | 14 +-- test/unit/descriptor/server_descriptor.py | 95 ++++++++---------- test/unit/tutorial.py | 8 +- test/unit/tutorial_examples.py | 10 +- 7 files changed, 188 insertions(+), 164 deletions(-)
diff --git a/stem/descriptor/__init__.py b/stem/descriptor/__init__.py index d50b661..32b7d7c 100644 --- a/stem/descriptor/__init__.py +++ b/stem/descriptor/__init__.py @@ -18,22 +18,6 @@ Package for parsing and processing descriptor data. |- get_unrecognized_lines - unparsed descriptor content +- __str__ - string that the descriptor was made from
-.. data:: DescriptorType (enum) - - Common descriptor types. - - .. versionadded:: 1.6.0 - - =================== =========== - DescriptorType Description - =================== =========== - **SERVER** :class:`~stem.descriptor.server_descriptor.RelayDescriptor` - **EXTRA** :class:`~stem.descriptor.extrainfo_descriptor.RelayExtraInfoDescriptor` - **MICRO** :class:`~stem.descriptor.microdescriptor.Microdescriptor` - **CONSENSUS** :class:`~stem.descriptor.networkstatus.NetworkStatusDocumentV3` - **HIDDEN_SERVICE** :class:`~stem.descriptor.hidden_service_descriptor.HiddenServiceDescriptor` - =================== =========== - .. data:: DocumentHandler (enum)
Ways in which we can parse a @@ -97,20 +81,18 @@ PGP_BLOCK_START = re.compile('^-----BEGIN ([%s%s]+)-----$' % (KEYWORD_CHAR, WHIT PGP_BLOCK_END = '-----END %s-----' EMPTY_COLLECTION = ([], {}, set())
+CRYPTO_BLOB = """ +MIGJAoGBAJv5IIWQ+WDWYUdyA/0L8qbIkEVH/cwryZWoIaPAzINfrw1WfNZGtBmg +skFtXhOHHqTRN4GPPrZsAIUOQGzQtGb66IQgT4tO/pj+P6QmSCCdTfhvGfgTCsC+ +WPi4Fl2qryzTb3QO5r5x7T8OsG2IBUET1bLQzmtbC560SYR49IvVAgMBAAE= +""" + DocumentHandler = stem.util.enum.UppercaseEnum( 'ENTRIES', 'DOCUMENT', 'BARE_DOCUMENT', )
-DescriptorType = stem.util.enum.Enum( - ('SERVER', 'server-descriptor 1.0'), - ('EXTRAINFO', 'extra-info 1.0'), - ('MICRO', 'microdescriptor 1.0'), - ('CONSENSUS', 'network-status-consensus-3 1.0'), - ('HIDDEN_SERVICE', 'hidden-service-descriptor 1.0'), -) -
def parse_file(descriptor_file, descriptor_type = None, validate = False, document_handler = DocumentHandler.ENTRIES, normalize_newlines = None, **kwargs): """ @@ -353,39 +335,64 @@ def _parse_metrics_file(descriptor_type, major_version, minor_version, descripto raise TypeError("Unrecognized metrics descriptor format. type: '%s', version: '%i.%i'" % (descriptor_type, major_version, minor_version))
-def create(desc_type, attr = None, exclude = (), validate = False, sign = False): +def _descriptor_content(attr = None, exclude = (), header_template = (), footer_template = ()): """ - Creates a descriptor with the given attributes. + Constructs a minimal descriptor with the given attributes. The content we + provide back is of the form...
- .. versionadded:: 1.6.0 + * header_template (with matching attr filled in) + * unused attr entries + * footer_template (with matching attr filled in)
- :param DescriptorType desc_type: type of descriptor to be created - :param dict attr: keyword/value mappings to be included in the descriptor - :param list exclude: mandatory keywords to exclude from the descriptor, this - results in an invalid descriptor - :param bool validate: checks the validity of the descriptor's content if - **True**, skips these checks otherwise - :param bool sign_content: includes cryptographic digest if True + So for instance... + + ::
- :returns: :class:`~stem.descriptor.Descriptor` subclass + _descriptor_content( + attr = {'nickname': 'caerSidi', 'contact': 'atagar'}, + header_template = ( + ('nickname', 'foobar'), + ('fingerprint', '12345'), + ), + )
- :raises: - * **ValueError** if the contents is malformed and validate is True - * **ImportError** if cryptography is unavailable and sign is True + ... would result in... + + :: + + nickname caerSidi + fingerprint 12345 + contact atagar + + :param dict attr: keyword/value mappings to be included in the descriptor + :param list exclude: mandatory keywords to exclude from the descriptor + :param tuple header_template: key/value pairs for mandatory fields before unrecognized content + :param tuple footer_template: key/value pairs for mandatory fields after unrecognized content + + :returns: str with the requested descriptor content """
- if desc_type == DescriptorType.SERVER: - pass - elif desc_type == DescriptorType.EXTRAINFO: - pass - elif desc_type == DescriptorType.MICRO: - pass - elif desc_type == DescriptorType.CONSENSUS: - pass - elif desc_type == DescriptorType.HIDDEN_SERVICE: - pass - else: - raise TypeError("'%s' isn't a valid descriptor type we can create" % desc_type) + header_content, footer_content = [], [] + attr = {} if attr is None else dict(attr) # shallow copy since we're destructive + + for content, template in ((header_content, header_template), + (footer_content, footer_template)): + for keyword, value in template: + if keyword in exclude: + continue + + value = attr.pop(keyword, value) + + if not value: + content.append(keyword) + elif value.startswith('\n'): + # some values like crypto follow the line instead + content.append('%s%s' % (keyword, value)) + else: + content.append('%s %s' % (keyword, value)) + + remainder = [('%s %s' % (k, v) if v else k) for k, v in attr.items()] + return stem.util.str_tools._to_bytes('\n'.join(header_content + remainder + footer_content))
def _value(line, entries): @@ -522,6 +529,53 @@ class Descriptor(object): self._entries = {} self._unrecognized_lines = []
+ @classmethod + def content(cls, attr = None, exclude = (), sign = False): + """ + Creates descriptor content with the given attributes. Mandatory fields are + filled with dummy information unless data is supplied. + + .. versionadded:: 1.6.0 + + :param dict attr: keyword/value mappings to be included in the descriptor + :param list exclude: mandatory keywords to exclude from the descriptor, this + results in an invalid descriptor + :param bool sign_content: includes cryptographic digest if True + + :returns: **str** with the content of a descriptor + + :raises: + * **ImportError** if cryptography is unavailable and sign is True + * **NotImplementedError** if not implemented for this descriptor type + """ + + raise NotImplementedError("The create and content methods haven't been implemented for %s" % cls.__name__) + + @classmethod + def create(cls, attr = None, exclude = (), validate = True, sign = False): + """ + Creates a descriptor with the given attributes. Mandatory fields are filled + with dummy information unless data is supplied. + + .. versionadded:: 1.6.0 + + :param dict attr: keyword/value mappings to be included in the descriptor + :param list exclude: mandatory keywords to exclude from the descriptor, this + results in an invalid descriptor + :param bool validate: checks the validity of the descriptor's content if + **True**, skips these checks otherwise + :param bool sign_content: includes cryptographic digest if True + + :returns: :class:`~stem.descriptor.Descriptor` subclass + + :raises: + * **ValueError** if the contents is malformed and validate is True + * **ImportError** if cryptography is unavailable and sign is True + * **NotImplementedError** if not implemented for this descriptor type + """ + + return cls(cls.content(attr, exclude, sign), validate = validate) + def get_path(self): """ Provides the absolute path that we loaded this descriptor from. diff --git a/stem/descriptor/server_descriptor.py b/stem/descriptor/server_descriptor.py index ddc1149..9f7c846 100644 --- a/stem/descriptor/server_descriptor.py +++ b/stem/descriptor/server_descriptor.py @@ -49,8 +49,10 @@ import stem.version from stem.util import str_type
from stem.descriptor import ( + CRYPTO_BLOB, PGP_BLOCK_END, Descriptor, + _descriptor_content, _get_descriptor_components, _read_until_keywords, _bytes_for_block, @@ -110,6 +112,19 @@ SINGLE_FIELDS = ( DEFAULT_IPV6_EXIT_POLICY = stem.exit_policy.MicroExitPolicy('reject 1-65535') REJECT_ALL_POLICY = stem.exit_policy.ExitPolicy('reject *:*')
+RELAY_SERVER_HEADER = ( + ('router', 'caerSidi 71.35.133.197 9001 0 0'), + ('published', '2012-03-01 17:15:27'), + ('bandwidth', '153600 256000 104590'), + ('reject', '*:*'), + ('onion-key', '\n-----BEGIN RSA PUBLIC KEY-----%s-----END RSA PUBLIC KEY-----' % CRYPTO_BLOB), + ('signing-key', '\n-----BEGIN RSA PUBLIC KEY-----%s-----END RSA PUBLIC KEY-----' % CRYPTO_BLOB), +) + +RELAY_SERVER_FOOTER = ( + ('router-signature', '\n-----BEGIN SIGNATURE-----%s-----END SIGNATURE-----' % CRYPTO_BLOB), +) +
def _parse_file(descriptor_file, is_bridge = False, validate = False, **kwargs): """ @@ -738,6 +753,9 @@ class RelayDescriptor(ServerDescriptor): Our **ed25519_certificate** is deprecated in favor of our new **certificate** attribute. The base64 encoded certificate is available via the certificate's **encoded** attribute. + + .. versionchanged:: 1.6.0 + Added the **skip_crypto_validation** constructor argument. """
ATTRIBUTES = dict(ServerDescriptor.ATTRIBUTES, **{ @@ -765,7 +783,7 @@ class RelayDescriptor(ServerDescriptor): 'router-signature': _parse_router_signature_line, })
- def __init__(self, raw_contents, validate = False, annotations = None): + def __init__(self, raw_contents, validate = False, annotations = None, skip_crypto_validation = False): super(RelayDescriptor, self).__init__(raw_contents, validate, annotations)
if validate: @@ -775,7 +793,7 @@ class RelayDescriptor(ServerDescriptor): if key_hash != self.fingerprint.lower(): raise ValueError('Fingerprint does not match the hash of our signing key (fingerprint: %s, signing key hash: %s)' % (self.fingerprint.lower(), key_hash))
- if stem.prereq.is_crypto_available(): + if not skip_crypto_validation and stem.prereq.is_crypto_available(): signed_digest = self._digest_for_signature(self.signing_key, self.signature)
if signed_digest != self.digest(): @@ -790,6 +808,14 @@ class RelayDescriptor(ServerDescriptor): if stem.prereq._is_pynacl_available() and self.certificate: self.certificate.validate(self)
+ @classmethod + def content(cls, attr = None, exclude = (), sign = False): + return _descriptor_content(attr, exclude, RELAY_SERVER_HEADER, RELAY_SERVER_FOOTER) + + @classmethod + def create(cls, attr = None, exclude = (), validate = True, sign = False): + return cls(cls.content(attr, exclude, sign), validate = validate, skip_crypto_validation = not sign) + @lru_cache() def digest(self): """ diff --git a/test/mocking.py b/test/mocking.py index c293cf0..4843a97 100644 --- a/test/mocking.py +++ b/test/mocking.py @@ -14,7 +14,6 @@ Helper functions for creating mock objects. get_protocolinfo_response - stem.response.protocolinfo.ProtocolInfoResponse
stem.descriptor.server_descriptor - get_relay_server_descriptor - RelayDescriptor get_bridge_server_descriptor - BridgeDescriptor
stem.descriptor.microdescriptor @@ -80,19 +79,6 @@ DOC_SIG = stem.descriptor.networkstatus.DocumentSignature( 'BF112F1C6D5543CFD0A32215ACABD4197B5279AD', '-----BEGIN SIGNATURE-----%s-----END SIGNATURE-----' % CRYPTO_BLOB)
-RELAY_SERVER_HEADER = ( - ('router', 'caerSidi 71.35.133.197 9001 0 0'), - ('published', '2012-03-01 17:15:27'), - ('bandwidth', '153600 256000 104590'), - ('reject', '*:*'), - ('onion-key', '\n-----BEGIN RSA PUBLIC KEY-----%s-----END RSA PUBLIC KEY-----' % CRYPTO_BLOB), - ('signing-key', '\n-----BEGIN RSA PUBLIC KEY-----%s-----END RSA PUBLIC KEY-----' % CRYPTO_BLOB), -) - -RELAY_SERVER_FOOTER = ( - ('router-signature', '\n-----BEGIN SIGNATURE-----%s-----END SIGNATURE-----' % CRYPTO_BLOB), -) - BRIDGE_SERVER_HEADER = ( ('router', 'Unnamed 10.45.227.253 9001 0 0'), ('router-digest', '006FD96BA35E7785A6A3B8B75FE2E2435A13BDB4'), @@ -366,33 +352,6 @@ def _get_descriptor_content(attr = None, exclude = (), header_template = (), foo return stem.util.str_tools._to_bytes('\n'.join(header_content + remainder + footer_content))
-def get_relay_server_descriptor(attr = None, exclude = (), content = False, sign_content = False): - """ - Provides the descriptor content for... - stem.descriptor.server_descriptor.RelayDescriptor - - :param dict attr: keyword/value mappings to be included in the descriptor - :param list exclude: mandatory keywords to exclude from the descriptor - :param bool content: provides the str content of the descriptor rather than the class if True - :param bool sign_content: sets a proper digest value if True - - :returns: RelayDescriptor for the requested descriptor content - """ - - desc_content = _get_descriptor_content(attr, exclude, RELAY_SERVER_HEADER, RELAY_SERVER_FOOTER) - - if content: - return desc_content - else: - if sign_content: - desc_content = sign_descriptor_content(desc_content) - - with patch('stem.prereq.is_crypto_available', Mock(return_value = False)): - desc = stem.descriptor.server_descriptor.RelayDescriptor(desc_content, validate = True) - - return desc - - def get_bridge_server_descriptor(attr = None, exclude = (), content = False): """ Provides the descriptor content for... diff --git a/test/unit/descriptor/export.py b/test/unit/descriptor/export.py index a262343..009c43d 100644 --- a/test/unit/descriptor/export.py +++ b/test/unit/descriptor/export.py @@ -11,10 +11,10 @@ except ImportError:
import stem.prereq
+from stem.descriptor.server_descriptor import RelayDescriptor from stem.descriptor.export import export_csv, export_csv_file
from test.mocking import ( - get_relay_server_descriptor, get_bridge_server_descriptor, )
@@ -29,7 +29,7 @@ class TestExport(unittest.TestCase): self.skipTest('(header added in python 2.7)') return
- desc = get_relay_server_descriptor() + desc = RelayDescriptor.create()
desc_csv = export_csv(desc, included_fields = ('nickname', 'address', 'published'), header = False) expected = 'caerSidi,71.35.133.197,2012-03-01 17:15:27\n' @@ -50,7 +50,7 @@ class TestExport(unittest.TestCase):
for nickname in nicknames: router_line = '%s 71.35.133.197 9001 0 0' % nickname - descriptors.append(get_relay_server_descriptor({'router': router_line})) + descriptors.append(RelayDescriptor.create({'router': router_line}))
expected = '\n'.join(nicknames) + '\n' self.assertEqual(expected, export_csv(descriptors, included_fields = ('nickname',), header = False)) @@ -61,7 +61,7 @@ class TestExport(unittest.TestCase): the same output as export_csv(). """
- desc = get_relay_server_descriptor() + desc = RelayDescriptor.create() desc_csv = export_csv(desc)
csv_buffer = StringIO() @@ -78,7 +78,7 @@ class TestExport(unittest.TestCase): self.skipTest('(header added in python 2.7)') return
- desc = get_relay_server_descriptor() + desc = RelayDescriptor.create() desc_csv = export_csv(desc)
self.assertTrue(',signature' in desc_csv) @@ -96,7 +96,7 @@ class TestExport(unittest.TestCase): Attempts to make a csv with attributes that don't exist. """
- desc = get_relay_server_descriptor() + desc = RelayDescriptor.create() self.assertRaises(ValueError, export_csv, desc, ('nickname', 'blarg!'))
def test_multiple_descriptor_types(self): @@ -104,6 +104,6 @@ class TestExport(unittest.TestCase): Attempts to make a csv with multiple descriptor types. """
- server_desc = get_relay_server_descriptor() + server_desc = RelayDescriptor.create() bridge_desc = get_bridge_server_descriptor() self.assertRaises(ValueError, export_csv, (server_desc, bridge_desc)) diff --git a/test/unit/descriptor/server_descriptor.py b/test/unit/descriptor/server_descriptor.py index 747600c..1b754bf 100644 --- a/test/unit/descriptor/server_descriptor.py +++ b/test/unit/descriptor/server_descriptor.py @@ -21,7 +21,6 @@ from stem.descriptor.certificate import CertType, ExtensionType from stem.descriptor.server_descriptor import RelayDescriptor, BridgeDescriptor
from test.mocking import ( - get_relay_server_descriptor, get_bridge_server_descriptor, CRYPTO_BLOB, ) @@ -433,8 +432,7 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4= attributes. """
- desc = get_relay_server_descriptor() - + desc = RelayDescriptor.create() self.assertEqual('caerSidi', desc.nickname) self.assertEqual('71.35.133.197', desc.address) self.assertEqual(None, desc.fingerprint) @@ -445,7 +443,7 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4= Includes an 'opt <keyword> <value>' entry. """
- desc = get_relay_server_descriptor({'opt': 'contact www.atagar.com/contact/'}) + desc = RelayDescriptor.create({'opt': 'contact www.atagar.com/contact/'}) self.assertEqual(b'www.atagar.com/contact/', desc.contact)
def test_unrecognized_line(self): @@ -453,7 +451,7 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4= Includes unrecognized content in the descriptor. """
- desc = get_relay_server_descriptor({'pepperjack': 'is oh so tasty!'}) + desc = RelayDescriptor.create({'pepperjack': 'is oh so tasty!'}) self.assertEqual(['pepperjack is oh so tasty!'], desc.get_unrecognized_lines())
def test_proceeding_line(self): @@ -461,79 +459,72 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4= Includes a line prior to the 'router' entry. """
- desc_text = b'hibernate 1\n' + get_relay_server_descriptor(content = True) - self._expect_invalid_attr(desc_text) + desc_text = b'hibernate 1\n' + RelayDescriptor.content() + self._expect_invalid_attr_for_text(desc_text)
def test_trailing_line(self): """ Includes a line after the 'router-signature' entry. """
- desc_text = get_relay_server_descriptor(content = True) + b'\nhibernate 1' - self._expect_invalid_attr(desc_text) + desc_text = RelayDescriptor.content() + b'\nhibernate 1' + self._expect_invalid_attr_for_text(desc_text)
def test_nickname_missing(self): """ Constructs with a malformed router entry. """
- desc_text = get_relay_server_descriptor({'router': ' 71.35.133.197 9001 0 0'}, content = True) - self._expect_invalid_attr(desc_text, 'nickname') + self._expect_invalid_attr({'router': ' 71.35.133.197 9001 0 0'}, 'nickname')
def test_nickname_too_long(self): """ Constructs with a nickname that is an invalid length. """
- desc_text = get_relay_server_descriptor({'router': 'saberrider2008ReallyLongNickname 71.35.133.197 9001 0 0'}, content = True) - self._expect_invalid_attr(desc_text, 'nickname') + self._expect_invalid_attr({'router': 'saberrider2008ReallyLongNickname 71.35.133.197 9001 0 0'}, 'nickname')
def test_nickname_invalid_char(self): """ Constructs with an invalid relay nickname. """
- desc_text = get_relay_server_descriptor({'router': '$aberrider2008 71.35.133.197 9001 0 0'}, content = True) - self._expect_invalid_attr(desc_text, 'nickname') + self._expect_invalid_attr({'router': '$aberrider2008 71.35.133.197 9001 0 0'}, 'nickname')
def test_address_malformed(self): """ Constructs with an invalid ip address. """
- desc_text = get_relay_server_descriptor({'router': 'caerSidi 371.35.133.197 9001 0 0'}, content = True) - self._expect_invalid_attr(desc_text, 'address') + self._expect_invalid_attr({'router': 'caerSidi 371.35.133.197 9001 0 0'}, 'address')
def test_port_too_high(self): """ Constructs with an ORPort that is too large. """
- desc_text = get_relay_server_descriptor({'router': 'caerSidi 71.35.133.197 900001 0 0'}, content = True) - self._expect_invalid_attr(desc_text, 'or_port') + self._expect_invalid_attr({'router': 'caerSidi 71.35.133.197 900001 0 0'}, 'or_port')
def test_port_malformed(self): """ Constructs with an ORPort that isn't numeric. """
- desc_text = get_relay_server_descriptor({'router': 'caerSidi 71.35.133.197 900a1 0 0'}, content = True) - self._expect_invalid_attr(desc_text, 'or_port') + self._expect_invalid_attr({'router': 'caerSidi 71.35.133.197 900a1 0 0'}, 'or_port')
def test_port_newline(self): """ Constructs with a newline replacing the ORPort. """
- desc_text = get_relay_server_descriptor({'router': 'caerSidi 71.35.133.197 \n 0 0'}, content = True) - self._expect_invalid_attr(desc_text, 'or_port') + self._expect_invalid_attr({'router': 'caerSidi 71.35.133.197 \n 0 0'}, 'or_port')
def test_platform_empty(self): """ Constructs with an empty platform entry. """
- desc_text = get_relay_server_descriptor({'platform': ''}, content = True) + desc_text = RelayDescriptor.content({'platform': ''}) desc = RelayDescriptor(desc_text, validate = False) self.assertEqual(b'', desc.platform)
@@ -547,7 +538,7 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4= Parse a platform line belonging to a node-Tor relay. """
- desc = get_relay_server_descriptor({'platform': 'node-Tor 0.1.0 on Linux x86_64'}) + desc = RelayDescriptor.create({'platform': 'node-Tor 0.1.0 on Linux x86_64'}) self.assertEqual(b'node-Tor 0.1.0 on Linux x86_64', desc.platform) self.assertEqual(stem.version.Version('0.1.0'), desc.tor_version) self.assertEqual('Linux x86_64', desc.operating_system) @@ -557,8 +548,7 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4= Constructs with a protocols line without circuit versions. """
- desc_text = get_relay_server_descriptor({'opt': 'protocols Link 1 2'}, content = True) - self._expect_invalid_attr(desc_text, 'circuit_protocols') + self._expect_invalid_attr({'opt': 'protocols Link 1 2'}, 'circuit_protocols')
@patch('stem.prereq.is_crypto_available', Mock(return_value = False)) def test_published_leap_year(self): @@ -567,20 +557,17 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4= invalid. """
- desc_text = get_relay_server_descriptor({'published': '2011-02-29 04:03:19'}, content = True) - self._expect_invalid_attr(desc_text, 'published') + self._expect_invalid_attr({'published': '2011-02-29 04:03:19'}, 'published')
- desc_text = get_relay_server_descriptor({'published': '2012-02-29 04:03:19'}, content = True) - expected_published = datetime.datetime(2012, 2, 29, 4, 3, 19) - self.assertEqual(expected_published, RelayDescriptor(desc_text).published) + desc = RelayDescriptor.create({'published': '2012-02-29 04:03:19'}) + self.assertEqual(datetime.datetime(2012, 2, 29, 4, 3, 19), desc.published)
def test_published_no_time(self): """ Constructs with a published entry without a time component. """
- desc_text = get_relay_server_descriptor({'published': '2012-01-01'}, content = True) - self._expect_invalid_attr(desc_text, 'published') + self._expect_invalid_attr({'published': '2012-01-01'}, 'published')
def test_read_and_write_history(self): """ @@ -591,7 +578,7 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4=
for field in ('read-history', 'write-history'): value = '2005-12-16 18:00:48 (900 s) 81,8848,8927,8927,83,8848' - desc = get_relay_server_descriptor({'opt %s' % field: value}) + desc = RelayDescriptor.create({'opt %s' % field: value})
if field == 'read-history': attr = (desc.read_history_end, desc.read_history_interval, desc.read_history_values) @@ -610,8 +597,7 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4= Parses a read-history with an empty value. """
- value = '2005-12-17 01:23:11 (900 s) ' - desc = get_relay_server_descriptor({'opt read-history': value}) + desc = RelayDescriptor.create({'opt read-history': '2005-12-17 01:23:11 (900 s) '}) self.assertEqual(datetime.datetime(2005, 12, 17, 1, 23, 11), desc.read_history_end) self.assertEqual(900, desc.read_history_interval) self.assertEqual([], desc.read_history_values) @@ -623,7 +609,7 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4= """
desc_text = b'@pepperjack very tasty\n@mushrooms not so much\n' - desc_text += get_relay_server_descriptor(content = True) + desc_text += RelayDescriptor.content() desc_text += b'\ntrailing text that should be invalid, ho hum'
# running _parse_file should provide an iterator with a single descriptor @@ -631,7 +617,7 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4= self.assertRaises(ValueError, list, desc_iter)
desc_text = b'@pepperjack very tasty\n@mushrooms not so much\n' - desc_text += get_relay_server_descriptor(content = True) + desc_text += RelayDescriptor.content() desc_iter = stem.descriptor.server_descriptor._parse_file(io.BytesIO(desc_text))
desc_entries = list(desc_iter) @@ -649,9 +635,9 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4= Constructs with a field appearing twice. """
- desc_text = get_relay_server_descriptor({'<replace>': ''}, content = True) + desc_text = RelayDescriptor.content({'<replace>': ''}) desc_text = desc_text.replace(b'<replace>', b'contact foo\ncontact bar') - self._expect_invalid_attr(desc_text, 'contact', b'foo') + self._expect_invalid_attr_for_text(desc_text, 'contact', b'foo')
def test_missing_required_attr(self): """ @@ -659,7 +645,7 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4= """
for attr in stem.descriptor.server_descriptor.REQUIRED_FIELDS: - desc_text = get_relay_server_descriptor(exclude = [attr], content = True) + desc_text = RelayDescriptor.content(exclude = [attr]) self.assertRaises(ValueError, RelayDescriptor, desc_text, True)
# check that we can still construct it without validation @@ -680,24 +666,22 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4= """
fingerprint = '4F0C 867D F0EF 6816 0568 C826 838F 482C EA7C FE45' - desc_text = get_relay_server_descriptor({'opt fingerprint': fingerprint}, content = True) - self._expect_invalid_attr(desc_text, 'fingerprint', fingerprint.replace(' ', '')) + self._expect_invalid_attr({'opt fingerprint': fingerprint}, 'fingerprint', fingerprint.replace(' ', ''))
def test_ipv6_policy(self): """ Checks a 'ipv6-policy' line. """
- expected = stem.exit_policy.MicroExitPolicy('accept 22-23,53,80,110') - desc = get_relay_server_descriptor({'ipv6-policy': 'accept 22-23,53,80,110'}) - self.assertEqual(expected, desc.exit_policy_v6) + desc = RelayDescriptor.create({'ipv6-policy': 'accept 22-23,53,80,110'}) + self.assertEqual(stem.exit_policy.MicroExitPolicy('accept 22-23,53,80,110'), desc.exit_policy_v6)
def test_extrainfo_sha256_digest(self): """ Extrainfo descriptor line with both a hex and base64 encoded sha256 digest. """
- desc = get_relay_server_descriptor({'extra-info-digest': '03272BF7C68484AFBDA508DAE3734D809E4A5BC4 DWMz1AEdqPlcroubwx3lPEoGbT+oX7S2BH653sPIqfI'}) + desc = RelayDescriptor.create({'extra-info-digest': '03272BF7C68484AFBDA508DAE3734D809E4A5BC4 DWMz1AEdqPlcroubwx3lPEoGbT+oX7S2BH653sPIqfI'}) self.assertEqual('03272BF7C68484AFBDA508DAE3734D809E4A5BC4', desc.extra_info_digest) self.assertEqual('DWMz1AEdqPlcroubwx3lPEoGbT+oX7S2BH653sPIqfI', desc.extra_info_sha256_digest)
@@ -706,7 +690,7 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4= Checks a 'proto' line. """
- desc = get_relay_server_descriptor({'proto': 'Cons=1 Desc=1 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 Link=1-4 LinkAuth=1 Microdesc=1 Relay=1-2'}) + desc = RelayDescriptor.create({'proto': 'Cons=1 Desc=1 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 Link=1-4 LinkAuth=1 Microdesc=1 Relay=1-2'}) self.assertEqual({'Cons': [1], 'Desc': [1], 'DirCache': [1], 'HSDir': [1], 'HSIntro': [3], 'HSRend': [1], 'Link': [1, 2, 3, 4], 'LinkAuth': [1], 'Microdesc': [1], 'Relay': [1, 2]}, desc.protocols)
def test_protocols_with_no_mapping(self): @@ -715,7 +699,7 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4= """
exc_msg = "Protocol entires are expected to be a series of 'key=value' pairs but was: proto Desc Link=1-4" - self.assertRaisesRegexp(ValueError, exc_msg, get_relay_server_descriptor, {'proto': 'Desc Link=1-4'}) + self.assertRaisesRegexp(ValueError, exc_msg, RelayDescriptor.create, {'proto': 'Desc Link=1-4'})
def test_parse_with_non_int_version(self): """ @@ -723,14 +707,14 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4= """
exc_msg = 'Protocol values should be a number or number range, but was: proto Desc=hi Link=1-4' - self.assertRaisesRegexp(ValueError, exc_msg, get_relay_server_descriptor, {'proto': 'Desc=hi Link=1-4'}) + self.assertRaisesRegexp(ValueError, exc_msg, RelayDescriptor.create, {'proto': 'Desc=hi Link=1-4'})
def test_ntor_onion_key(self): """ Checks a 'ntor-onion-key' line. """
- desc = get_relay_server_descriptor({'ntor-onion-key': 'Od2Sj3UXFyDjwESLXk6fhatqW9z/oBL/vAKJ+tbDqUU='}) + desc = RelayDescriptor.create({'ntor-onion-key': 'Od2Sj3UXFyDjwESLXk6fhatqW9z/oBL/vAKJ+tbDqUU='}) self.assertEqual('Od2Sj3UXFyDjwESLXk6fhatqW9z/oBL/vAKJ+tbDqUU=', desc.ntor_onion_key)
def test_minimal_bridge_descriptor(self): @@ -775,7 +759,7 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4= its unsanatized content. """
- desc_text = get_relay_server_descriptor({'router-digest': '006FD96BA35E7785A6A3B8B75FE2E2435A13BDB4'}, content = True) + desc_text = RelayDescriptor.content({'router-digest': '006FD96BA35E7785A6A3B8B75FE2E2435A13BDB4'}) desc = BridgeDescriptor(desc_text) self.assertFalse(desc.is_scrubbed())
@@ -848,7 +832,10 @@ Qlx9HNCqCY877ztFRC624ja2ql6A2hBcuoYMbkHjcQ4= desc = BridgeDescriptor(desc_text) self.assertEqual(expected_or_addresses, desc.or_addresses)
- def _expect_invalid_attr(self, desc_text, attr = None, expected_value = None): + def _expect_invalid_attr(self, desc_attrs, attr = None, expected_value = None): + self._expect_invalid_attr_for_text(RelayDescriptor.content(desc_attrs), attr, expected_value) + + def _expect_invalid_attr_for_text(self, desc_text, attr = None, expected_value = None): """ Asserts that construction will fail due to desc_text having a malformed attribute. If an attr is provided then we check that it matches an expected diff --git a/test/unit/tutorial.py b/test/unit/tutorial.py index ac0bdd4..26ea2b1 100644 --- a/test/unit/tutorial.py +++ b/test/unit/tutorial.py @@ -165,7 +165,7 @@ class TestTutorial(unittest.TestCase): @patch('stem.descriptor.reader.DescriptorReader', spec = DescriptorReader) def test_mirror_mirror_on_the_wall_4(self, reader_mock, stdout_mock): reader = reader_mock().__enter__() - reader.__iter__.return_value = iter([mocking.get_relay_server_descriptor()]) + reader.__iter__.return_value = iter([RelayDescriptor.create()])
exec_documentation_example('past_descriptors.py') self.assertEqual('found relay caerSidi (None)\n', stdout_mock.getvalue()) @@ -206,16 +206,14 @@ class TestTutorial(unittest.TestCase): if count > 15: return
- exit_descriptor = mocking.get_relay_server_descriptor({ - 'router': 'speedyexit 149.255.97.109 9001 0 0' - }, content = True).replace(b'reject *:*', b'accept *:*') + exit_descriptor = RelayDescriptor.content({'router': 'speedyexit 149.255.97.109 9001 0 0'}).replace(b'reject *:*', b'accept *:*')
exit_descriptor = mocking.sign_descriptor_content(exit_descriptor) exit_descriptor = RelayDescriptor(exit_descriptor)
downloader_mock().get_server_descriptors().run.return_value = [ exit_descriptor, - mocking.get_relay_server_descriptor(), # non-exit + RelayDescriptor.create(), # non-exit exit_descriptor, exit_descriptor, ] diff --git a/test/unit/tutorial_examples.py b/test/unit/tutorial_examples.py index de5079e..267fd3d 100644 --- a/test/unit/tutorial_examples.py +++ b/test/unit/tutorial_examples.py @@ -18,12 +18,12 @@ import stem.prereq from stem.control import Controller from stem.util import str_type from stem.descriptor.remote import DIRECTORY_AUTHORITIES +from stem.descriptor.server_descriptor import RelayDescriptor
from test import mocking from test.unit import exec_documentation_example
from test.mocking import ( - get_relay_server_descriptor, get_router_status_entry_v3, ROUTER_STATUS_ENTRY_V3_HEADER, get_network_status_document_v3, @@ -228,10 +228,10 @@ class TestTutorialExamples(unittest.TestCase): @patch('stem.descriptor.remote.DescriptorDownloader') def test_outdated_relays(self, downloader_mock, stdout_mock): downloader_mock().get_server_descriptors.return_value = [ - get_relay_server_descriptor({'platform': 'node-Tor 0.2.3.0 on Linux x86_64'}), - get_relay_server_descriptor({'platform': 'node-Tor 0.1.0 on Linux x86_64'}), - get_relay_server_descriptor({'opt': 'contact Random Person admin@gtr-10.de', 'platform': 'node-Tor 0.2.3.0 on Linux x86_64'}), - get_relay_server_descriptor({'opt': 'contact Sambuddha Basu', 'platform': 'node-Tor 0.1.0 on Linux x86_64'}), + RelayDescriptor.create({'platform': 'node-Tor 0.2.3.0 on Linux x86_64'}), + RelayDescriptor.create({'platform': 'node-Tor 0.1.0 on Linux x86_64'}), + RelayDescriptor.create({'opt': 'contact Random Person admin@gtr-10.de', 'platform': 'node-Tor 0.2.3.0 on Linux x86_64'}), + RelayDescriptor.create({'opt': 'contact Sambuddha Basu', 'platform': 'node-Tor 0.1.0 on Linux x86_64'}), ]
exec_documentation_example('outdated_relays.py')