def test_unpack_for_new_link(self): expected_certs = ( (CertType.LINK, 1, b'0\x82\x02F0\x82\x01\xaf'), (CertType.IDENTITY, 2, b'0\x82\x01\xc90\x82\x012'), (CertType.UNKNOWN, 4, b'\x01\x04\x00\x06m\x1f'), (CertType.UNKNOWN, 5, b'\x01\x05\x00\x06m\n\x01'), (CertType.UNKNOWN, 7, b'\x1a\xa5\xb3\xbd\x88\xb1C'), ) content = test_data('new_link_cells') version_cell, content = Cell.pop(content, 2) self.assertEqual(VersionsCell([3, 4, 5]), version_cell) certs_cell, content = Cell.pop(content, 2) self.assertEqual(CertsCell, type(certs_cell)) self.assertEqual(len(expected_certs), len(certs_cell.certificates)) for i, (cert_type, cert_type_int, cert_prefix) in enumerate(expected_certs): self.assertEqual(cert_type, certs_cell.certificates[i].type) self.assertEqual(cert_type_int, certs_cell.certificates[i].type_int) self.assertTrue(certs_cell.certificates[i].value.startswith(cert_prefix)) auth_challenge_cell, content = Cell.pop(content, 2) self.assertEqual(AuthChallengeCell([1, 3], b'\x89Y\t\x99\xb2\x1e\xd9*V\xb6\x1bn\n\x05\xd8/\xe3QH\x85\x13Z\x17\xfc\x1c\x00{\xa9\xae\x83^K'), auth_challenge_cell) netinfo_cell, content = Cell.pop(content, 2) self.assertEqual(NetinfoCell, type(netinfo_cell)) self.assertEqual(datetime.datetime(2018, 1, 14, 1, 46, 56), netinfo_cell.timestamp) self.assertEqual(Address('127.0.0.1'), netinfo_cell.receiver_address) self.assertEqual([Address('97.113.15.2')], netinfo_cell.sender_addresses) self.assertEqual(ZERO * 492, netinfo_cell.unused) self.assertEqual(b'', content) # check that we've consumed all of the bytes
def _recv(self, raw=False): """ Reads the next cell from our ORPort. If none is present this blocks until one is available. :param bool raw: provides bytes rather than parsing as a cell if **True** :returns: next :class:`~stem.client.cell.Cell` """ with self._orport_lock: # cells begin with [circ_id][cell_type][...] circ_id_size = self.link_protocol.circ_id_size.size while len(self._orport_buffer) < (circ_id_size + CELL_TYPE_SIZE.size): self._orport_buffer += self._orport.recv( ) # read until we know the cell type cell_type = Cell.by_value( CELL_TYPE_SIZE.pop(self._orport_buffer[circ_id_size:])[0]) if cell_type.IS_FIXED_SIZE: cell_size = circ_id_size + CELL_TYPE_SIZE.size + FIXED_PAYLOAD_LEN else: # variable length, our next field is the payload size while len(self._orport_buffer) < (circ_id_size + CELL_TYPE_SIZE.size + FIXED_PAYLOAD_LEN.size): self._orport_buffer += self._orport.recv( ) # read until we know the cell size payload_len = FIXED_PAYLOAD_LEN.pop( self._orport_buffer[circ_id_size + CELL_TYPE_SIZE.size:])[0] cell_size = circ_id_size + CELL_TYPE_SIZE.size + FIXED_PAYLOAD_LEN.size + payload_len while len(self._orport_buffer) < cell_size: self._orport_buffer += self._orport.recv( ) # read until we have the full cell if raw: content, self._orport_buffer = split(self._orport_buffer, cell_size) return content else: cell, self._orport_buffer = Cell.pop(self._orport_buffer, self.link_protocol) return cell
def test_destroy_cell(self): for cell_bytes, (circ_id, reason, reason_int, unused, link_protocol) in DESTROY_CELLS.items(): if not unused.strip(ZERO): self.assertEqual( cell_bytes, DestroyCell(circ_id, reason).pack(link_protocol)) self.assertEqual( cell_bytes, DestroyCell(circ_id, reason_int).pack(link_protocol)) else: self.assertEqual( cell_bytes, DestroyCell(circ_id, reason, unused=unused).pack(link_protocol)) self.assertEqual( cell_bytes, DestroyCell(circ_id, reason_int, unused=unused).pack(link_protocol)) cell = Cell.pop(cell_bytes, link_protocol)[0] self.assertEqual(circ_id, cell.circ_id) self.assertEqual(reason, cell.reason) self.assertEqual(reason_int, cell.reason_int) self.assertEqual(unused, cell.unused) self.assertEqual(cell_bytes, cell.pack(link_protocol))
def test_created_fast_cell(self): for cell_bytes, (circ_id, key_material, derivative_key, unused, link_protocol) in CREATED_FAST_CELLS.items(): if not unused.strip(ZERO): self.assertEqual( cell_bytes, CreatedFastCell(circ_id, derivative_key, key_material).pack(link_protocol)) else: self.assertEqual( cell_bytes, CreatedFastCell(circ_id, derivative_key, key_material, unused=unused).pack(link_protocol)) cell = Cell.pop(cell_bytes, link_protocol)[0] self.assertEqual(circ_id, cell.circ_id) self.assertEqual(key_material, cell.key_material) self.assertEqual(derivative_key, cell.derivative_key) self.assertEqual(unused, cell.unused) self.assertEqual(cell_bytes, cell.pack(link_protocol)) self.assertRaisesWith(ValueError, 'Key material should be 20 bytes, but was 3', CreateFastCell, 5, 'boo')
def test_vpadding_cell(self): for cell_bytes, (payload, link_protocol) in VPADDING_CELLS.items(): self.assertEqual(cell_bytes, VPaddingCell(payload=payload).pack(link_protocol)) cell = Cell.pop(cell_bytes, link_protocol)[0] self.assertEqual(payload, cell.payload) self.assertEqual(b'', cell.unused) # always empty self.assertEqual(cell_bytes, cell.pack(link_protocol)) empty_constructed_cell = VPaddingCell(size=0) self.assertEqual(VPADDING_CELL_EMPTY_PACKED, empty_constructed_cell.pack(2)) self.assertEqual(b'', empty_constructed_cell.payload) self.assertRaisesWith( ValueError, 'VPaddingCell constructor specified both a size of 5 bytes and payload of 1 bytes', VPaddingCell, 5, '\x02') self.assertRaisesWith(ValueError, 'VPaddingCell size (-15) cannot be negative', VPaddingCell, -15) self.assertRaisesWith( ValueError, 'VPaddingCell constructor must specify payload or size', VPaddingCell)
def test_certs_cell(self): for cell_bytes, (certs, unused, link_protocol) in CERTS_CELLS.items(): if not unused.strip(ZERO): self.assertEqual(cell_bytes, CertsCell(certs).pack(link_protocol)) else: self.assertEqual( cell_bytes, CertsCell(certs, unused=unused).pack(link_protocol)) cell = Cell.pop(cell_bytes, link_protocol)[0] self.assertEqual(certs, cell.certificates) self.assertEqual(unused, cell.unused) self.assertEqual(cell_bytes, cell.pack(link_protocol)) # truncated or missing certificates should error self.assertRaisesWith( ValueError, 'CERTS cell should have a certificate with 3 bytes, but only had 1 remaining', Cell.pop, b'\x00\x00\x81\x00\x05\x01\x01\x00\x03\x08', 2) self.assertRaisesWith( ValueError, 'CERTS cell indicates it should have 2 certificates, but only contained 1', Cell.pop, b'\x00\x00\x81\x00\x05\x02\x01\x00\x01\x08', 2)
def test_auth_challenge_cell(self): for cell_bytes, (challenge, methods, unused, link_protocol) in AUTH_CHALLENGE_CELLS.items(): if not unused.strip(ZERO): self.assertEqual( cell_bytes, AuthChallengeCell(methods, challenge).pack(link_protocol)) else: self.assertEqual( cell_bytes, AuthChallengeCell(methods, challenge, unused=unused).pack(link_protocol)) cell = Cell.pop(cell_bytes, link_protocol)[0] self.assertEqual(challenge, cell.challenge) self.assertEqual(methods, cell.methods) self.assertEqual(unused, cell.unused) self.assertEqual(cell_bytes, cell.pack(link_protocol)) self.assertRaisesWith( ValueError, 'AUTH_CHALLENGE cell should have a payload of 38 bytes, but only had 16', Cell.pop, b'\x00\x00\x82\x00&' + CHALLENGE[:10] + b'\x00\x02\x00\x01\x00\x03', 2) self.assertRaisesWith( ValueError, 'AUTH_CHALLENGE should have 3 methods, but only had 4 bytes for it', Cell.pop, b'\x00\x00\x82\x00&' + CHALLENGE + b'\x00\x03\x00\x01\x00\x03', 2)
def test_padding_cell(self): for cell_bytes, (payload, link_protocol) in PADDING_CELLS.items(): self.assertEqual(cell_bytes, PaddingCell(payload).pack(link_protocol)) cell = Cell.pop(cell_bytes, link_protocol)[0] self.assertEqual(payload, cell.payload) self.assertEqual(b'', cell.unused) # always empty self.assertEqual(cell_bytes, cell.pack(link_protocol))
def test_versions_cell(self): for cell_bytes, (versions, link_protocol) in VERSIONS_CELLS.items(): self.assertEqual(cell_bytes, VersionsCell(versions).pack(link_protocol)) cell = Cell.pop(cell_bytes, link_protocol)[0] self.assertEqual(versions, cell.versions) self.assertEqual(b'', cell.unused) # always empty self.assertEqual(cell_bytes, cell.pack(link_protocol))
def test_by_value(self): cls = Cell.by_value(8) self.assertEqual('NETINFO', cls.NAME) self.assertEqual(8, cls.VALUE) self.assertEqual(True, cls.IS_FIXED_SIZE) self.assertRaises(ValueError, Cell.by_value, 'NOPE') self.assertRaises(ValueError, Cell.by_value, 85) self.assertRaises(ValueError, Cell.by_value, None)
def test_unimplemented_cell_methods(self): cell_instance = Cell() self.assertRaisesWith(NotImplementedError, 'Packing not yet implemented for UNKNOWN cells', cell_instance.pack, 2) self.assertRaisesWith( NotImplementedError, 'Unpacking not yet implemented for UNKNOWN cells', cell_instance._unpack, b'dummy', 0, 2)
def test_unimplemented_cell_methods(self): cell_instance = Cell() self.assertRaisesRegexp( NotImplementedError, re.escape('Packing not yet implemented for UNKNOWN cells'), cell_instance.pack, 2) self.assertRaisesRegexp( NotImplementedError, re.escape('Unpacking not yet implemented for UNKNOWN cells'), cell_instance._unpack, b'dummy', 0, 2)
def test_relay_cell(self): for cell_bytes, (command, command_int, circ_id, stream_id, data, digest) in RELAY_CELLS.items(): self.assertEqual( cell_bytes, RelayCell(circ_id, command, data, digest, stream_id).pack(2)) self.assertEqual( cell_bytes, RelayCell(circ_id, command_int, data, digest, stream_id).pack(2)) cell = Cell.pop(cell_bytes, 2)[0] self.assertEqual(circ_id, cell.circ_id) self.assertEqual(command, cell.command) self.assertEqual(command_int, cell.command_int) self.assertEqual(data, cell.data) self.assertEqual(digest, cell.digest) self.assertEqual(stream_id, cell.stream_id) self.assertEqual(ZERO * (498 - len(cell.data)), cell.unused) digest = hashlib.sha1(b'hi') self.assertEqual( 3257622417, RelayCell(5, 'RELAY_BEGIN_DIR', '', digest, 564346860).digest) self.assertEqual( 3257622417, RelayCell(5, 'RELAY_BEGIN_DIR', '', digest.digest(), 564346860).digest) self.assertEqual( 3257622417, RelayCell(5, 'RELAY_BEGIN_DIR', '', 3257622417, 564346860).digest) self.assertRaisesRegexp( ValueError, 'RELAY cell digest must be a hash, string, or int but was a list', RelayCell, 5, 'RELAY_BEGIN_DIR', '', [], 564346860) self.assertRaisesRegexp( ValueError, "Invalid enumeration 'NO_SUCH_COMMAND', options are RELAY_BEGIN, RELAY_DATA", RelayCell, 5, 'NO_SUCH_COMMAND', '', 5, 564346860) mismatched_data_length_bytes = b''.join(( b'\x00\x01', # circ ID b'\x03', # command b'\x02', # relay command b'\x00\x00', # 'recognized' b'\x00\x01', # stream ID b'\x15:m\xe0', # digest b'\xFF\xFF', # data len (65535, clearly invalid) ZERO * 498, # data )) expected_message = 'RELAY cell said it had 65535 bytes of data, but only had 498' self.assertRaisesRegexp(ValueError, '^%s$' % re.escape(expected_message), Cell.pop, mismatched_data_length_bytes, 2)
def test_create_fast_cell(self): for cell_bytes, (circ_id, key_material) in CREATE_FAST_CELLS.items(): self.assertEqual(cell_bytes, CreateFastCell(circ_id, key_material).pack(5)) cell = Cell.pop(cell_bytes, 5)[0] self.assertEqual(circ_id, cell.circ_id) self.assertEqual(key_material, cell.key_material) self.assertEqual(ZERO * 489, cell.unused) self.assertRaisesRegexp(ValueError, 'Key material should be 20 bytes, but was 3', CreateFastCell, 5, 'boo')
def test_netinfo_cell(self): for cell_bytes, (timestamp, receiver_address, sender_addresses) in NETINFO_CELLS.items(): self.assertEqual( cell_bytes, NetinfoCell(receiver_address, sender_addresses, timestamp).pack(2)) cell = Cell.pop(cell_bytes, 2)[0] self.assertEqual(timestamp, cell.timestamp) self.assertEqual(receiver_address, cell.receiver_address) self.assertEqual(sender_addresses, cell.sender_addresses) self.assertEqual(ZERO * 492, cell.unused)
def test_netinfo_cell(self): for cell_bytes, (timestamp, receiver_address, sender_addresses, unused, link_protocol) in NETINFO_CELLS.items(): if not unused.strip(ZERO): self.assertEqual(cell_bytes, NetinfoCell(receiver_address, sender_addresses, timestamp).pack(link_protocol)) else: self.assertEqual(cell_bytes, NetinfoCell(receiver_address, sender_addresses, timestamp, unused = unused).pack(link_protocol)) cell = Cell.pop(cell_bytes, link_protocol)[0] self.assertEqual(timestamp, cell.timestamp) self.assertEqual(receiver_address, cell.receiver_address) self.assertEqual(sender_addresses, cell.sender_addresses) self.assertEqual(unused, cell.unused) self.assertEqual(cell_bytes, cell.pack(link_protocol))
def test_certs_cell(self): for cell_bytes, certs in CERTS_CELLS.items(): self.assertEqual(cell_bytes, CertsCell(certs).pack(2)) self.assertEqual(certs, Cell.pop(cell_bytes, 2)[0].certificates) # extra bytes after the last certificate should be ignored self.assertEqual( [Certificate(1, '\x08')], Cell.pop(b'\x00\x00\x81\x00\x07\x01\x01\x00\x01\x08\x06\x04', 2)[0].certificates) # ... but truncated or missing certificates should error self.assertRaisesRegexp( ValueError, 'CERTS cell should have a certificate with 3 bytes, but only had 1 remaining', Cell.pop, b'\x00\x00\x81\x00\x05\x01\x01\x00\x03\x08', 2) self.assertRaisesRegexp( ValueError, 'CERTS cell indicates it should have 2 certificates, but only contained 1', Cell.pop, b'\x00\x00\x81\x00\x05\x02\x01\x00\x01\x08', 2)
def test_destroy_cell(self): for cell_bytes, (circ_id, reason, reason_int, unused) in DESTROY_CELLS.items(): # Packed cells always pad with zeros, so if we're testing something with # non-zero padding then skip this check. if not unused.strip(ZERO): self.assertEqual(cell_bytes, DestroyCell(circ_id, reason).pack(5)) self.assertEqual(cell_bytes, DestroyCell(circ_id, reason_int).pack(5)) cell = Cell.pop(cell_bytes, 5)[0] self.assertEqual(circ_id, cell.circ_id) self.assertEqual(reason, cell.reason) self.assertEqual(reason_int, cell.reason_int) self.assertEqual(unused, cell.unused)
def test_auth_challenge_cell(self): for cell_bytes, (challenge, methods) in AUTH_CHALLENGE_CELLS.items(): self.assertEqual(cell_bytes, AuthChallengeCell(methods, challenge).pack(2)) cell = Cell.pop(cell_bytes, 2)[0] self.assertEqual(challenge, cell.challenge) self.assertEqual(methods, cell.methods) self.assertEqual(b'', cell.unused) self.assertRaisesRegexp( ValueError, 'AUTH_CHALLENGE cell should have a payload of 38 bytes, but only had 16', Cell.pop, b'\x00\x00\x82\x00&' + CHALLENGE[:10] + b'\x00\x02\x00\x01\x00\x03', 2) self.assertRaisesRegexp( ValueError, 'AUTH_CHALLENGE should have 3 methods, but only had 4 bytes for it', Cell.pop, b'\x00\x00\x82\x00&' + CHALLENGE + b'\x00\x03\x00\x01\x00\x03', 2)
def test_vpadding_cell(self): for cell_bytes, payload in VPADDING_CELLS.items(): self.assertEqual(cell_bytes, VPaddingCell(payload=payload).pack(2)) self.assertEqual(payload, Cell.pop(cell_bytes, 2)[0].payload) empty_constructed_cell = VPaddingCell(size=0) self.assertEqual(VPADDING_CELL_EMPTY_PACKED, empty_constructed_cell.pack(2)) self.assertEqual(b'', empty_constructed_cell.payload) self.assertRaisesRegexp( ValueError, 'VPaddingCell constructor specified both a size of 5 bytes and payload of 1 bytes', VPaddingCell, 5, '\x02') self.assertRaisesRegexp( ValueError, re.escape('VPaddingCell size (-15) cannot be negative'), VPaddingCell, -15) self.assertRaisesRegexp( ValueError, re.escape('VPaddingCell constructor must specify payload or size'), VPaddingCell)
def test_versions_cell(self): for cell_bytes, (versions, link_protocol) in VERSIONS_CELLS.items(): self.assertEqual(cell_bytes, VersionsCell(versions).pack(link_protocol)) self.assertEqual(versions, Cell.pop(cell_bytes, link_protocol)[0].versions)
def test_padding_cell(self): for cell_bytes, payload in PADDING_CELLS.items(): self.assertEqual(cell_bytes, PaddingCell(payload).pack(2)) self.assertEqual(payload, Cell.pop(cell_bytes, 2)[0].payload)