commit aeb17f9d150a7e3f404a37f57c29759325ab3221 Author: Damian Johnson atagar@torproject.org Date: Mon Jan 22 12:10:21 2018 -0800
Support DESTROY cells --- stem/client/__init__.py | 40 +++++++++++++++++++++++++ stem/client/cell.py | 76 ++++++++++++++++++++++++++++++++++++++++++++++-- test/unit/client/cell.py | 20 ++++++++++++- 3 files changed, 132 insertions(+), 4 deletions(-)
diff --git a/stem/client/__init__.py b/stem/client/__init__.py index 1fd05f9d..01c6b749 100644 --- a/stem/client/__init__.py +++ b/stem/client/__init__.py @@ -49,6 +49,29 @@ a wrapper for :class:`~stem.socket.RelaySocket`, much the same way as **AUTHENTICATE** RSA1024 AUTHENTICATE cell link certificate **UNKNOWN** unrecognized certificate type ===================== =========== + +.. data:: CloseReason (enum) + + Reason a relay is closed. + + ===================== =========== + CloseReason Description + ===================== =========== + **NONE** no reason given + **PROTOCOL** tor protocol violation + **INTERNAL** internal error + **REQUESTED** client sent a TRUNCATE command + **HIBERNATING** relay suspended, trying to save bandwidth + **RESOURCELIMIT** out of memory, sockets, or circuit IDs + **CONNECTFAILED** unable to reach relay + **OR_IDENTITY** connected, but its OR identity was not as expected + **OR_CONN_CLOSED** connection that was carrying this circuit died + **FINISHED** circuit has expired for being dirty or old + **TIMEOUT** circuit construction took too long + **DESTROYED** circuit was destroyed without a client TRUNCATE + **NOSUCHSERVICE** request was for an unknown hidden service + **UNKNOWN** unrecognized reason + ===================== =========== """
import io @@ -81,6 +104,23 @@ CertType = stem.util.enum.UppercaseEnum( 'UNKNOWN', )
+CloseReason = stem.util.enum.UppercaseEnum( + 'NONE', + 'PROTOCOL', + 'INTERNAL', + 'REQUESTED', + 'HIBERNATING', + 'RESOURCELIMIT', + 'CONNECTFAILED', + 'OR_IDENTITY', + 'OR_CONN_CLOSED', + 'FINISHED', + 'TIMEOUT', + 'DESTROYED', + 'NOSUCHSERVICE', + 'UNKNOWN', +) +
def split(content, size): """ diff --git a/stem/client/cell.py b/stem/client/cell.py index 6f107b79..76a75108 100644 --- a/stem/client/cell.py +++ b/stem/client/cell.py @@ -44,7 +44,7 @@ import random import sys
from stem import UNDEFINED -from stem.client import ZERO, Address, Certificate, Size, split +from stem.client import ZERO, Address, Certificate, CloseReason, Size, split from stem.util import _hash_attr, datetime_to_unix
FIXED_PAYLOAD_LEN = 509 @@ -261,10 +261,80 @@ class RelayCell(CircuitCell):
class DestroyCell(CircuitCell): + """ + Closes the given circuit. + + :var stem.client.CloseReason reason: reason the circuit is being closed + :var int reason_int: integer value of our closure reason + """ + NAME = 'DESTROY' VALUE = 4 IS_FIXED_SIZE = True
+ REASON_FOR_INT = { + 0: CloseReason.NONE, + 1: CloseReason.PROTOCOL, + 2: CloseReason.INTERNAL, + 3: CloseReason.REQUESTED, + 4: CloseReason.HIBERNATING, + 5: CloseReason.RESOURCELIMIT, + 6: CloseReason.CONNECTFAILED, + 7: CloseReason.OR_IDENTITY, + 8: CloseReason.OR_CONN_CLOSED, + 9: CloseReason.FINISHED, + 10: CloseReason.TIMEOUT, + 11: CloseReason.DESTROYED, + 12: CloseReason.NOSUCHSERVICE, + } + + INT_FOR_REASON = dict((v, k) for k, v in REASON_FOR_INT.items()) + + def __init__(self, circ_id, reason): + super(DestroyCell, self).__init__(circ_id) + + if isinstance(reason, int): + self.reason = DestroyCell.REASON_FOR_INT.get(reason, CloseReason.UNKNOWN) + self.reason_int = reason + elif reason in CloseReason: + self.reason = reason + self.reason_int = DestroyCell.INT_FOR_REASON.get(reason, -1) + else: + raise ValueError('Invalid closure reason: %s' % reason) + + @classmethod + def pack(cls, link_version, circ_id, reason = CloseReason.NONE): + """ + Provides payload to close the given circuit. + + :param int link_version: link protocol version + :param int circ_id: circuit id + :param stem.client.CloseReason reason: reason to close the circuit + + :returns: **bytes** to close the circuit + """ + + reason = DestroyCell.INT_FOR_REASON.get(reason, reason) + + if not isinstance(reason, int): + raise ValueError('Invalid closure reason: %s' % reason) + + return cls._pack(link_version, Size.CHAR.pack(reason), circ_id) + + @classmethod + def _unpack(cls, content, circ_id, link_version): + content = content.rstrip(ZERO) + + if not content: + content = ZERO + elif len(content) > 1: + raise ValueError('Circuit closure reason should be a single byte, but was %i' % len(content)) + + return DestroyCell(circ_id, Size.CHAR.unpack(content)) + + def __hash__(self): + return _hash_attr(self, 'circ_id', 'reason_int') +
class CreateFastCell(CircuitCell): """ @@ -309,7 +379,7 @@ class CreateFastCell(CircuitCell): return CreateFastCell(circ_id, content)
def __hash__(self): - return _hash_attr(self, 'key_material') + return _hash_attr(self, 'circ_id', 'key_material')
class CreatedFastCell(CircuitCell): @@ -362,7 +432,7 @@ class CreatedFastCell(CircuitCell): return CreatedFastCell(circ_id, content[:HASH_LEN], content[HASH_LEN:])
def __hash__(self): - return _hash_attr(self, 'derivative_key', 'key_material') + return _hash_attr(self, 'circ_id', 'derivative_key', 'key_material')
class VersionsCell(Cell): diff --git a/test/unit/client/cell.py b/test/unit/client/cell.py index 577114c3..5b8db2bb 100644 --- a/test/unit/client/cell.py +++ b/test/unit/client/cell.py @@ -6,13 +6,14 @@ import datetime import os import unittest
-from stem.client import ZERO, AddrType, CertType, Address, Certificate +from stem.client import ZERO, AddrType, CertType, CloseReason, Address, Certificate from test.unit.client import test_data
from stem.client.cell import ( FIXED_PAYLOAD_LEN, Cell, PaddingCell, + DestroyCell, CreateFastCell, CreatedFastCell, VersionsCell, @@ -29,6 +30,11 @@ PADDING_CELLS = { '\x00\x00\x00' + RANDOM_PAYLOAD: RANDOM_PAYLOAD, }
+DESTROY_CELLS = { + '\x80\x00\x00\x00\x04\x00' + ZERO * 508: (2147483648, CloseReason.NONE, 0), + '\x80\x00\x00\x00\x04\x03' + ZERO * 508: (2147483648, CloseReason.REQUESTED, 3), +} + CREATE_FAST_CELLS = { ('\x80\x00\x00\x00\x05\x92O\x0c\xcb\xa8\xac\xfb\xc9\x7f\xd0\rz\x1a\x03u\x91\xceas\xce' + ZERO * 489): (2147483648, '\x92O\x0c\xcb\xa8\xac\xfb\xc9\x7f\xd0\rz\x1a\x03u\x91\xceas\xce'), } @@ -128,6 +134,18 @@ class TestCell(unittest.TestCase): self.assertEqual(cell_bytes, PaddingCell.pack(2, payload)) self.assertEqual(payload, Cell.unpack(cell_bytes, 2)[0].payload)
+ def test_destroy_packing(self): + for cell_bytes, (circ_id, reason, reason_int) in DESTROY_CELLS.items(): + self.assertEqual(cell_bytes, DestroyCell.pack(5, circ_id, reason)) + self.assertEqual(cell_bytes, DestroyCell.pack(5, circ_id, reason_int)) + + cell = Cell.unpack(cell_bytes, 5)[0] + self.assertEqual(circ_id, cell.circ_id) + self.assertEqual(reason, cell.reason) + self.assertEqual(reason_int, cell.reason_int) + + self.assertRaisesRegexp(ValueError, 'Circuit closure reason should be a single byte, but was 2', Cell.unpack, '\x80\x00\x00\x00\x04\x01\x01' + ZERO * 507, 5) + def test_create_fast(self): for cell_bytes, (circ_id, key_material) in CREATE_FAST_CELLS.items(): self.assertEqual(cell_bytes, CreateFastCell.pack(5, circ_id, key_material))
tor-commits@lists.torproject.org