class StatusReport(CborArray): ''' The Status Report of BPbis Section 6.1.1. ''' @enum.unique class ReasonCode(enum.IntEnum): NO_INFO = 0 # "No additional information" LIFETIME_EXP = 1 # "Lifetime expired" FWD_UNI = 2 # "Forwarded over unidirectional link" TX_CANCEL = 3 # "Transmission canceled" DEPLETE_STORAGE = 4 # "Depleted storage" DEST_EID_UNINTEL = 5 # "Destination endpoint ID unintelligible" NO_ROUTE = 6 # "No known route to destination from here" NO_NEXT_CONTACT = 7 # "No timely contact with next node on route" BLOCK_UNINTEL = 8 # "Block unintelligible" HOP_LIMIT_EXC = 9 # "Hop limit exceeded" TRAFIC_PAIRED = 10 # "Traffic pared" UNKNOWN_SEC = 13 # "Unknown Security Operation" FAILED_SEC = 15 # "Failed Security Operation" fields_desc = ( PacketField('status', default=StatusInfoArray(), cls=StatusInfoArray), EnumField('reason_code', default=ReasonCode.NO_INFO, enum=ReasonCode), EidField('subj_source'), PacketField('subj_ts', default=Timestamp(), cls=Timestamp), OptionalField(UintField('fragment_offset'), ), OptionalField(UintField('payload_len'), ), )
class AbstractSecurityBlock(CborSequence): ''' Block data from 'draft-ietf-dtn-bpsec-22' Section 3.6. ''' @enum.unique class Flag(enum.IntFlag): ''' Security flags. Flags must be in LSbit-first order. ''' NONE = 0 PARAMETERS_PRESENT = 2**0 fields_desc = ( ArrayWrapField( FieldListField('targets', default=[], fld=UintField('block_num'))), UintField('context_id'), FlagsField('context_flags', default=Flag.NONE, flags=Flag), EidField('source'), ConditionalField( ArrayWrapField( PacketListField('parameters', default=None, cls=TypeValuePair), ), lambda block: block.getfieldval('context_flags') & AbstractSecurityBlock.Flag.PARAMETERS_PRESENT), ArrayWrapField( PacketListField('results', default=[], cls=TargetResultList), ), )
class HopCountBlock(CborArray): ''' Block data from BPbis Section 4.3.3. ''' fields_desc = ( UintField('limit'), UintField('count'), )
class PrimaryBlock(AbstractBlock): ''' The primary block definition ''' @enum.unique class Flag(enum.IntFlag): ''' Bundle flags. ''' NONE = 0 #: bundle deletion status reports are requested. REQ_DELETION_REPORT = 0x040000 #: bundle delivery status reports are requested. REQ_DELIVERY_REPORT = 0x020000 #: bundle forwarding status reports are requested. REQ_FORWARDING_REPORT = 0x010000 #: bundle reception status reports are requested. REQ_RECEPTION_REPORT = 0x004000 #: status time is requested in all status reports. REQ_STATUS_TIME = 0x000040 #: user application acknowledgement is requested. USER_APP_ACK = 0x000020 #: bundle must not be fragmented. NO_FRAGMENT = 0x000004 #: payload is an administrative record. PAYLOAD_ADMIN = 0x000002 #: bundle is a fragment. IS_FRAGMENT = 0x000001 fields_desc = ( UintField('bp_version', default=7), FlagsField('bundle_flags', default=Flag.NONE, flags=Flag), EnumField('crc_type', default=AbstractBlock.CrcType.NONE, enum=AbstractBlock.CrcType), EidField('destination'), EidField('source'), EidField('report_to'), PacketField('create_ts', default=Timestamp(), cls=Timestamp), UintField('lifetime', default=0), ConditionalField( UintField('fragment_offset', default=0), lambda block: block.getfieldval('bundle_flags') & PrimaryBlock.Flag.IS_FRAGMENT ), ConditionalField( UintField('total_app_data_len', default=0), lambda block: block.getfieldval('bundle_flags') & PrimaryBlock.Flag.IS_FRAGMENT ), ConditionalField( BstrField('crc_value'), lambda block: block.getfieldval('crc_type') != 0 ), )
def testAddfield(self): fld = FieldListField('field', [], UintField('field')) s_init = [0, 'hi'] val = [1, 2] s_final = fld.addfield(None, s_init, val) self.assertEqual(s_final, [0, 'hi', 1, 2])
def testGetfield(self): fld = FieldListField('field', [], UintField('field')) s_init = [1, 2] (s_final, val) = fld.getfield(None, s_init) self.assertEqual(s_final, []) self.assertEqual(val, [1, 2])
class TypeValuePair(CborArray): ''' A pattern for an array encoding which contains exactly two values. ''' fields_desc = ( UintField('type_code'), CborField('value'), )
class Timestamp(CborArray): ''' A structured representation of an DTN Timestamp. The timestamp is a two-tuple of (time, sequence number) The creation time portion is automatically converted from a :py:cls:`datetime.datetime` object and text. ''' fields_desc = ( DtnTimeField('dtntime', default=0), UintField('seqno', default=0), )
def testDecode(self): fld = UintField('field') self.assertEqual(fld.m2i(None, 0), 0) self.assertEqual(fld.m2i(None, 10), 10) with self.assertRaises(ValueError): fld.m2i(None, 'hi')
def testAddfield(self): fld = OptionalField(UintField('field')) s_init = [0, 'hi'] val = 10 s_final = fld.addfield(None, s_init, val) self.assertEqual(s_final, [0, 'hi', 10]) s_init = [0, 'hi'] val = False s_final = fld.addfield(None, s_init, val) self.assertEqual(s_final, [0, 'hi', False]) s_init = [0, 'hi'] val = None s_final = fld.addfield(None, s_init, val) self.assertEqual(s_final, [0, 'hi'])
def testGetfield(self): fld = OptionalField(UintField('field')) s_init = [10, 0, 'hi'] (s_final, val) = fld.getfield(None, s_init) self.assertEqual(s_final, [0, 'hi']) self.assertEqual(val, 10) s_init = [None] (s_final, val) = fld.getfield(None, s_init) self.assertEqual(s_final, []) self.assertEqual(val, None) s_init = [] (s_final, val) = fld.getfield(None, s_init) self.assertEqual(s_final, []) self.assertEqual(val, None)
def testDecode(self): fld = OptionalField(UintField('field')) self.assertEqual(fld.m2i(None, 10), 10) self.assertEqual(fld.m2i(None, False), 0)
def testDecode(self): fld = FieldListField('field', [], UintField('field')) self.assertEqual(fld.m2i(None, [0]), [0]) self.assertEqual(fld.m2i(None, [1, 2]), [1, 2])
class BundleAgeBlock(CborItem): ''' Block data from BPbis Section 4.3.2. ''' fields_desc = ( UintField('age'), )
class CanonicalBlock(AbstractBlock): ''' The canonical block definition with a type-specific payload. Any payload of this block is encoded as the "data" field when building and decoded from the "data" field when dissecting. ''' @enum.unique class Flag(enum.IntFlag): ''' Block flags. Flags must be in LSbit-first order. ''' NONE = 0 #: block must be removed from bundle if it can't be processed. REMOVE_IF_NO_PROCESS = 0x10 #: bundle must be deleted if block can't be processed. DELETE_IF_NO_PROCESS = 0x04 #: transmission of a status report is requested if block can't be processed. STATUS_IF_NO_PROCESS = 0x02 #: block must be replicated in every fragment. REPLICATE_IN_FRAGMENT = 0x01 fields_desc = ( UintField('type_code', default=None), UintField('block_num', default=None), FlagsField('block_flags', default=Flag.NONE, flags=Flag), EnumField('crc_type', default=AbstractBlock.CrcType.NONE, enum=AbstractBlock.CrcType), BstrField('btsd', default=None), # block-type-specific data here ConditionalField( BstrField('crc_value'), lambda block: block.crc_type != 0 ), ) def ensure_block_type_specific_data(self): ''' Embed payload as field data if not already present. ''' if isinstance(self.payload, scapy.packet.NoPayload): return if self.fields.get('btsd') is not None: return if isinstance(self.payload, AbstractCborStruct): pay_data = bytes(self.payload) else: pay_data = self.payload.do_build() self.fields['btsd'] = pay_data def fill_fields(self): self.ensure_block_type_specific_data() super().fill_fields() def self_build(self, *args, **kwargs): self.ensure_block_type_specific_data() return super().self_build(*args, **kwargs) def do_build_payload(self): # Payload is handled by self_build() return b'' def post_dissect(self, s): # Extract payload from fields pay_type = self.fields.get('type_code') pay_data = self.fields.get('btsd') if (pay_data is not None and pay_type is not None): try: cls = self.guess_payload_class(None) LOGGER.debug('CanonicalBlock.post_dissect with class %s from: %s', cls, encode_diagnostic(pay_data)) except KeyError: cls = None if cls is not None: try: pay = cls(pay_data) self.add_payload(pay) except Exception as err: if conf.debug_dissector: raise LOGGER.warning('CanonicalBlock failed to dissect payload: %s', err) return super().post_dissect(s) def default_payload_class(self, payload): return scapy.packet.Raw @classmethod def bind_type(cls, type_code): ''' Bind a block-type-specific packet-class handler. :param int type_code: The type to bind to the payload class. ''' def func(othercls): scapy.packet.bind_layers(cls, othercls, type_code=type_code) return othercls return func