commit 25fb815e8ab7ee0218ea7b9c44cd9700c3b50e51 Author: Damian Johnson atagar@torproject.org Date: Mon Jan 15 12:07:21 2018 -0800
Unpack NETINFO cells
Adding support for unpacking NETINFO cells, but not packing. Not terribly hard to add, but also not needed right now. --- stem/client/cell.py | 62 +++++++++++++++++++++++++++++++++++++++++++++++- stem/util/__init__.py | 3 +++ test/unit/client/cell.py | 42 +++++++++++++++++++++++++++++--- 3 files changed, 103 insertions(+), 4 deletions(-)
diff --git a/stem/client/cell.py b/stem/client/cell.py index df34508d..98ab198c 100644 --- a/stem/client/cell.py +++ b/stem/client/cell.py @@ -36,6 +36,7 @@ Messages communicated over a Tor relay's ORPort. +- unpack - Decodes bytes for this cell class. """
+import datetime import inspect import io import os @@ -43,7 +44,8 @@ import random import sys
from stem import UNDEFINED -from stem.client import ZERO, Certificate, Size +from stem.client import ZERO, Address, Certificate, Size +from stem.util import _hash_attr
FIXED_PAYLOAD_LEN = 509 AUTH_CHALLENGE_SIZE = 32 @@ -184,6 +186,12 @@ class Cell(object):
raise NotImplementedError('Unpacking not yet implemented for %s cells' % cls.NAME)
+ def __eq__(self, other): + return hash(self) == hash(other) if isinstance(other, Cell) else False + + def __ne__(self, other): + return not self == other +
class CircuitCell(Cell): """ @@ -245,6 +253,9 @@ class PaddingCell(Cell): def _unpack(cls, content, circ_id, link_version): return PaddingCell(content)
+ def __hash__(self): + return _hash_attr(self, 'payload') +
class CreateCell(CircuitCell): NAME = 'CREATE' @@ -322,12 +333,52 @@ class VersionsCell(Cell):
return VersionsCell(link_versions)
+ def __hash__(self): + return _hash_attr(self, 'versions') +
class NetinfoCell(Cell): + """ + Information relays exchange about each other. + + :var datetime timestamp: current time + :var stem.client.Address receiver_address: receiver's OR address + :var list sender_addresses: sender's OR addresses + """ + NAME = 'NETINFO' VALUE = 8 IS_FIXED_SIZE = True
+ def __init__(self, timestamp, receiver_address, sender_addresses): + self.timestamp = timestamp + self.receiver_address = receiver_address + self.sender_addresses = sender_addresses + + @classmethod + def pack(cls, link_version, receiver_address, sender_addresses, timestamp = None): + raise NotImplementedError('Netinfo packing not yet implemented') + + @classmethod + def _unpack(cls, content, circ_id, link_version): + if len(content) < 4: + raise ValueError('NETINFO cell expected to start with a timestamp') + + timestamp, content = Size.LONG.pop(content) + receiver_address, content = Address.pop(content) + + sender_addresses = [] + sender_addr_count, content = Size.CHAR.pop(content) + + for i in range(sender_addr_count): + addr, content = Address.pop(content) + sender_addresses.append(addr) + + return NetinfoCell(datetime.datetime.utcfromtimestamp(timestamp), receiver_address, sender_addresses) + + def __hash__(self): + return _hash_attr(self, 'timestamp', 'receiver_address', 'sender_addresses') +
class RelayEarlyCell(CircuitCell): NAME = 'RELAY_EARLY' @@ -394,6 +445,9 @@ class VPaddingCell(Cell): def _unpack(cls, content, circ_id, link_version): return VPaddingCell(content)
+ def __hash__(self): + return _hash_attr(self, 'payload') +
class CertsCell(Cell): """ @@ -450,6 +504,9 @@ class CertsCell(Cell):
return CertsCell(certs)
+ def __hash__(self): + return _hash_attr(self, 'certificates') +
class AuthChallengeCell(Cell): """ @@ -513,6 +570,9 @@ class AuthChallengeCell(Cell):
return AuthChallengeCell(challenge, methods)
+ def __hash__(self): + return _hash_attr(self, 'challenge', 'methods') +
class AuthenticateCell(Cell): NAME = 'AUTHENTICATE' diff --git a/stem/util/__init__.py b/stem/util/__init__.py index 242a3110..b206dc73 100644 --- a/stem/util/__init__.py +++ b/stem/util/__init__.py @@ -69,6 +69,9 @@ def _hash_attr(obj, *attributes, **kwargs): if isinstance(attr_value, dict): for k in sorted(attr_value.keys()): my_hash = (my_hash + hash(k)) * 1024 + hash(attr_value[k]) + elif isinstance(attr_value, (list, tuple)): + for entry in attr_value: + my_hash = (my_hash + hash(entry)) * 1024 else: my_hash += hash(attr_value)
diff --git a/test/unit/client/cell.py b/test/unit/client/cell.py index f95a664e..cf7eb684 100644 --- a/test/unit/client/cell.py +++ b/test/unit/client/cell.py @@ -2,10 +2,11 @@ Unit tests for the stem.client.cell. """
+import datetime import os import unittest
-from stem.client import Certificate +from stem.client import ZERO, Address, Certificate from test.unit.client import test_data
from stem.client.cell import ( @@ -13,6 +14,7 @@ from stem.client.cell import ( Cell, PaddingCell, VersionsCell, + NetinfoCell, VPaddingCell, CertsCell, AuthChallengeCell, @@ -31,6 +33,8 @@ VERSIONS_CELLS = { '\x00\x00\x07\x00\x06\x00\x01\x00\x02\x00\x03': [1, 2, 3], }
+NETINFO_CELL = '\x00\x00\x08ZZ\xb6\x90\x04\x04\x7f\x00\x00\x01\x01\x04\x04aq\x0f\x02' + ZERO * (FIXED_PAYLOAD_LEN - 17) + VPADDING_CELLS = { '\x00\x00\x80\x00\x00': '', '\x00\x00\x80\x00\x01\x08': '\x08', @@ -74,8 +78,33 @@ class TestCell(unittest.TestCase): self.assertRaisesRegexp(NotImplementedError, 'Unpacking not yet implemented for AUTHORIZE cells', Cell.unpack, '\x00\x00\x84\x00\x06\x00\x01\x00\x02\x00\x03', 2)
def test_unpack_for_new_link(self): - # TODO: we need to support more cell types before we can test this - self.assertRaisesRegexp(NotImplementedError, 'Unpacking not yet implemented for NETINFO cells', Cell.unpack, test_data('new_link_cells'), 2) + expected_certs = ( + (1, '0\x82\x02F0\x82\x01\xaf'), + (2, '0\x82\x01\xc90\x82\x012'), + (4, '\x01\x04\x00\x06m\x1f'), + (5, '\x01\x05\x00\x06m\n\x01'), + (7, '\x1a\xa5\xb3\xbd\x88\xb1C'), + ) + + link_cells = Cell.unpack(test_data('new_link_cells'), 2) + self.assertEqual(4, len(link_cells)) + self.assertEqual(VersionsCell([3, 4, 5]), link_cells[0]) + + certs_cell = link_cells[1] + self.assertEqual(CertsCell, type(certs_cell)) + self.assertEqual(len(expected_certs), len(certs_cell.certificates)) + + for i, (cert_type, cert_prefix) in enumerate(expected_certs): + self.assertEqual(cert_type, certs_cell.certificates[i].type) + self.assertTrue(certs_cell.certificates[i].value.startswith(cert_prefix)) + + self.assertEqual(AuthChallengeCell('\x89Y\t\x99\xb2\x1e\xd9*V\xb6\x1bn\n\x05\xd8/\xe3QH\x85\x13Z\x17\xfc\x1c\x00{\xa9\xae\x83^K', [1, 3]), link_cells[2]) + + netinfo_cell = link_cells[3] + self.assertEqual(NetinfoCell, type(netinfo_cell)) + self.assertEqual(datetime.datetime(2018, 1, 14, 1, 46, 56), netinfo_cell.timestamp) + self.assertEqual(Address(type='IPv4', type_int=4, value='127.0.0.1', value_bin='\x7f\x00\x00\x01'), netinfo_cell.receiver_address) + self.assertEqual([Address(type='IPv4', type_int=4, value='97.113.15.2', value_bin='aq\x0f\x02')], netinfo_cell.sender_addresses)
def test_padding_packing(self): for cell_bytes, payload in PADDING_CELLS.items(): @@ -87,6 +116,13 @@ class TestCell(unittest.TestCase): self.assertEqual(cell_bytes, VersionsCell.pack(versions)) self.assertEqual(versions, Cell.unpack(cell_bytes, 2)[0].versions)
+ def test_netinfo_packing(self): + cell = Cell.unpack(NETINFO_CELL, 2)[0] + + self.assertEqual(datetime.datetime(2018, 1, 14, 1, 46, 56), cell.timestamp) + self.assertEqual(Address(type='IPv4', type_int=4, value='127.0.0.1', value_bin='\x7f\x00\x00\x01'), cell.receiver_address) + self.assertEqual([Address(type='IPv4', type_int=4, value='97.113.15.2', value_bin='aq\x0f\x02')], cell.sender_addresses) + def test_vpadding_packing(self): for cell_bytes, payload in VPADDING_CELLS.items(): self.assertEqual(cell_bytes, VPaddingCell.pack(2, payload = payload))