# Copyright 2014, 2015, Nik Kinkel and David Johnston
# See LICENSE for licensing information
import struct
import oppy.cell.definitions as DEF
from oppy.cell.exceptions import BadRelayCellHeader, BadPayloadData
from oppy.cell.fixedlen import FixedLenCell
RELAY_HEADER_LEN = 11
[docs]class RelayCell(FixedLenCell):
'''A container class for representing relay cells.
.. note:: All Relay cells are fixed-length (length determined by
Link Protocol version in use).
'''
rheader = None
rpayload = None
_subclass_map = None
def __init__(self, header, rheader=None, rpayload=''):
'''
:param :class:`~cell.fixedlen.FixedLenCell.Header` header:
Initialized fixed-length header to use with this RelayCell.
:param :class:`~cell.relay.RelayCell.RelayHeader` rheader:
Initialized RelayCell header to use.
:param str rpayload: this cell's relay payload
'''
self.header = header
self.rheader = rheader
self.rpayload = rpayload
[docs] def getBytes(self, trimmed=False):
'''Construct and return the byte string represented by this cell.
:param bool trimmed: If **True**, return the non-padded payload.
Otherwise pad the payload with null bytes to the fixed-size
length in use according to the Link Protocol version.
:returns: **str** byte string represented by this cell
'''
ret = self.header.getBytes() + self.rheader.getBytes() + self.rpayload
return FixedLenCell.padCellBytes(ret, self.header.link_version)
def _parsePayload(self, data):
self._parseRelayHeader(data)
self._parseRelayPayload(data)
def _parseRelayHeader(self, data):
'''Parse *data* and extract this cell's relay header values.
:param str data: data string to parse and extract values from
'''
start, end = self.relayHeaderRange()
rheader = struct.unpack(RelayCell.RelayHeader.FORMAT, data[start:end])
r = RelayCell.RelayHeader(*rheader)
if r.rpayload_len > DEF.MAX_RPAYLOAD_LEN:
msg = 'rpayload_len {} found, but max rpayload_len is {}'
msg = msg.format(r.rpayload_len, DEF.MAX_RPAYLOAD_LEN)
raise BadRelayCellHeader(msg)
if r.cmd not in DEF.RELAY_CMD_IDS:
msg = 'Unrecognized relay cmd {}'.format(r.cmd)
raise BadRelayCellHeader(msg)
self.rheader = r
def _parseRelayPayload(self, data):
'''Parse *data* and extract this cell's relay payload.
:param str data: data string to parse and extract values from
'''
start, end = self.relayPayloadRange()
self.rpayload = data[start:end]
[docs] def relayPayloadRange(self):
'''Return a two-tuple indicating the (start,end) indices of
this cell's relay header.
:returns: **tuple, int** (start, end) indices of this cell's
relay payload
'''
_, start = self.relayHeaderRange()
end = start + self.rheader.rpayload_len
return start, end
@classmethod
def _getSubclass(cls, header, data):
'''Uses *header* to interpret the given cell data. A cell
type which will be appropriate for encapsulating/representing this cell
data is then selected and returned.
:type str header: RelayCell.RelayHeader
:returns: Concrete subclass of RelayCell
'''
if RelayCell._subclass_map is None:
RelayCell._initSubclassMap()
cmd = RelayCell._extractRelayCmd(header, data)
return RelayCell._subclass_map[cmd]
@staticmethod
def _initSubclassMap():
RelayCell._subclass_map = {
DEF.RELAY_BEGIN_CMD : RelayBeginCell,
DEF.RELAY_DATA_CMD : RelayDataCell,
DEF.RELAY_END_CMD : RelayEndCell,
DEF.RELAY_CONNECTED_CMD : RelayConnectedCell,
DEF.RELAY_SENDME_CMD : RelaySendMeCell,
DEF.RELAY_EXTEND_CMD : RelayExtendCell,
DEF.RELAY_EXTENDED_CMD : RelayExtendedCell,
DEF.RELAY_TRUNCATE_CMD : RelayTruncateCell,
DEF.RELAY_TRUNCATED_CMD : RelayTruncatedCell,
DEF.RELAY_DROP_CMD : RelayDropCell,
DEF.RELAY_RESOLVE_CMD : RelayResolveCell,
DEF.RELAY_RESOLVED_CMD : RelayResolvedCell,
DEF.RELAY_BEGIN_DIR_CMD : RelayBeginDirCell,
DEF.RELAY_EXTEND2_CMD : RelayExtend2Cell,
DEF.RELAY_EXTENDED2_CMD : RelayExtended2Cell
}
@staticmethod
def _extractRecognized(header, data):
'''Use *header* to interpret cell data; extract and return
*recognized* field value.
:param :class:`~cell.relay.RelayCell.RelayHeader` header:
relay header in use
:param str data: data string to extract command from
:returns: **str** recongnized command
'''
circ_len = 2 if header.link_version <= 3 else 4
start = circ_len + 1 + 1
end = start + 2
return data[start:end]
@staticmethod
def _extractRelayCmd(header, data):
'''Use *header* to interpret the given cell data; extract and
return *relay_cmd* field value.
:param :class:`cell.relay.RelayCell.RelayHeader` header:
relay cell header to use
:param str data: data string to extract values from
:returns: **int** relay_cmd
'''
circ_len = 2 if header.link_version <= 3 else 4
rh_start = circ_len + 1
(rcmd,) = struct.unpack("!B", data[rh_start:rh_start + 1])
return rcmd
def __repr__(self):
fmt = type(self).__name__ + "(header={}, rheader={}, rpayload={})"
return fmt.format(repr(self.header),
repr(self.rheader),
repr(self.rpayload))
[docs]class RelayBeginDirCell(RelayCell):
'''.. note:: Not Implemented'''
def __init__(self, header):
raise NotImplementedError("Can't make RelayBeginDirCell yet.")
[docs]class RelayBeginCell(RelayCell):
'''.. note:: tor-spec, Section 6.2'''
def __init__(self, header, rheader=None, addr=None, flags=None):
'''
:param :class:`~cell.fixedlen.FixedLenCell.Header` header:
fixed-length header to use in this cell
:param :class:`~cell.relay.RelayCell.RelayHeader` rheader:
relay header to use in this cell
:param str addr: address to begin a connection to (see tor-spec for
format)
:param list, int flags: big-endian integer(s) with bits set to indicate
addr flags, see tor-spec for details.
'''
self.header = header
self.rheader = rheader
self.addr = addr
self.flags = flags
@staticmethod
[docs] def make(circ_id, stream_id, request, flags=None, link_version=3):
'''Build and return a RelayBegin cell, filling in default values
when possible.
Automatically create a default FixedLenCell.Header and
RelayCell.RelayHeader.
:param int circ_id: Circuit ID to use in this cell
:param int stream_id: Stream ID to use in this cell
:param oppy.util.ExitRequest request: destination this
cell will request a connection to
:param list, int flags: flags to use for this connection
:param int link_version: Link Protocol version in use on this
connection
:returns: :class:`~cell.relay.RelayBeginCell`
'''
addr = str(request)
if flags is None:
flags = [DEF.BEGIN_FLAG_IPv6_OK]
f = 0
for flag in flags:
if flag not in DEF.RELAY_BEGIN_FLAGS:
msg = 'Unrecognized Relay Begin flag: {}'.format(flag)
raise BadPayloadData(msg)
f |= (1 << (flag - 1))
flags = struct.pack('!I', f)
h = FixedLenCell.Header(circ_id=circ_id,
cmd=DEF.RELAY_CMD,
link_version=link_version)
r = RelayCell.RelayHeader(cmd=DEF.RELAY_BEGIN_CMD,
recognized=DEF.RECOGNIZED,
stream_id=stream_id,
digest=DEF.EMPTY_DIGEST,
rpayload_len=len(addr + flags))
return RelayBeginCell(h, rheader=r, addr=addr, flags=flags)
[docs] def getBytes(self, trimmed=False):
'''Build and return the raw byte string this cell represents.
:param bool trimmed: If **True**, do not pad this cell's payload.
:returns: **str** raw byte string represented by this cell
'''
ret = self.header.getBytes() + self.rheader.getBytes()
ret += self.addr
ret += self.flags
if trimmed is True:
return ret
else:
return FixedLenCell.padCellBytes(ret, self.header.link_version)
def _parseRelayPayload(self, data):
# Not yet implemented because of low-priority (OP does not receive
# relay begin cells). In fact, we must immediately tear down a circuit
# if we receive a RELAY_BEGIN cell since it's a forward cell.
raise NotImplementedError()
def __repr__(self):
fmt = "RelayBeginCell({}, rheader={}, addr={}, flags={})"
return fmt.format(repr(self.header), repr(self.rheader),
repr(self.addr), repr(self.flags))
ZERO_ADDR_STR = '\x00\x00\x00\x00'
ADDR_TYPE_LEN = 1
TTL_LEN = 4
[docs]class RelayConnectedCell(RelayCell):
'''.. note:: tor-spec, Section 6.2'''
def __init__(self, header, rheader=None, addr_type=None, addr=None,
ttl=None):
'''
:param :class:`~oppy.cell.fixedlen.FixedLenCell.Header` header:
fixed-length header to use in this cell
:param :class:`~oppy.cell.relay.RelayCell.RelayHeader` rheader:
relay header to use in this cell
:param str addr_type: type of IP address sent (only set if IPv6)
:param str addr: address a connection has been made to
:param str ttl: number of seconds the address can be cached
'''
self.header = header
self.rheader = rheader
self.addr_type = addr_type
self.addr = addr
self.ttl = ttl
[docs] def getBytes(self, trimmed=False):
'''Build and return the raw byte string this cell represents.
:param bool trimmed: if **True**, do not pad payload bytes
:returns: **str** raw bytes this cell represents
'''
ret = self.header.getBytes() + self.rheader.getBytes()
ret += self.addr + self.addr_type + self.ttl
if trimmed is True:
return ret
else:
return FixedLenCell.padCellBytes(ret, self.header.link_version)
def _parseRelayPayload(self, data):
'''Parse the string *data* and extract cell fields.
:param str data: string to parse
'''
start, _ = self.relayPayloadRange()
offset = start
self.addr = data[offset:offset + DEF.IPv4_ADDR_LEN]
offset += DEF.IPv4_ADDR_LEN
# ZERO_ADDR_STR indicates we have an IPv6 address
if self.addr == ZERO_ADDR_STR:
self.addr_type = data[offset:offset + ADDR_TYPE_LEN]
offset += ADDR_TYPE_LEN
self.addr = data[offset:offset + DEF.IPv6_ADDR_LEN]
offset += DEF.IPv6_ADDR_LEN
else:
self.addr_type = ''
self.ttl = data[offset:offset + TTL_LEN]
def __repr__(self):
fmt = '{}, rheader={}, addr_type={}, addr={}, ttl={}'
fmt = 'RelayConnectedCell=({})'.format(fmt)
return fmt.format(repr(self.header), repr(self.rheader),
repr(self.addr_type), repr(self.addr),
repr(self.ttl))
[docs]class RelayDataCell(RelayCell):
'''.. note:: tor-spec, Section 6.2
.. note: RelayDataCell's don't have any fields beyond the header
and relay header, so the payload is just treated as a blob.
'''
@staticmethod
[docs] def make(circ_id, stream_id, rpayload, link_version=3):
'''Construct and return a RelayData cell, using default values
where possible.
Create a FixedLenCell.Header and a RelayCell.RelayHeader.
:param int circ_id: Circuit ID to use in this cell
:param int stream_id: Stream ID to use in this cell
:param str rpayload: data to use as the relay payload
:param int link_version: Link Protocol version in use
'''
if len(rpayload) > DEF.MAX_RPAYLOAD_LEN:
raise BadPayloadData()
h = FixedLenCell.Header(circ_id=circ_id,
cmd=DEF.RELAY_CMD,
link_version=link_version)
r = RelayCell.RelayHeader(cmd=DEF.RELAY_DATA_CMD,
recognized=DEF.RECOGNIZED,
stream_id=stream_id,
digest=DEF.EMPTY_DIGEST,
rpayload_len=len(rpayload))
return RelayDataCell(h, rheader=r, rpayload=rpayload)
[docs]class RelayDropCell(RelayCell):
'''.. note:: tor-spec, Section 6.2
.. note: RelayDropCell is a long-range dummy cell and they are immediately
dropped upon receipt.
'''
pass
REASON_SIZE = 1
[docs]class RelayEndCell(RelayCell):
'''.. note:: tor-spec, Section 6.3'''
def __init__(self, header, rheader=None, reason=None, reason_data=None):
'''
:param :class:`~oppy.cell.fixedlen.FixedLenCell.Header` header:
fixed-length header to use in this cell
:param :class:`~oppy.cell.relay.RelayCell.RelayHeader` rheader:
relay header to use in this cell
:param int reason: Single byte that describes the reason this stream
was closed
:param str reason_data: with REASON_EXITPOLICY, this optional field
may be filled in
'''
self.header = header
self.rheader = rheader
self.reason = reason
self.reason_data = reason_data
@staticmethod
[docs] def make(circ_id, stream_id, reason=DEF.REASON_DONE, reason_data='',
link_version=3):
'''Construct and return a RelayEnd cell, using default values where
possible.
Create a FixedLenCell.Header and a RelayCell.RelayHeader.
:param int circ_id: Circuit ID to use in this cell
:param int stream_id: Stream ID to use in this cell
:param int reason: Single byte that describes the reason this stream
was closed
:param str reason_data: with REASON_EXITPOLICY, this optional field
may be filled in
:returns: :class:`~oppy.cell.relay.RelayEndCell`
'''
h = FixedLenCell.Header(circ_id=circ_id,
cmd=DEF.RELAY_CMD,
link_version=link_version)
r = RelayCell.RelayHeader(cmd=DEF.RELAY_END_CMD,
recognized=DEF.RECOGNIZED,
stream_id=stream_id,
digest=DEF.EMPTY_DIGEST,
rpayload_len=REASON_SIZE + len(reason_data))
return RelayEndCell(h, rheader=r, reason=reason,
reason_data=reason_data)
[docs] def getBytes(self, trimmed=False):
'''Build and return the raw byte string this cell represents.
:param bool trimmed: if **True**, do not pad payload bytes
:returns: **str** raw byte string this cell represents
'''
ret = self.header.getBytes() + self.rheader.getBytes()
ret += struct.pack('!B', self.reason)
ret += self.reason_data
if trimmed is True:
return ret
else:
return FixedLenCell.padCellBytes(ret, self.header.link_version)
def _parseRelayPayload(self, data):
'''Parse the string *data* and extract RelayEndCell payload values.
Fill in this cell's fields with values.
:param str data: data string to parse
'''
start, end = self.relayPayloadRange()
offset = start
self.reason = struct.unpack('!B', data[offset:offset + REASON_SIZE])[0]
offset += REASON_SIZE
if self.reason == DEF.REASON_EXITPOLICY:
self.reason_data = data[offset:end]
else:
self.reason_data = ''
def __repr__(self):
fmt = '{}, rheader={}, reason={}, reason_data={}'
fmt = 'RelayBeginCell({})'.format(fmt)
return fmt.format(repr(self.header), repr(self.rheader),
repr(self.reason), repr(self.reason_data))
NSPEC_LEN = 1
LSTYPE_LEN = 1
LSLEN_LEN = 1
HTYPE_LEN = 2
HLEN_LEN = 2
[docs]class RelayExtend2Cell(RelayCell):
'''.. note:: tor-spec, Section 5.1.2'''
def __init__(self, header, rheader=None, nspec=None, lspecs=None,
htype=None, hlen=None, hdata=None):
'''
:param :class:`~oppy.cell.fixedlen.FixedLenCell.Header` header:
fixed-length header to use in this cell
:param :class:`~oppy.cell.relay.RelayCell.RelayHeader` rheader:
relay header to use in this cell
:param int nspec: the number of Link Specifiers in this cell
:param list, :class:`~oppy.cell.util.LinkSpecifier` lspecs: list of
Link Specifiers to include in this cell
:param int htype: Type of handshake in use
:param int hlen: length of the handshake data field
:param str hdata: handshake data (*onion skin*)
'''
self.header = header
self.rheader = rheader
self.nspec = nspec
self.lspecs = lspecs
self.htype = htype
self.hlen = hlen
self.hdata = hdata
@staticmethod
[docs] def make(circ_id, stream_id=0, nspec=None, lspecs=None,
htype=DEF.NTOR_HTYPE, hlen=DEF.NTOR_HLEN, hdata='',
link_version=3, early=True):
'''Construct and return a RelayExtend2Cell, using default values where
possible.
Create a FixedLenCell.Header and a RelayCell.RelayHeader for use
in this cell.
.. note:: oppy currently only supports the NTor handshake and will
reject unrecognized htype's and hlen's.
:param int circ_id: Circuit ID to use in this cell
:param int stream_id: Stream ID to use in this cell (should be zero)
:param int nspec: the number of Link Specifiers in this cell
:param list, oppy.cell.util.LinkSpecifier lspecs: list of
Link Specifiers to include in this cell
:param int htype: Type of handshake in use
:param int hlen: length of the handshake data field
:param str hdata: handshake data (*onion skin*)
:param int link_version: Link Protocol version in use
:param bool early: if **True**, use a RELAY_EARLY cmd instead of
RELAY cmd
:returns: :class:`~oppy.cell.relay.RelayExtend2Cell`
'''
if lspecs is None:
lspecs = []
if stream_id != 0:
msg = "EXTEND2 cells should use stream_id=0."
raise BadPayloadData(msg)
if htype != DEF.NTOR_HTYPE:
msg = 'htype was {}, but we currently only support '
msg += '{} (NTor) handshakes.'
msg = msg.format(htype, DEF.NTOR_HTYPE)
raise BadPayloadData(msg)
if hlen != DEF.NTOR_HLEN:
msg = 'htype was NTor and hlen was {} but expecting {}'
msg = msg.format(hlen, DEF.NTOR_HLEN)
raise BadPayloadData(msg)
if hlen != len(hdata):
msg = 'hlen {} neq len(hdata) {}'.format(hlen, len(hdata))
raise BadPayloadData(msg)
cmd = DEF.RELAY_EARLY_CMD if early is True else DEF.RELAY_CMD
h = FixedLenCell.Header(circ_id=circ_id,
cmd=cmd,
link_version=link_version)
if nspec is None:
nspec = len(lspecs)
if len(lspecs) == 0:
msg = 'No Link Specifiers found. At least 1 Link Specifier '
msg += 'is required.'
raise BadPayloadData(msg)
if nspec != len(lspecs):
msg = 'Expected {} LinkSpecifiers but found {}'
msg = msg.format(nspec, len(lspecs))
raise BadPayloadData(msg)
rpayload_len = NSPEC_LEN
for lspec in lspecs:
rpayload_len += len(lspec)
rpayload_len += HTYPE_LEN + HLEN_LEN + hlen
r = RelayCell.RelayHeader(cmd=DEF.RELAY_EXTEND2_CMD,
recognized=DEF.RECOGNIZED,
stream_id=stream_id,
digest=DEF.EMPTY_DIGEST,
rpayload_len=rpayload_len)
return RelayExtend2Cell(h, rheader=r, nspec=nspec, lspecs=lspecs,
htype=htype, hlen=hlen, hdata=hdata)
[docs] def getBytes(self, trimmed=False):
'''Build and return the raw byte string this cell represents.
:param bool trimmed: if **True**, do not pad payload bytes
:returns: **str** raw byte string this cell represents
'''
ret = self.header.getBytes() + self.rheader.getBytes()
ret += struct.pack('!B', self.nspec)
for lspec in self.lspecs:
ret += lspec.getBytes()
ret += struct.pack('!H', self.htype)
ret += struct.pack('!H', self.hlen)
ret += self.hdata
if trimmed is True:
return ret
else:
return FixedLenCell.padCellBytes(ret, self.header.link_version)
def _parseRelayPayload(self, data):
'''Parse the string *data* and extract Extend2 fields.
Fill in this cell's values.
:param str data: data string to parse
'''
start, _ = self.relayPayloadRange()
offset = start
self.nspec = struct.unpack('!B', data[offset:offset + NSPEC_LEN])[0]
offset += NSPEC_LEN
# Find the start and end of the lspecs slice.
lspecs_start = offset
for idx in xrange(self.nspec):
offset += LSTYPE_LEN # skipped lstype
lslen = struct.unpack('!B', data[offset:offset + LSLEN_LEN])[0]
offset += LSLEN_LEN # consumed lslen
offset += lslen # skipped lspec
lspecs_end = offset
self.lspecs = data[lspecs_start:lspecs_end]
# Parse each of the handshake fields.
self.htype = struct.unpack('!H', data[offset:offset + HTYPE_LEN])[0]
offset += HTYPE_LEN
self.hlen = struct.unpack('!H', data[offset:offset + HLEN_LEN])[0]
offset += HLEN_LEN
self.hdata = data[offset:offset + self.hlen]
def __repr__(self):
fmt = '{}, rheader={}, nspec={}, lspecs={}, htype={}, hlen={}, '
fmt += 'hdata={}'
fmt = 'RelayExtend2Cell({})'.format(fmt)
return fmt.format(repr(self.header), repr(self.rheader),
repr(self.nspec), repr(self.lspecs),
repr(self.htype), repr(self.hlen),
repr(self.hdata))
[docs]class RelayExtended2Cell(RelayCell):
'''.. note:: tor-spec, Section 5.1, 5.1.2'''
def __init__(self, header, rheader=None, hlen=None, hdata=None):
'''
:param :class:`~oppy.cell.fixedlen.FixedLenCell.Header` header:
header to use in this cell
:param :class:`~oppy.cell.relay.RelayCell.RelayHeader` rheader:
relay header to use in this cell
:param int hlen: length of the handshake data field
:param str hdata: handshake data (onion skin)
'''
self.header = header
self.rheader = rheader
self.hlen = hlen
self.hdata = hdata
[docs] def getBytes(self, trimmed=False):
'''Build and return the raw byte string this cell represents.
:param bool trimmed: if **True**, do not pad payload bytes
:returns: **str** raw byte string this cell represents
'''
ret = self.header.getBytes() + self.rheader.getBytes()
ret += struct.pack('!H', self.hlen)
ret += self.hdata
if trimmed is True:
return ret
else:
return FixedLenCell.padCellBytes(ret, self.header.link_version)
def _parseRelayPayload(self, data):
'''Parse the string *data* and extract Extended2 fields.
Fill in this cell's attributes.
:param str data: data string to parse
'''
start, _ = self.relayPayloadRange()
offset = start
self.hlen = struct.unpack('!H', data[offset:offset + HLEN_LEN])[0]
offset += HLEN_LEN
try:
self.hdata = data[offset:offset + self.hlen]
except IndexError:
raise BadPayloadData('Not enough hdata bytes.')
def __repr__(self):
fmt = 'RelayExtended2Cell({}, rheader={}, hlen={}, hdata={})'
return fmt.format(repr(self.header), repr(self.rheader),
repr(self.hlen), repr(self.hdata))
[docs]class RelayExtendedCell(RelayCell):
'''.. note:: Not Implemented'''
def __init__(self, header):
raise NotImplementedError("Can't make RelayExtendedCell yet.")
[docs]class RelayExtendCell(RelayCell):
'''.. note:: Not Implemented'''
def __init__(self, header):
raise NotImplementedError("Can't make RelayExtendCell yet.")
[docs]class RelayResolvedCell(RelayCell):
'''.. note:: Not Implemented'''
def __init__(self, header):
raise NotImplementedError("Can't make RelayResolvedCell yet.")
[docs]class RelayResolveCell(RelayCell):
'''.. note:: Not Implemented'''
def __init__(self, header):
raise NotImplementedError("Can't make RelayResolveCell yet.")
[docs]class RelaySendMeCell(RelayCell):
'''.. note:: tor-spec, Section 7.3, 7.4
.. note: There are no fields in a SendMe cell's payload, so
we just use parent class fields and methods.
'''
# convenience function to simplify construction
@staticmethod
[docs] def make(circ_id, stream_id=0, link_version=3):
'''Construct and return a RelaySendMeCell, using default values
where possible.
Create a FixedLenCell.Header and a RelayCell.RelayHeader for use
in this cell.
:param int circ_id: Circuit ID to use in this cell
:param int stream_id: Stream ID to use in this cell. A Stream ID of
zero indicates a 'circuit-level' SendMe cell, and a non-zero
Stream ID indicates a 'stream-level' SendMe cell.
:param int link_version: Link Protocol version in use
:returns: :class:`~oppy.cell.relay.RelaySendMeCell`
'''
h = FixedLenCell.Header(circ_id=circ_id,
cmd=DEF.RELAY_CMD,
link_version=link_version)
r = RelayCell.RelayHeader(cmd=DEF.RELAY_SENDME_CMD,
recognized=DEF.RECOGNIZED,
stream_id=stream_id,
digest=DEF.EMPTY_DIGEST,
rpayload_len=0)
return RelaySendMeCell(h, r)
[docs]class RelayTruncatedCell(RelayCell):
'''.. note:: tor-spec, Section 5.4'''
def __init__(self, header, rheader=None, reason=None):
'''
:param :class:`~oppy.cell.fixedlen.FixedLenCell.Header` header:
header to use in this cell
:param :class:`~oppy.cell.relay.RelayCell.RelayHeader` rheader:
relay header to use in this cell
:param int reason: A single byte describing the reason this
RelayTruncatedCell was sent.
'''
self.header = header
self.rheader = rheader
self.reason = reason
[docs] def getBytes(self, trimmed=False):
'''Build and return the raw byte string this cell represents.
:param bool trimmed: if **True**, do not pad payload bytes
:returns: **str** raw bytes this cell represents
'''
ret = self.header.getBytes() + self.rheader.getBytes()
ret += struct.pack('!B', self.reason)
if trimmed is True:
return ret
else:
return FixedLenCell.padCellBytes(ret, self.header.link_version)
def _parseRelayPayload(self, data):
'''Parse the string *data* and extract RelayTruncatedCell fields.
Fill in this cell's attributes.
:param str data: data string to parse
'''
start, _ = self.relayPayloadRange()
self.reason = struct.unpack('!B', data[start:start + REASON_SIZE])[0]
def __repr__(self):
fmt = 'RelayTruncatedCell=({}, rheader={}, reason={})'
return fmt.format(repr(self.header), repr(self.rheader),
repr(self.reason))
[docs]class RelayTruncateCell(RelayCell):
'''.. note:: Not Implemented'''
def __init__(self, header, rheader=None, reason=None):
raise NotImplementedError("Can't make RelayTruncateCell yet.")