commit 9b5c6bf74e3d6bc7a718d84c9d24e07bac460f0d Author: Damian Johnson atagar@torproject.org Date: Mon Jan 8 11:42:46 2018 -0800
Split stem.client.cell into its own module --- stem/client/__init__.py | 141 +---------------------------- stem/client/cell.py | 151 ++++++++++++++++++++++++++++++++ test/settings.cfg | 2 +- test/unit/client/__init__.py | 7 ++ test/unit/{client.py => client/cell.py} | 13 +-- 5 files changed, 169 insertions(+), 145 deletions(-)
diff --git a/stem/client/__init__.py b/stem/client/__init__.py index a0288f52..21786835 100644 --- a/stem/client/__init__.py +++ b/stem/client/__init__.py @@ -31,128 +31,14 @@ a wrapper for :class:`~stem.socket.RelaySocket`, much the same way as Relay - Connection with a relay's ORPort. """
-import collections -import struct - from stem.util import enum
ZERO = '\x00'
-class Cell(collections.namedtuple('Cell', ['name', 'value', 'fixed_size', 'for_circuit'])): - """ - Metadata for ORPort cells. - - :var str name: command of the cell - :var int value: integer value of the command on the wire - :var bool fixed_size: **True** if cells have a fixed length, - **False** if variable - :var bool for_circuit: **True** if command is for a circuit, - **False** otherwise - """ - - @staticmethod - def by_name(name): - """ - Provides cell attributes by its name. - - :parm str name: cell command to fetch - - :raise: **ValueError** if cell type is invalid - """ - - for cell_type in CELL_TYPES: - if name == cell_type.name: - return cell_type - - raise ValueError("'%s' isn't a valid cell type" % name) - - @staticmethod - def by_value(value): - """ - Provides cell attributes by its value. - - :parm int value: cell value to fetch - - :raise: **ValueError** if cell type is invalid - """ - - for cell_type in CELL_TYPES: - if value == cell_type.value: - return cell_type - - raise ValueError("'%s' isn't a valid cell value" % value) - - @staticmethod - def pack(name, link_version, payload, circ_id = None): - """ - Provides bytes that can be used on the wire for these cell attributes. - - :param str name: cell command - :param int link_version: link protocol version - :param bytes payload: cell payload - :param int circ_id: circuit id, if for a circuit - - :raise: **ValueError** if... - * cell type or circuit id is invalid - * payload is too large - """ - - attr = Cell.by_name(name) - circ_id_len = Pack.LONG if link_version > 3 else Pack.SHORT - - if attr.for_circuit and circ_id is None: - if name.startswith('CREATE'): - # Since we're initiating the circuit we pick any value from a range - # that's determined by our link version. - - circ_id = 0x80000000 if link_version > 3 else 0x01 - else: - raise ValueError('%s cells require a circ_id' % name) - elif not attr.for_circuit: - if circ_id: - raise ValueError("%s cells don't concern circuits, circ_id is unused" % name) - - circ_id = 0 # field is still mandatory for all cells - - packed_circ_id = struct.pack(circ_id_len, circ_id) - packed_command = struct.pack(Pack.CHAR, attr.value) - packed_size = b'' if attr.fixed_size else struct.pack(Pack.SHORT, len(payload)) - cell = b''.join((packed_circ_id, packed_command, packed_size, payload)) - - # pad fixed sized cells to the required length - - if attr.fixed_size: - fixed_cell_len = 514 if link_version > 3 else 512 - - if len(cell) > fixed_cell_len: - raise ValueError('Payload of %s is too large (%i bytes), must be less than %i' % (name, len(cell), fixed_cell_len)) - - cell += ZERO * (fixed_cell_len - len(cell)) - - return cell - - -class VersionCell(Cell): - """ - Link version negotiation cell. - """ - - @staticmethod - def pack(versions): - """ - Provides the payload for a series of link versions. - - :param list versions: link versions to serialize - - :returns: **bytes** with a payload for these versions - """ - - # Used for link version negotiation so we don't have that yet. This is fine - # since VERSION cells avoid most version dependent attributes. - - payload = b''.join([struct.pack(Pack.SHORT, v) for v in versions]) - return Cell.pack('VERSIONS', 3, payload) +__all__ = [ + 'cell', +]
class Relay(object): @@ -168,24 +54,3 @@ Pack = enum.Enum( ('LONG', '!L'), # 4 bytes ('LONG_LONG', '!Q'), # 8 bytes ) - -CELL_TYPES = ( - Cell('PADDING', 0, True, False), # Padding (section 7.2) - Cell('CREATE', 1, True, True), # Create a circuit (section 5.1) - Cell('CREATED', 2, True, True), # Acknowledge create (section 5.1) - Cell('RELAY', 3, True, True), # End-to-end data (section 5.5 and 6) - Cell('DESTROY', 4, True, True), # Stop using a circuit (section 5.4) - Cell('CREATE_FAST', 5, True, True), # Create a circuit, no PK (section 5.1) - Cell('CREATED_FAST', 6, True, True), # Circuit created, no PK (section 5.1) - Cell('VERSIONS', 7, False, False), # Negotiate proto version (section 4) - Cell('NETINFO', 8, True, False), # Time and address info (section 4.5) - Cell('RELAY_EARLY', 9, True, True), # End-to-end data; limited (section 5.6) - Cell('CREATE2', 10, True, True), # Extended CREATE cell (section 5.1) - Cell('CREATED2', 11, True, True), # Extended CREATED cell (section 5.1) - Cell('PADDING_NEGOTIATE', 12, True, False), # Padding negotiation (section 7.2) - Cell('VPADDING', 128, False, False), # Variable-length padding (section 7.2) - Cell('CERTS', 129, False, False), # Certificates (section 4.2) - Cell('AUTH_CHALLENGE', 130, False, False), # Challenge value (section 4.3) - Cell('AUTHENTICATE', 131, False, False), # Client authentication (section 4.5) - Cell('AUTHORIZE', 132, False, False), # Client authorization (not yet used) -) diff --git a/stem/client/cell.py b/stem/client/cell.py new file mode 100644 index 00000000..5cd23c0e --- /dev/null +++ b/stem/client/cell.py @@ -0,0 +1,151 @@ +# Copyright 2018, Damian Johnson and The Tor Project +# See LICENSE for licensing information + +""" +Messages communicated over a Tor relay's ORPort. + +.. versionadded:: 1.7.0 +""" + +import collections +import struct + +from stem.client import ZERO, Pack + + +class Cell(collections.namedtuple('Cell', ['name', 'value', 'fixed_size', 'for_circuit'])): + """ + Metadata for ORPort cells. + + :var str name: command of the cell + :var int value: integer value of the command on the wire + :var bool fixed_size: **True** if cells have a fixed length, + **False** if variable + :var bool for_circuit: **True** if command is for a circuit, + **False** otherwise + """ + + @staticmethod + def by_name(name): + """ + Provides cell attributes by its name. + + :parm str name: cell command to fetch + + :raise: **ValueError** if cell type is invalid + """ + + for cell_type in CELL_TYPES: + if name == cell_type.name: + return cell_type + + raise ValueError("'%s' isn't a valid cell type" % name) + + @staticmethod + def by_value(value): + """ + Provides cell attributes by its value. + + :parm int value: cell value to fetch + + :raise: **ValueError** if cell type is invalid + """ + + for cell_type in CELL_TYPES: + if value == cell_type.value: + return cell_type + + raise ValueError("'%s' isn't a valid cell value" % value) + + @staticmethod + def pack(name, link_version, payload, circ_id = None): + """ + Provides bytes that can be used on the wire for these cell attributes. + + :param str name: cell command + :param int link_version: link protocol version + :param bytes payload: cell payload + :param int circ_id: circuit id, if for a circuit + + :raise: **ValueError** if... + * cell type or circuit id is invalid + * payload is too large + """ + + attr = Cell.by_name(name) + circ_id_len = Pack.LONG if link_version > 3 else Pack.SHORT + + if attr.for_circuit and circ_id is None: + if name.startswith('CREATE'): + # Since we're initiating the circuit we pick any value from a range + # that's determined by our link version. + + circ_id = 0x80000000 if link_version > 3 else 0x01 + else: + raise ValueError('%s cells require a circ_id' % name) + elif not attr.for_circuit: + if circ_id: + raise ValueError("%s cells don't concern circuits, circ_id is unused" % name) + + circ_id = 0 # field is still mandatory for all cells + + packed_circ_id = struct.pack(circ_id_len, circ_id) + packed_command = struct.pack(Pack.CHAR, attr.value) + packed_size = b'' if attr.fixed_size else struct.pack(Pack.SHORT, len(payload)) + cell = b''.join((packed_circ_id, packed_command, packed_size, payload)) + + # pad fixed sized cells to the required length + + if attr.fixed_size: + fixed_cell_len = 514 if link_version > 3 else 512 + + if len(cell) > fixed_cell_len: + raise ValueError('Payload of %s is too large (%i bytes), must be less than %i' % (name, len(cell), fixed_cell_len)) + + cell += ZERO * (fixed_cell_len - len(cell)) + + return cell + + +class VersionCell(Cell): + """ + Link version negotiation cell. + """ + + @staticmethod + def pack(versions): + """ + Provides the payload for a series of link versions. + + :param list versions: link versions to serialize + + :returns: **bytes** with a payload for these versions + """ + + # Used for link version negotiation so we don't have that yet. This is fine + # since VERSION cells avoid most version dependent attributes. + + payload = b''.join([struct.pack(Pack.SHORT, v) for v in versions]) + return Cell.pack('VERSIONS', 3, payload) + + +CELL_TYPES = ( + Cell('PADDING', 0, True, False), # Padding (section 7.2) + Cell('CREATE', 1, True, True), # Create a circuit (section 5.1) + Cell('CREATED', 2, True, True), # Acknowledge create (section 5.1) + Cell('RELAY', 3, True, True), # End-to-end data (section 5.5 and 6) + Cell('DESTROY', 4, True, True), # Stop using a circuit (section 5.4) + Cell('CREATE_FAST', 5, True, True), # Create a circuit, no PK (section 5.1) + Cell('CREATED_FAST', 6, True, True), # Circuit created, no PK (section 5.1) + Cell('VERSIONS', 7, False, False), # Negotiate proto version (section 4) + Cell('NETINFO', 8, True, False), # Time and address info (section 4.5) + Cell('RELAY_EARLY', 9, True, True), # End-to-end data; limited (section 5.6) + Cell('CREATE2', 10, True, True), # Extended CREATE cell (section 5.1) + Cell('CREATED2', 11, True, True), # Extended CREATED cell (section 5.1) + Cell('PADDING_NEGOTIATE', 12, True, False), # Padding negotiation (section 7.2) + Cell('VPADDING', 128, False, False), # Variable-length padding (section 7.2) + Cell('CERTS', 129, False, False), # Certificates (section 4.2) + Cell('AUTH_CHALLENGE', 130, False, False), # Challenge value (section 4.3) + Cell('AUTHENTICATE', 131, False, False), # Client authentication (section 4.5) + Cell('AUTHORIZE', 132, False, False), # Client authorization (not yet used) +) diff --git a/test/settings.cfg b/test/settings.cfg index 501dff9c..b4b125a8 100644 --- a/test/settings.cfg +++ b/test/settings.cfg @@ -229,7 +229,7 @@ test.unit_tests |test.unit.response.authchallenge.TestAuthChallengeResponse |test.unit.response.protocolinfo.TestProtocolInfoResponse |test.unit.response.mapaddress.TestMapAddressResponse -|test.unit.client.TestClient +|test.unit.client.cell.TestCell |test.unit.connection.authentication.TestAuthenticate |test.unit.connection.connect.TestConnect |test.unit.control.controller.TestControl diff --git a/test/unit/client/__init__.py b/test/unit/client/__init__.py new file mode 100644 index 00000000..e35416a8 --- /dev/null +++ b/test/unit/client/__init__.py @@ -0,0 +1,7 @@ +""" +Unit tests for stem.client.* contents. +""" + +__all__ = [ + 'cell', +] diff --git a/test/unit/client.py b/test/unit/client/cell.py similarity index 87% rename from test/unit/client.py rename to test/unit/client/cell.py index 6faa3e8e..8093b8d9 100644 --- a/test/unit/client.py +++ b/test/unit/client/cell.py @@ -1,15 +1,16 @@ """ -Unit tests for the stem.client. +Unit tests for the stem.client.cell. """
import struct import unittest
-from stem.client import Pack, Cell +from stem.client import Pack +from stem.client.cell import Cell
-class TestClient(unittest.TestCase): - def test_cell_by_name(self): +class TestCell(unittest.TestCase): + def test_by_name(self): cell = Cell.by_name('NETINFO') self.assertEqual('NETINFO', cell.name) self.assertEqual(8, cell.value) @@ -20,7 +21,7 @@ class TestClient(unittest.TestCase): self.assertRaises(ValueError, Cell.by_name, 85) self.assertRaises(ValueError, Cell.by_name, None)
- def test_cell_by_value(self): + def test_by_value(self): cell = Cell.by_value(8) self.assertEqual('NETINFO', cell.name) self.assertEqual(8, cell.value) @@ -31,7 +32,7 @@ class TestClient(unittest.TestCase): self.assertRaises(ValueError, Cell.by_value, 85) self.assertRaises(ValueError, Cell.by_value, None)
- def test_cell_pack(self): + def test_pack(self): version_payload = struct.pack(Pack.SHORT, 2)
# basic link v2 and v4 VERSIONS cell
tor-commits@lists.torproject.org