[tor-commits] [stem/master] Unpack NETINFO cells

atagar at torproject.org atagar at torproject.org
Sun Jan 21 02:04:04 UTC 2018


commit 25fb815e8ab7ee0218ea7b9c44cd9700c3b50e51
Author: Damian Johnson <atagar at 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))





More information about the tor-commits mailing list