def test_votes_by_bandwidth_authorities(self, stdout_mock, authorities_mock, query_mock): authorities_mock().values.return_value = [ DIRECTORY_AUTHORITIES['gabelmoo'], DIRECTORY_AUTHORITIES['moria1'], DIRECTORY_AUTHORITIES['maatuska'], ] entry_with_measurement = RouterStatusEntryV3.create( {'w': 'Bandwidth=1 Measured=1'}) entry_without_measurement = RouterStatusEntryV3.create() query1 = Mock() query1.download_url = 'http://131.188.40.189:80/tor/status-vote/current/authority' query1.run.return_value = [entry_with_measurement] * 5935 + [ entry_without_measurement ] * 1332 query2 = Mock() query2.download_url = 'http://128.31.0.39:9131/tor/status-vote/current/authority' query2.run.return_value = [entry_with_measurement ] * 6647 + [entry_without_measurement] * 625 query3 = Mock() query3.download_url = 'http://171.25.193.9:443/tor/status-vote/current/authority' query3.run.return_value = [entry_with_measurement] * 6313 + [ entry_without_measurement ] * 1112 query_mock.side_effect = [query1, query2, query3] import votes_by_bandwidth_authorities self.assertEqual(EXPECTED_VOTES_BY_BANDWIDTH_AUTHORITIES, stdout_mock.getvalue())
def test_votes_by_bandwidth_authorities(self, query_mock, authorities_mock, stdout_mock): directory_values = [ DIRECTORY_AUTHORITIES['gabelmoo'], DIRECTORY_AUTHORITIES['moria1'], DIRECTORY_AUTHORITIES['maatuska'], ] directory_values[0].address = '131.188.40.189' authorities_mock().values.return_value = directory_values entry_with_measurement = RouterStatusEntryV3.create({'w': 'Bandwidth=1 Measured=1'}) entry_without_measurement = RouterStatusEntryV3.create() query1 = Mock() query1.download_url = 'http://131.188.40.189:80/tor/status-vote/current/authority' query1.run.return_value = [entry_with_measurement] * 5935 + [entry_without_measurement] * 1332 query2 = Mock() query2.download_url = 'http://128.31.0.39:9131/tor/status-vote/current/authority' query2.run.return_value = [entry_with_measurement] * 6647 + [entry_without_measurement] * 625 query3 = Mock() query3.download_url = 'http://171.25.193.9:443/tor/status-vote/current/authority' query3.run.return_value = [entry_with_measurement] * 6313 + [entry_without_measurement] * 1112 query_mock.side_effect = [query1, query2, query3] exec_documentation_example('votes_by_bandwidth_authorities.py') self.assertCountEqual(VOTES_BY_BANDWIDTH_AUTHORITIES_OUTPUT.splitlines(), stdout_mock.getvalue().splitlines())
def test_microdescriptor_hashes(self): """ Handles a variety of 'm' lines. """ test_values = { '8,9,10,11,12': [([8, 9, 10, 11, 12], {})], '8,9,10,11,12 sha256=g1vx9si329muxV3tquWIXXySNOIwRGMeAESKs/v4DWs': [([8, 9, 10, 11, 12], { 'sha256': 'g1vx9si329muxV3tquWIXXySNOIwRGMeAESKs/v4DWs' })], '8,9,10,11,12 sha256=g1vx9si329muxV md5=3tquWIXXySNOIwRGMeAESKs/v4DWs': [([8, 9, 10, 11, 12], { 'sha256': 'g1vx9si329muxV', 'md5': '3tquWIXXySNOIwRGMeAESKs/v4DWs' })], } for m_line, expected in test_values.items(): content = get_router_status_entry_v3({'m': m_line}, content=True) entry = RouterStatusEntryV3(content, document=vote_document()) self.assertEqual(expected, entry.microdescriptor_hashes) # try with multiple 'm' lines content = get_router_status_entry_v3(content=True) content += b'\nm 11,12 sha256=g1vx9si329muxV3tquWIXXySNOIwRGMeAESKs/v4DWs' content += b'\nm 31,32 sha512=g1vx9si329muxV3tquWIXXySNOIwRGMeAESKs/v4DWs' expected = [ ([11, 12], { 'sha256': 'g1vx9si329muxV3tquWIXXySNOIwRGMeAESKs/v4DWs' }), ([31, 32], { 'sha512': 'g1vx9si329muxV3tquWIXXySNOIwRGMeAESKs/v4DWs' }), ] entry = RouterStatusEntryV3(content, document=vote_document()) self.assertEqual(expected, entry.microdescriptor_hashes) # try without a document content = get_router_status_entry_v3({'m': '8,9,10,11,12'}, content=True) self._expect_invalid_attr(content, 'microdescriptor_hashes', expected_value=[]) # tries some invalid inputs test_values = ( '', '4,a,2', '1,2,3 stuff', ) for m_line in test_values: content = get_router_status_entry_v3({'m': m_line}, content=True) self.assertRaises(ValueError, RouterStatusEntryV3, content, True, vote_document())
def test_missing_fields(self): """ Parses a router status entry that's missing fields. """ expect_invalid_attr_for_text(self, RouterStatusEntryV3.content(exclude = ('r', 's')), 'address') expect_invalid_attr_for_text(self, RouterStatusEntryV3.content(exclude = ('r',)), 'address') expect_invalid_attr_for_text(self, RouterStatusEntryV3.content(exclude = ('s',)), 'flags')
def test_blank_lines(self): """ Includes blank lines, which should be ignored. """ content = RouterStatusEntryV3.content() + b'\n\nv Tor 0.2.2.35\n\n' entry = RouterStatusEntryV3(content) self.assertEqual('Tor 0.2.2.35', entry.version_line)
def test_proceeding_line(self): """ Includes content prior to the 'r' line. """ content = b'z some stuff\n' + RouterStatusEntryV3.content() self.assertRaises(ValueError, RouterStatusEntryV3, content, True) self.assertEqual(['z some stuff'], RouterStatusEntryV3(content, False).get_unrecognized_lines())
def test_from_str(self): """ Exercise our RouterStatusEntry.from_str(). """ desc = RouterStatusEntryV3.create() content = desc.get_bytes() self.assertEqual(desc, RouterStatusEntryV3.from_str(content)) self.assertRaisesWith(NotImplementedError, 'Please use the from_str() method from RouterStatusEntry subclasses, not RouterStatusEntry itself', RouterStatusEntry.from_str, content) self.assertRaisesWith(ValueError, "Router status entries don't have their own @type annotation. As such providing a 'descriptor_type' argument with RouterStatusEntry.from_str() does not work. Please drop the 'descriptor_type' argument when using this these subclasses' from_str() method.", RouterStatusEntryV3.from_str, content, descriptor_type = 'network-status-consensus-3 1.0')
def test_microdescriptor_hashes(self): """ Handles a variety of 'm' lines. """ test_values = { "8,9,10,11,12": [([8, 9, 10, 11, 12], {})], "8,9,10,11,12 sha256=g1vx9si329muxV3tquWIXXySNOIwRGMeAESKs/v4DWs": [([8, 9, 10, 11, 12], {"sha256": "g1vx9si329muxV3tquWIXXySNOIwRGMeAESKs/v4DWs"})], "8,9,10,11,12 sha256=g1vx9si329muxV md5=3tquWIXXySNOIwRGMeAESKs/v4DWs": [([8, 9, 10, 11, 12], {"sha256": "g1vx9si329muxV", "md5": "3tquWIXXySNOIwRGMeAESKs/v4DWs"})], } # we need a document that's a vote mock_document = lambda x: x # just need anything with a __dict__ setattr(mock_document, "is_vote", True) setattr(mock_document, "is_consensus", False) for m_line, expected in test_values.items(): content = get_router_status_entry_v3({'m': m_line}, content = True) entry = RouterStatusEntryV3(content, document = mock_document) self.assertEquals(expected, entry.microdescriptor_hashes) # try with multiple 'm' lines content = get_router_status_entry_v3(content = True) content += "\nm 11,12 sha256=g1vx9si329muxV3tquWIXXySNOIwRGMeAESKs/v4DWs" content += "\nm 31,32 sha512=g1vx9si329muxV3tquWIXXySNOIwRGMeAESKs/v4DWs" expected = [ ([11, 12], {"sha256": "g1vx9si329muxV3tquWIXXySNOIwRGMeAESKs/v4DWs"}), ([31, 32], {"sha512": "g1vx9si329muxV3tquWIXXySNOIwRGMeAESKs/v4DWs"}), ] entry = RouterStatusEntryV3(content, document = mock_document) self.assertEquals(expected, entry.microdescriptor_hashes) # try without a document content = get_router_status_entry_v3({'m': "8,9,10,11,12"}, content = True) self._expect_invalid_attr(content, "microdescriptor_hashes", expected_value = []) # tries some invalid inputs test_values = ( "", "4,a,2", "1,2,3 stuff", ) for m_line in test_values: content = get_router_status_entry_v3({'m': m_line}, content = True) self.assertRaises(ValueError, RouterStatusEntryV3, content, True, mock_document)
def test_duplicate_lines(self): """ Duplicates linesin the entry. """ lines = RouterStatusEntryV3.content().split(b'\n') for index, duplicate_line in enumerate(lines): content = b'\n'.join(lines[:index] + [duplicate_line] + lines[index:]) self.assertRaises(ValueError, RouterStatusEntryV3, content, True) entry = RouterStatusEntryV3(content, False) self.assertTrue(entry.nickname.startswith('Unnamed'))
def test_compare_flags(self, stdout_mock, query_mock, authorities_mock): authorities_mock().items.return_value = [ ('moria1', DIRECTORY_AUTHORITIES['moria1']), ('maatuska', DIRECTORY_AUTHORITIES['maatuska']), ] r_line = 'caerSidi %s oQZFLYe9e4A7bOkWKR7TaNxb0JE 2012-08-06 11:19:31 71.35.150.29 9001 0' moria1_consensus = NetworkStatusDocumentV3.create(routers=[ RouterStatusEntryV3.create({ 'r': r_line % 'kvy2dIpA5giOIvurlDqy3XQ+qBg=', 's': ' ' }), RouterStatusEntryV3.create({ 'r': r_line % 'aHH2gjULqTGDjA7B5KIwRNrganM=', 's': ' ' }), RouterStatusEntryV3.create({ 'r': r_line % '4rsTqi9pYM2Tq+UleoJWh/OXPGI=', 's': ' ' }), RouterStatusEntryV3.create( {'r': r_line % 'VGxU4qidiOB5TQSuy/Gsisnagd4='}), RouterStatusEntryV3.create( {'r': r_line % '3K7D0GncOarkPRPIrzG1ZF4F7WE='}), ]) maatuska_consensus = NetworkStatusDocumentV3.create(routers=[ RouterStatusEntryV3.create( {'r': r_line % 'kvy2dIpA5giOIvurlDqy3XQ+qBg='}), RouterStatusEntryV3.create( {'r': r_line % 'aHH2gjULqTGDjA7B5KIwRNrganM='}), RouterStatusEntryV3.create( {'r': r_line % '4rsTqi9pYM2Tq+UleoJWh/OXPGI='}), RouterStatusEntryV3.create({ 'r': r_line % 'VGxU4qidiOB5TQSuy/Gsisnagd4=', 's': ' ' }), RouterStatusEntryV3.create({ 'r': r_line % '3K7D0GncOarkPRPIrzG1ZF4F7WE=', 's': ' ' }), ]) query_mock().run.side_effect = [[moria1_consensus], [maatuska_consensus]] import compare_flags self.assertEqual(EXPECTED_COMPARE_FLAGS, stdout_mock.getvalue())
def test_unrecognized_lines(self): """ Parses a router status entry with new keywords. """ entry = RouterStatusEntryV3.create({'z': 'New tor feature: sparkly unicorns!'}) self.assertEqual(['z New tor feature: sparkly unicorns!'], entry.get_unrecognized_lines())
def test_with_microdescriptor_router_status_entries(self): """ Includes microdescriptor flavored router status entries within the document. """ entry1 = get_router_status_entry_micro_v3({'s': "Fast"}) entry2 = get_router_status_entry_micro_v3({ 'r': "tornodeviennasil AcWxDFxrHetHYS5m6/MVt8ZN6AM 2013-03-13 22:09:13 78.142.142.246 443 80", 's': "Valid", }) document = get_network_status_document_v3({"network-status-version": "3 microdesc"}, routers = (entry1, entry2)) self.assertTrue(entry1 in document.routers.values()) self.assertTrue(entry2 in document.routers.values()) # try with an invalid RouterStatusEntry entry3 = RouterStatusEntryMicroV3(get_router_status_entry_micro_v3({'r': "ugabuga"}, content = True), False) content = get_network_status_document_v3({"network-status-version": "3 microdesc"}, routers = (entry3,), content = True) self.assertRaises(ValueError, NetworkStatusDocumentV3, content) document = NetworkStatusDocumentV3(content, False) self.assertEquals([entry3], document.routers.values()) # try including microdescriptor entry in a normal consensus content = get_network_status_document_v3(routers = (entry1,), content = True) self.assertRaises(ValueError, NetworkStatusDocumentV3, content) document = NetworkStatusDocumentV3(content, False) self.assertEqual([RouterStatusEntryV3(str(entry1), False)], document.routers.values())
def test_with_router_status_entries(self): """ Includes router status entries within the document. This isn't to test the RouterStatusEntry parsing but rather the inclusion of it within the document. """ entry1 = get_router_status_entry_v3({'s': "Fast"}) entry2 = get_router_status_entry_v3({ 'r': "Nightfae AWt0XNId/OU2xX5xs5hVtDc5Mes 6873oEfM7fFIbxYtwllw9GPDwkA 2013-02-20 11:12:27 85.177.66.233 9001 9030", 's': "Valid", }) document = get_network_status_document_v3(routers = (entry1, entry2)) self.assertTrue(entry1 in document.routers.values()) self.assertTrue(entry2 in document.routers.values()) # try with an invalid RouterStatusEntry entry3 = RouterStatusEntryV3(get_router_status_entry_v3({'r': "ugabuga"}, content = True), False) content = get_network_status_document_v3(routers = (entry3,), content = True) self.assertRaises(ValueError, NetworkStatusDocumentV3, content) document = NetworkStatusDocumentV3(content, False) self.assertEquals([entry3], document.routers.values()) # try including with a microdescriptor consensus content = get_network_status_document_v3({"network-status-version": "3 microdesc"}, routers = (entry1,), content = True) self.assertRaises(ValueError, NetworkStatusDocumentV3, content) document = NetworkStatusDocumentV3(content, False) self.assertEqual([RouterStatusEntryMicroV3(str(entry1), False)], document.routers.values())
def test_flags(self): """ Handles a variety of flag inputs. """ test_values = { '': [], 'Fast': [Flag.FAST], 'Fast Valid': [Flag.FAST, Flag.VALID], 'Ugabuga': ['Ugabuga'], } for s_line, expected in test_values.items(): entry = RouterStatusEntryV3.create({'s': s_line}) self.assertEqual(expected, entry.flags) # tries some invalid inputs test_values = { 'Fast ': [Flag.FAST, '', '', ''], 'Fast Valid': [Flag.FAST, '', Flag.VALID], 'Fast Fast': [Flag.FAST, Flag.FAST], } for s_line, expected in test_values.items(): expect_invalid_attr(self, {'s': s_line}, 'flags', expected)
def test_minimal_v3(self): """ Parses a minimal v3 router status entry. """ entry = RouterStatusEntryV3.create() expected_flags = set([Flag.FAST, Flag.NAMED, Flag.RUNNING, Flag.STABLE, Flag.VALID]) self.assertEqual(None, entry.document) self.assertTrue(entry.nickname.startswith('Unnamed')) self.assertEqual('A106452D87BD7B803B6CE916291ED368DC5BD091', entry.digest) self.assertEqual(9001, entry.or_port) self.assertEqual(None, entry.dir_port) self.assertEqual(expected_flags, set(entry.flags)) self.assertEqual(None, entry.version_line) self.assertEqual(None, entry.version) self.assertEqual(None, entry.bandwidth) self.assertEqual(None, entry.measured) self.assertEqual(False, entry.is_unmeasured) self.assertEqual([], entry.unrecognized_bandwidth_entries) self.assertEqual(None, entry.exit_policy) self.assertEqual([], entry.microdescriptor_hashes) self.assertEqual(None, entry.identifier_type) self.assertEqual(None, entry.identifier) self.assertEqual([], entry.get_unrecognized_lines())
def test_with_ed25519(self): """ Parses a router status entry with a ed25519 value. """ microdescriptor_hashes = [ ([13], {'sha256': 'PTSHzE7RKnRGZMRmBddSzDiZio254FUhv9+V4F5zq8s'}), ([14, 15], {'sha256': '0wsEwBbxJ8RtPmGYwilHQTVEw2pWzUBEVlSgEO77OyU'}), ([16, 17], {'sha256': 'JK2xhYr/VsCF60px+LsT990BCpfKfQTeMxRbD63o2vE'}), ([18, 19, 20], {'sha256': 'AkZH3gIvz3wunsroqh5izBJizdYuR7kn2oVbsvqgML8'}), ([21], {'sha256': 'AVp41YVxKEJCaoEf0+77Cdvyw5YgpyDXdob0+LSv/pE'}), ] entry = RouterStatusEntryV3(ENTRY_WITH_ED25519, document = vote_document(), validate = True) self.assertEqual('PDrelay1', entry.nickname) self.assertEqual('000149E6EF7102AACA9690D6E8DD2932124B94AB', entry.fingerprint) self.assertEqual(datetime.datetime(2015, 8, 23, 16, 52, 37), entry.published) self.assertEqual('95.215.44.189', entry.address) self.assertEqual(8080, entry.or_port) self.assertEqual(None, entry.dir_port) self.assertEqual(set([Flag.FAST, Flag.RUNNING, Flag.STABLE, Flag.VALID]), set(entry.flags)) self.assertEqual('Tor 0.2.7.2-alpha-dev', entry.version_line) self.assertEqual(Version('0.2.7.2-alpha-dev'), entry.version) self.assertEqual(608, entry.bandwidth) self.assertEqual(472, entry.measured) self.assertEqual(False, entry.is_unmeasured) self.assertEqual([], entry.unrecognized_bandwidth_entries) self.assertEqual(MicroExitPolicy('reject 1-65535'), entry.exit_policy) self.assertEqual(microdescriptor_hashes, entry.microdescriptor_hashes) self.assertEqual('ed25519', entry.identifier_type) self.assertEqual('8RH34kO07Pp+XYwzdoATVyCibIvmbslUjRkAm7J4IA8', entry.identifier) self.assertEqual('CAB27A6FFEF7A661C18B0B11120C3E8A77FC585C', entry.digest) self.assertEqual([], entry.get_unrecognized_lines())
def test_minimal_v3(self): """ Parses a minimal v3 router status entry. """ entry = RouterStatusEntryV3.create() expected_flags = set( [Flag.FAST, Flag.NAMED, Flag.RUNNING, Flag.STABLE, Flag.VALID]) self.assertEqual(None, entry.document) self.assertTrue(entry.nickname.startswith('Unnamed')) self.assertEqual('A106452D87BD7B803B6CE916291ED368DC5BD091', entry.digest) self.assertEqual(9001, entry.or_port) self.assertEqual(None, entry.dir_port) self.assertEqual(expected_flags, set(entry.flags)) self.assertEqual(None, entry.version_line) self.assertEqual(None, entry.version) self.assertEqual(None, entry.bandwidth) self.assertEqual(None, entry.measured) self.assertEqual(False, entry.is_unmeasured) self.assertEqual([], entry.unrecognized_bandwidth_entries) self.assertEqual(None, entry.exit_policy) self.assertEqual([], entry.microdescriptor_hashes) self.assertEqual(None, entry.identifier_type) self.assertEqual(None, entry.identifier) self.assertEqual([], entry.get_unrecognized_lines())
def test_exit_used(self, stdout_mock, from_port_mock): path_1 = ('9EA317EECA56BDF30CAEB208A253FB456EDAB1A0', 'bolobolo1') path_2 = ('00C2C2A16AEDB51D5E5FB7D6168FC66B343D822F', 'ph3x') path_3 = ('A59E1E7C7EAEE083D756EE1FF6EC31CA3D8651D7', 'chaoscomputerclub19') event = ControlMessage.from_str( '650 STREAM 15 SUCCEEDED 3 64.15.112.44:80', 'EVENT', normalize=True) r_line = '%s pZ4efH6u4IPXVu4f9uwxyj2GUdc= oQZFLYe9e4A7bOkWKR7TaNxb0JE 2012-08-06 11:19:31 31.172.30.2 443 0' controller = from_port_mock().__enter__() controller.get_circuit.return_value = _make_circ_event( 1, path_1, path_2, path_3) controller.get_network_status.return_value = RouterStatusEntryV3.create( {'r': r_line % path_3[1]}) controller.get_info.return_value = 'unknown' import exit_used with patch( 'builtins.input', Mock(side_effect=functools.partial(exit_used.stream_event, controller, event))): exit_used.main() self.assertEqual(EXPECTED_EXIT_USED, stdout_mock.getvalue())
def test_persisting_a_consensus(self, query_mock, parse_file_mock, stdout_mock): def tutorial_example_2(): from stem.descriptor import DocumentHandler, parse_file consensus = next( parse_file( '/tmp/descriptor_dump', descriptor_type='network-status-consensus-3 1.0', document_handler=DocumentHandler.DOCUMENT, )) for fingerprint, relay in consensus.routers.items(): print('%s: %s' % (fingerprint, relay.nickname)) network_status = NetworkStatusDocumentV3.create( routers=(RouterStatusEntryV3.create({ 'r': 'caerSidi p1aag7VwarGxqctS7/fS0y5FU+s oQZFLYe9e4A7bOkWKR7TaNxb0JE 2012-08-06 11:19:31 71.35.150.29 9001 0', }), )) query_mock().run.return_value = [network_status] parse_file_mock.return_value = itertools.cycle([network_status]) exec_documentation_example('persisting_a_consensus.py') exec_documentation_example('persisting_a_consensus_with_parse_file.py') self.assertEqual(PERSISTING_A_CONSENSUS_OUTPUT, stdout_mock.getvalue()) if os.path.exists('/tmp/descriptor_dump'): os.remove('/tmp/descriptor_dump')
def test_without_ed25519(self): """ Parses a router status entry without a ed25519 value. """ microdescriptor_hashes = [ ([13, 14, 15], {'sha256': 'uaAYTOVuYRqUwJpNfP2WizjzO0FiNQB4U97xSQu+vMc'}), ([16, 17], {'sha256': 'G6FmPe/ehgfb6tsRzFKDCwvvae+RICeP1MaP0vWDGyI'}), ([18, 19, 20, 21], {'sha256': '/XhIMOnhElo2UiKjL2S10uRka/fhg1CFfNd+9wgUwEE'}), ] entry = RouterStatusEntryV3(ENTRY_WITHOUT_ED25519, document = vote_document(), validate = True) self.assertEqual('seele', entry.nickname) self.assertEqual('000A10D43011EA4928A35F610405F92B4433B4DC', entry.fingerprint) self.assertEqual(datetime.datetime(2015, 8, 23, 0, 26, 35), entry.published) self.assertEqual('73.15.150.172', entry.address) self.assertEqual(9001, entry.or_port) self.assertEqual(None, entry.dir_port) self.assertEqual(set([Flag.RUNNING, Flag.STABLE, Flag.VALID]), set(entry.flags)) self.assertEqual('Tor 0.2.6.10', entry.version_line) self.assertEqual(Version('0.2.6.10'), entry.version) self.assertEqual(102, entry.bandwidth) self.assertEqual(31, entry.measured) self.assertEqual(False, entry.is_unmeasured) self.assertEqual([], entry.unrecognized_bandwidth_entries) self.assertEqual(MicroExitPolicy('reject 1-65535'), entry.exit_policy) self.assertEqual(microdescriptor_hashes, entry.microdescriptor_hashes) self.assertEqual('ed25519', entry.identifier_type) self.assertEqual('none', entry.identifier) self.assertEqual('9B4CA73EEC3349EC6DCEC897609600D0771EF82B', entry.digest) self.assertEqual([], entry.get_unrecognized_lines())
def test_blank_lines(self): """ Includes blank lines, which should be ignored. """ content = get_router_status_entry_v3(content = True) + "\n\nv Tor 0.2.2.35\n\n" entry = RouterStatusEntryV3(content) self.assertEqual("Tor 0.2.2.35", entry.version_line)
def test_microdescriptor_hashes(self): """ Handles a variety of 'm' lines. """ test_values = { '8,9,10,11,12': [([8, 9, 10, 11, 12], {})], '8,9,10,11,12 sha256=g1vx9si329muxV3tquWIXXySNOIwRGMeAESKs/v4DWs': [([8, 9, 10, 11, 12], {'sha256': 'g1vx9si329muxV3tquWIXXySNOIwRGMeAESKs/v4DWs'})], '8,9,10,11,12 sha256=g1vx9si329muxV md5=3tquWIXXySNOIwRGMeAESKs/v4DWs': [([8, 9, 10, 11, 12], {'sha256': 'g1vx9si329muxV', 'md5': '3tquWIXXySNOIwRGMeAESKs/v4DWs'})], } for m_line, expected in test_values.items(): content = RouterStatusEntryV3.content({'m': m_line}) entry = RouterStatusEntryV3(content, document = vote_document()) self.assertEqual(expected, entry.microdescriptor_hashes) # try with multiple 'm' lines content = RouterStatusEntryV3.content() content += b'\nm 11,12 sha256=g1vx9si329muxV3tquWIXXySNOIwRGMeAESKs/v4DWs' content += b'\nm 31,32 sha512=g1vx9si329muxV3tquWIXXySNOIwRGMeAESKs/v4DWs' expected = [ ([11, 12], {'sha256': 'g1vx9si329muxV3tquWIXXySNOIwRGMeAESKs/v4DWs'}), ([31, 32], {'sha512': 'g1vx9si329muxV3tquWIXXySNOIwRGMeAESKs/v4DWs'}), ] entry = RouterStatusEntryV3(content, document = vote_document()) self.assertEqual(expected, entry.microdescriptor_hashes) # try without a document expect_invalid_attr(self, {'m': '8,9,10,11,12'}, 'microdescriptor_hashes', expected_value = []) # tries some invalid inputs test_values = ( '', '4,a,2', '1,2,3 stuff', ) for m_line in test_values: content = RouterStatusEntryV3.content({'m': m_line}) self.assertRaises(ValueError, RouterStatusEntryV3, content, True, vote_document())
def test_check_digests(self): import check_digests as module fingerprint = 'A7569A83B5706AB1B1A9CB52EFF7D2D32E4553EB' extrainfo_desc = RelayExtraInfoDescriptor.create() server_desc = RelayDescriptor.create( {'extra-info-digest': extrainfo_desc.digest()}, sign=True) encoded_digest = base64.b64encode( binascii.unhexlify(server_desc.digest())).rstrip(b'=') consensus_desc = RouterStatusEntryV3.create({ 'r': 'caerSidi p1aag7VwarGxqctS7/fS0y5FU+s %s 2012-08-06 11:19:31 71.35.150.29 9001 0' % encoded_digest.decode('utf-8'), }) bad_consensus_desc = RouterStatusEntryV3.create({ 'r': 'caerSidi p1aag7VwarGxqctS7/fS0y5FU+s oQZFLYe9e4A7bOkWKR7TaNxb0JE 2012-08-06 11:19:31 71.35.150.29 9001 0', }) with patch('stem.descriptor.remote.get_server_descriptors', _download_of(server_desc)): with patch('stem.descriptor.remote.get_extrainfo_descriptors', _download_of(extrainfo_desc)): # correctly signed descriptors with patch('stem.descriptor.remote.get_consensus', _download_of(consensus_desc)): with patch('sys.stdout', new_callable=io.StringIO) as stdout_mock: module.validate_relay(fingerprint) self.assertEqual(EXPECTED_CHECK_DIGESTS_OK, stdout_mock.getvalue()) # incorrect server descriptor digest with patch('stem.descriptor.remote.get_consensus', _download_of(bad_consensus_desc)): with patch('sys.stdout', new_callable=io.StringIO) as stdout_mock: module.validate_relay(fingerprint) self.assertEqual( EXPECTED_CHECK_DIGESTS_BAD % server_desc.digest(), stdout_mock.getvalue())
def _get_router_status(address = None, port = None, nickname = None, fingerprint_base64 = None, s_line = None): r_line = 'caerSidi p1aag7VwarGxqctS7/fS0y5FU+s oQZFLYe9e4A7bOkWKR7TaNxb0JE 2012-08-06 11:19:31 71.35.150.29 9001 0' if address: r_line = r_line.replace('71.35.150.29', address) if port: r_line = r_line.replace('9001', port) if nickname: r_line = r_line.replace('caerSidi', nickname) if fingerprint_base64: r_line = r_line.replace('p1aag7VwarGxqctS7/fS0y5FU+s', fingerprint_base64) if s_line: return RouterStatusEntryV3.create({'r': r_line, 's': s_line}) else: return RouterStatusEntryV3.create({'r': r_line})
def test_with_microdescriptor_router_status_entries(self): """ Includes microdescriptor flavored router status entries within the document. """ entry1 = get_router_status_entry_micro_v3({'s': "Fast"}) entry2 = get_router_status_entry_micro_v3({'s': "Valid"}) document = get_network_status_document_v3( {"network-status-version": "3 microdesc"}, routers=(entry1, entry2)) self.assertEquals((entry1, entry2), document.routers) # try with an invalid RouterStatusEntry entry3 = RouterStatusEntryMicroV3( get_router_status_entry_micro_v3({'r': "ugabuga"}, content=True), False) content = get_network_status_document_v3( {"network-status-version": "3 microdesc"}, routers=(entry3, ), content=True) self.assertRaises(ValueError, NetworkStatusDocumentV3, content) document = NetworkStatusDocumentV3(content, False) self.assertEquals((entry3, ), document.routers) # try including microdescriptor entries in a normal consensus content = get_network_status_document_v3(routers=(entry1, entry2), content=True) self.assertRaises(ValueError, NetworkStatusDocumentV3, content) expected_routers = ( RouterStatusEntryV3(str(entry1), False), RouterStatusEntryV3(str(entry2), False), ) document = NetworkStatusDocumentV3(content, False) self.assertEquals(expected_routers, document.routers)
def test_validate_descriptor_content(self, stdout_mock, parse_file_mock): parse_file_mock.return_value = [ RouterStatusEntryV3.create({ 'r': 'caerSidi p1aag7VwarGxqctS7/fS0y5FU+s oQZFLYe9e4A7bOkWKR7TaNxb0JE 2012-08-06 11:19:31 71.35.150.29 9001 0', }) ] import validate_descriptor_content self.assertEqual( 'found relay caerSidi (A7569A83B5706AB1B1A9CB52EFF7D2D32E4553EB)\n', stdout_mock.getvalue())
def test_duplicate_lines(self): """ Duplicates linesin the entry. """ lines = get_router_status_entry_v3(content = True).split("\n") for index, duplicate_line in enumerate(lines): content = "\n".join(lines[:index] + [duplicate_line] + lines[index:]) self.assertRaises(ValueError, RouterStatusEntryV3, content) entry = RouterStatusEntryV3(content, False) self.assertEqual("caerSidi", entry.nickname)
def test_descriptor_from_orport(self, stdout_mock, downloader_mock): downloader_mock().get_consensus.return_value = [ RouterStatusEntryV3.create({ 'r': 'caerSidi p1aag7VwarGxqctS7/fS0y5FU+s oQZFLYe9e4A7bOkWKR7TaNxb0JE 2012-08-06 11:19:31 71.35.150.29 9001 0', }) ] import descriptor_from_orport self.assertEqual( 'found relay caerSidi (A7569A83B5706AB1B1A9CB52EFF7D2D32E4553EB)\n', stdout_mock.getvalue())
def make_router_status_entry( self) -> 'stem.descriptor.router_status_entry.RouterStatusEntryV3': """ Provides a RouterStatusEntryV3 for this descriptor content. .. versionadded:: 1.6.0 :returns: :class:`~stem.descriptor.router_status_entry.RouterStatusEntryV3` 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, _truncated_b64encode( binascii.unhexlify( stem.util.str_tools._to_bytes(self.fingerprint))), _truncated_b64encode( binascii.unhexlify( stem.util.str_tools._to_bytes(self.digest()))), self.published.strftime('%Y-%m-%d %H:%M:%S'), self.address, 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'] = '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' % _truncated_b64encode( self.certificate.key) return RouterStatusEntryV3.create(attr) # type: ignore
def _expect_invalid_attr(self, content, attr=None, expected_value=None): """ Asserts that construction will fail due to content having a malformed attribute. If an attr is provided then we check that it matches an expected value when we're constructed without validation. """ self.assertRaises(ValueError, RouterStatusEntryV3, content) entry = RouterStatusEntryV3(content, False) if attr: self.assertEquals(expected_value, getattr(entry, attr)) else: self.assertEquals('caerSidi', entry.nickname)
def test_persisting_a_consensus_with_parse_file(self, stdout_mock, parse_file_mock): consensus = NetworkStatusDocumentV3.create( routers=(RouterStatusEntryV3.create({ 'r': 'caerSidi p1aag7VwarGxqctS7/fS0y5FU+s oQZFLYe9e4A7bOkWKR7TaNxb0JE 2012-08-06 11:19:31 71.35.150.29 9001 0', }), )) parse_file_mock.return_value = iter([consensus]) import persisting_a_consensus_with_parse_file self.assertEqual(EXPECTED_PERSISTING_A_CONSENSUS, stdout_mock.getvalue())
def test_ipv6_addresses(self): """ Handles a variety of 'a' lines. """ test_values = { '[2607:fcd0:daaa:101::602c:bd62]:443': [('2607:fcd0:daaa:101::602c:bd62', 443, True)], } for a_line, expected in test_values.items(): entry = RouterStatusEntryV3.create({'a': a_line}) self.assertEqual(expected, entry.or_addresses) # includes multiple 'a' lines content = RouterStatusEntryV3.content() content += b'\na [2607:fcd0:daaa:101::602c:bd62]:443' content += b'\na [1148:fcd0:daaa:101::602c:bd62]:80' expected = [ ('2607:fcd0:daaa:101::602c:bd62', 443, True), ('1148:fcd0:daaa:101::602c:bd62', 80, True), ] entry = RouterStatusEntryV3(content) self.assertEqual(expected, entry.or_addresses) # tries some invalid inputs test_values = ( '', '[1148:fcd0:daaa:101::602c:bd62]:80000', ) for a_line in test_values: expect_invalid_attr(self, {'a': a_line}, expected_value={})
def test_ipv6_addresses(self): """ Handles a variety of 'a' lines. """ test_values = { '[2607:fcd0:daaa:101::602c:bd62]:443': [ ('2607:fcd0:daaa:101::602c:bd62', 443, True)], } for a_line, expected in test_values.items(): entry = RouterStatusEntryV3.create({'a': a_line}) self.assertEqual(expected, entry.or_addresses) # includes multiple 'a' lines content = RouterStatusEntryV3.content() content += b'\na [2607:fcd0:daaa:101::602c:bd62]:443' content += b'\na [1148:fcd0:daaa:101::602c:bd62]:80' expected = [ ('2607:fcd0:daaa:101::602c:bd62', 443, True), ('1148:fcd0:daaa:101::602c:bd62', 80, True), ] entry = RouterStatusEntryV3(content) self.assertEqual(expected, entry.or_addresses) # tries some invalid inputs test_values = ( '', '[1148:fcd0:daaa:101::602c:bd62]:80000', ) for a_line in test_values: expect_invalid_attr(self, {'a': a_line}, expected_value = {})
def test_mirror_mirror_on_the_wall_3(self, open_mock, stdout_mock): def tutorial_example(): from stem.descriptor import parse_file for desc in parse_file(open('/home/atagar/.tor/cached-consensus')): print('found relay %s (%s)' % (desc.nickname, desc.fingerprint)) test_file = io.BytesIO(NetworkStatusDocumentV3.content(routers = [RouterStatusEntryV3.create({ 'r': 'caerSidi p1aag7VwarGxqctS7/fS0y5FU+s oQZFLYe9e4A7bOkWKR7TaNxb0JE 2012-08-06 11:19:31 71.35.150.29 9001 0', })])) test_file.name = '/home/atagar/.tor/cached-consensus' open_mock.return_value = test_file tutorial_example() self.assertEqual('found relay caerSidi (A7569A83B5706AB1B1A9CB52EFF7D2D32E4553EB)\n', stdout_mock.getvalue())
def test_versions(self): """ Handles a variety of version inputs. """ test_values = { 'Tor 0.2.2.35': Version('0.2.2.35'), 'Tor 0.1.2': Version('0.1.2'), 'Torr new_stuff': None, 'new_stuff and stuff': None, } for v_line, expected in test_values.items(): entry = RouterStatusEntryV3.create({'v': v_line}) self.assertEqual(expected, entry.version) self.assertEqual(v_line, entry.version_line) # tries an invalid input expect_invalid_attr(self, {'v': 'Tor ugabuga'}, 'version')
def test_bandwidth(self): """ Handles a variety of 'w' lines. """ test_values = { 'Bandwidth=0': (0, None, False, []), 'Bandwidth=63138': (63138, None, False, []), 'Bandwidth=11111 Measured=482': (11111, 482, False, []), 'Bandwidth=11111 Measured=482 Blarg!': (11111, 482, False, ['Blarg!']), 'Bandwidth=11111 Measured=482 Unmeasured=1 Blarg!': (11111, 482, True, ['Blarg!']), } for w_line, expected in test_values.items(): entry = RouterStatusEntryV3.create({'w': w_line}) self.assertEqual(expected[0], entry.bandwidth) self.assertEqual(expected[1], entry.measured) self.assertEqual(expected[2], entry.is_unmeasured) self.assertEqual(expected[3], entry.unrecognized_bandwidth_entries) # tries some invalid inputs test_values = ( '', 'blarg', 'Bandwidth', 'Bandwidth=', 'Bandwidth:0', 'Bandwidth 0', 'Bandwidth=-10', 'Bandwidth=10 Measured', 'Bandwidth=10 Measured=', 'Bandwidth=10 Measured=-50', 'Bandwidth=10 Measured=482 Unmeasured', 'Bandwidth=10 Measured=482 Unmeasured=', 'Bandwidth=10 Measured=482 Unmeasured=0', 'Bandwidth=10 Measured=482 Unmeasured=842', 'Bandwidth=10 Measured=482 Unmeasured=-5', ) for w_line in test_values: expect_invalid_attr(self, {'w': w_line})
def make_router_status_entry(self): """ Provides a RouterStatusEntryV3 for this descriptor content. .. versionadded:: 1.6.0 :returns: :class:`~stem.descriptor.router_status_entry.RouterStatusEntryV3` 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, _truncated_b64encode(binascii.unhexlify(stem.util.str_tools._to_bytes(self.fingerprint))), _truncated_b64encode(binascii.unhexlify(stem.util.str_tools._to_bytes(self.digest()))), self.published.strftime('%Y-%m-%d %H:%M:%S'), self.address, 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'] = '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' % _truncated_b64encode(self.certificate.key) return RouterStatusEntryV3.create(attr)
def test_exit_policy(self): """ Handles a variety of 'p' lines. """ test_values = { 'reject 1-65535': MicroExitPolicy('reject 1-65535'), 'accept 80,110,143,443': MicroExitPolicy('accept 80,110,143,443'), } for p_line, expected in test_values.items(): entry = RouterStatusEntryV3.create({'p': p_line}) self.assertEqual(expected, entry.exit_policy) # tries some invalid inputs test_values = ( '', 'blarg', 'reject -50', 'accept 80,', ) for p_line in test_values: expect_invalid_attr(self, {'p': p_line}, 'exit_policy')
def test_protocols(self): desc = RouterStatusEntryV3.create({'pr': 'Cons=1 Desc=1 DirCache=1 HSDir=1 HSIntro=3 HSRend=1 Link=1-4 LinkAuth=1 Microdesc=1 Relay=1-2'}) self.assertEqual(10, len(desc.protocols))