class CustomBlock: "Serialize & deserialze a PCAPNG Custom Block" head_encoding = '=LLL' tail_encoding = '=L' UNPACK_DISPATCH_TABLE = util.dict_merge_all([ option.Comment.dispatch_entry(), option.CustomStringCopyable.dispatch_entry(), option.CustomBinaryCopyable.dispatch_entry(), option.CustomStringNonCopyable.dispatch_entry(), option.CustomBinaryNonCopyable.dispatch_entry() ]) @staticmethod def is_custom_block_option(obj): "Internal method to validate legal custom block options" result = (isinstance(obj, option.Comment) | isinstance(obj, option.CustomOption)) return result def __init__(self, block_type, pen_val, content, options_lst=[]): "Creates a Custom Block object" pcapng.pen.assert_valid_pen(pen_val) for opt in options_lst: assert self.is_custom_block_option(opt) self.block_type = block_type self.pen_val = pen_val self.content = content self.options_lst = options_lst #todo define these for all blocks def to_map(self): "Converts a CB object to a map representation" return util.select_keys( self.__dict__, ['block_type', 'pen_val', 'content', 'options_lst']) def __repr__(self): return str(self.to_map()) def __eq__(self, other): return self.to_map() == other.to_map() def __ne__(self, other): return (not __eq__(self, other)) def pack(self): """Serialize an CB object into packed bytes. NOTE: We are required to use Length-Value (LV) encoding for the custom content so that any options may also be recovered.""" content_bytes = util.block32_lv_bytes_pack(to_bytes(self.content)) options_bytes = option.pack_all(self.options_lst) block_total_len = 16 + len(content_bytes) + len(options_bytes) packed_bytes = (struct.pack(self.head_encoding, self.block_type, block_total_len, self.pen_val) + content_bytes + options_bytes + struct.pack(self.tail_encoding, block_total_len)) return packed_bytes
def test_dict_merge(): assert {"a": 1, 'b': 2} == util.dict_merge({'a': 1}, {'b': 2}) assert { "a": 1, 'b': 2, 'c': 3 } == util.dict_merge_all([{ 'a': 1 }, { 'b': 2 }, { 'c': 3 }])
def test_custom_option_value(): #todo include standalone value pack/unpack #todo include pack/unpack mixed with regular options def assert_custom_option_value_codec(pen, content): csc = option.CustomStringCopyable(pen, content) csc_unpacked = option.CustomStringCopyable.unpack(csc.pack()) assert csc_unpacked == csc cbc = option.CustomBinaryCopyable(pen, content) cbc_unpacked = option.CustomBinaryCopyable.unpack(cbc.pack()) assert cbc_unpacked == cbc csnc = option.CustomStringNonCopyable(pen, content) csnc_unpacked = option.CustomStringNonCopyable.unpack(csnc.pack()) assert csnc_unpacked == csnc cbnc = option.CustomBinaryNonCopyable(pen, content) cbnc_unpacked = option.CustomBinaryNonCopyable.unpack(cbnc.pack()) assert cbnc_unpacked == cbnc assert_custom_option_value_codec(pen.BROCADE_PEN, '') assert_custom_option_value_codec(pen.BROCADE_PEN, 'a') assert_custom_option_value_codec(pen.BROCADE_PEN, 'go') assert_custom_option_value_codec(pen.BROCADE_PEN, 'ray') assert_custom_option_value_codec(pen.BROCADE_PEN, 'Doh!') assert_custom_option_value_codec(pen.BROCADE_PEN, 'How do you like me now?') unpack_dispatch_table = util.dict_merge_all([ option.Comment.dispatch_entry(), option.CustomStringCopyable.dispatch_entry(), option.CustomBinaryCopyable.dispatch_entry(), option.CustomStringNonCopyable.dispatch_entry(), option.CustomBinaryNonCopyable.dispatch_entry() ]) opts_lst = [ option.Comment("five"), option.CustomStringCopyable(pen.BROCADE_PEN, "yo"), option.Comment("six"), option.CustomBinaryCopyable(pen.BROCADE_PEN, '10011010'), option.Comment("seventy-seven"), option.CustomStringNonCopyable(pen.BROCADE_PEN, "don't copy me"), option.Comment("eight"), option.CustomBinaryNonCopyable(pen.BROCADE_PEN, 'fin'), option.Comment("Agent 009") ] packed_bytes = option.pack_all(opts_lst) opts_lst_unpacked = option.unpack_all(unpack_dispatch_table, packed_bytes) assert opts_lst == opts_lst_unpacked
class SectionHeaderBlock: "Serialize & deserialze a PCAPNG Section Header Block" SPEC_CODE = 0x0A0D0D0A MAJOR_VERSION = 1 MINOR_VERSION = 0 block_head_encoding = '=LLLHHq' #todo need determine endian on read block_tail_encoding = '=L' #todo need determine endian on read UNPACK_DISPATCH_TABLE = util.dict_merge_all([ option.Comment.dispatch_entry(), option.CustomStringCopyable.dispatch_entry(), option.CustomBinaryCopyable.dispatch_entry(), option.CustomStringNonCopyable.dispatch_entry(), option.CustomBinaryNonCopyable.dispatch_entry(), option.ShbHardware.dispatch_entry(), option.ShbOs.dispatch_entry(), option.ShbUserAppl.dispatch_entry() ]) @staticmethod def is_shb_option(obj): "Internal method to validate legal SHB options" result = (isinstance(obj, option.Comment) | isinstance(obj, option.CustomOption) | isinstance(obj, option.ShbOption)) return result def __init__(self, options_lst=[]): "Creates an SHB object with the specified options" for opt in options_lst: assert self.is_shb_option(opt) self.options_lst = options_lst def to_map(self): "Converts an SHB object to a map representation" base_map = { 'SPEC_CODE': self.SPEC_CODE, 'SHB_MAJOR_VERSION': self.MAJOR_VERSION, 'SHB_MINOR_VERSION': self.MINOR_VERSION, } result = util.dict_merge( base_map, util.select_keys(self.__dict__, ['options_lst'])) return result def __repr__(self): return str(self.to_map()) def __eq__(self, other): return self.to_map() == other.to_map() def __ne__(self, other): return (not __eq__(self, other)) @staticmethod def dispatch_entry(): return {SectionHeaderBlock.SPEC_CODE: SectionHeaderBlock.unpack} def pack(self): #todo data_len "Serialize a SHB object into packed bytes" options_bytes = option.pack_all(self.options_lst) section_len = -1 #todo unused at present; must pre-accum all blocks if want to use block_total_len = ( 4 + # block type 4 + # block total length 4 + # byte order magic 2 + 2 + # major version + minor version 8 + # section length len(options_bytes) + 4) # block total length packed_bytes = ( struct.pack(self.block_head_encoding, self.SPEC_CODE, block_total_len, BYTE_ORDER_MAGIC, self.MAJOR_VERSION, self.MINOR_VERSION, section_len) + options_bytes + struct.pack(self.block_tail_encoding, block_total_len)) return packed_bytes @staticmethod def unpack(block_bytes): #todo verify block type & all fields "Deserialize a SHB object from packed bytes" util.assert_type_bytes(block_bytes) (block_type, block_total_len, byte_order_magic, major_version, minor_version, section_len) = struct.unpack(SectionHeaderBlock.block_head_encoding, block_bytes[:24]) (block_total_len_end, ) = struct.unpack( SectionHeaderBlock.block_tail_encoding, block_bytes[-4:]) assert block_type == SectionHeaderBlock.SPEC_CODE assert byte_order_magic == BYTE_ORDER_MAGIC assert major_version == SectionHeaderBlock.MAJOR_VERSION assert minor_version == SectionHeaderBlock.MINOR_VERSION assert block_total_len == block_total_len_end == len(block_bytes) # section_len currently ignored options_bytes = block_bytes[24:-4] options_lst = option.unpack_all( SectionHeaderBlock.UNPACK_DISPATCH_TABLE, options_bytes) result_obj = SectionHeaderBlock(options_lst) return result_obj
class EnhancedPacketBlock: "Serialize & deserialze a PCAPNG Enhanced Packet Block" SPEC_CODE = 0x06 head_encoding = '=LLLLLLL' tail_encoding = '=L' UNPACK_DISPATCH_TABLE = util.dict_merge_all([ option.Comment.dispatch_entry(), option.CustomStringCopyable.dispatch_entry(), option.CustomBinaryCopyable.dispatch_entry(), option.CustomStringNonCopyable.dispatch_entry(), option.CustomBinaryNonCopyable.dispatch_entry(), option.EpbFlags.dispatch_entry(), option.EpbHash.dispatch_entry(), option.EpbDropCount.dispatch_entry() ]) @staticmethod def is_epb_option(obj): "Internal method to validate legal EPB options" result = (isinstance(obj, option.Comment) | isinstance(obj, option.CustomOption) | isinstance(obj, option.EpbOption)) return result def __init__(self, interface_id, pkt_data_captured, pkt_data_orig_len=None, options_lst=[], timestamp=None): """Creates an EPB object. If 'timestamp' is not supplied, uses the current UTC time in the default format (uint64 microseconds since unix epoch). User-supplied timestamp must also be in this format unless IDB options uses option.IdbTsResol to specify alternate.""" util.assert_uint32(interface_id) #todo verify args in all fns pkt_data_captured = to_bytes( pkt_data_captured) #todo is list & tuple & str ok? if pkt_data_orig_len is None: pkt_data_orig_len = len(pkt_data_captured) else: util.assert_uint32(pkt_data_orig_len) assert len(pkt_data_captured) <= pkt_data_orig_len util.assert_type_list(options_lst) #todo check type on all fns for opt in options_lst: assert self.is_epb_option(opt) if timestamp is None: time_utc_micros = util.curr_time_utc_micros() else: time_utc_micros = timestamp self.interface_id = interface_id self.pkt_data_captured = pkt_data_captured self.pkt_data_orig_len = pkt_data_orig_len self.options_lst = options_lst self.time_utc_micros = time_utc_micros def to_map(self): "Converts an EPB object to a map representation" return util.select_keys(self.__dict__, [ 'interface_id', 'pkt_data_captured', 'pkt_data_orig_len', 'options_lst', 'time_utc_micros' ]) def __repr__(self): return str(self.to_map()) def __eq__(self, other): return self.to_map() == other.to_map() def __ne__(self, other): return (not __eq__(self, other)) @staticmethod def dispatch_entry(): return {EnhancedPacketBlock.SPEC_CODE: EnhancedPacketBlock.unpack} def pack(self): "Serialize an EPB object into packed bytes" #todo make all arg validation look like this (in order, at top) pkt_data_pad = util.block32_pad_bytes(self.pkt_data_captured) pkt_data_captured_len = len(self.pkt_data_captured) pkt_data_captured_pad_len = len(pkt_data_pad) options_bytes = option.pack_all(self.options_lst) block_total_len = ( 4 + # block type 4 + # block total length 4 + # interface id 4 + # timestamp - high 4 + # timestamp - low 4 + # captured packet length 4 + # original packet length pkt_data_captured_pad_len + len(options_bytes) + 4 ) # block total length (timestamp_high, timestamp_low) = util.uint64_split32(self.time_utc_micros) packed_bytes = ( struct.pack(self.head_encoding, self.SPEC_CODE, block_total_len, self.interface_id, timestamp_high, timestamp_low, pkt_data_captured_len, self.pkt_data_orig_len) + pkt_data_pad + options_bytes + struct.pack(self.tail_encoding, block_total_len)) return packed_bytes @staticmethod def unpack(packed_bytes): "Deserialize an EPB object from packed bytes" util.assert_type_bytes(packed_bytes) (block_type, block_total_len, interface_id, timestamp_high, timestamp_low, pkt_data_captured_len, pkt_data_orig_len) = struct.unpack(EnhancedPacketBlock.head_encoding, packed_bytes[:28]) (block_total_len_end, ) = struct.unpack( EnhancedPacketBlock.tail_encoding, packed_bytes[-4:]) time_utc_micros = util.uint64_join32(timestamp_high, timestamp_low) assert block_type == EnhancedPacketBlock.SPEC_CODE #todo verify block type & all fields, all fns assert block_total_len == block_total_len_end == len(packed_bytes) assert pkt_data_captured_len <= pkt_data_orig_len pkt_data_captured_pad_len = util.block32_ceil_num_bytes( pkt_data_captured_len) block_bytes_stripped = packed_bytes[28:-4] pkt_data = block_bytes_stripped[:pkt_data_captured_len] options_bytes = block_bytes_stripped[pkt_data_captured_pad_len:] options_lst = option.unpack_all( EnhancedPacketBlock.UNPACK_DISPATCH_TABLE, options_bytes) result_obj = EnhancedPacketBlock(interface_id, pkt_data, pkt_data_orig_len, options_lst, timestamp=time_utc_micros) return result_obj
class InterfaceDescBlock: "Serialize & deserialze a PCAPNG Interface Description Block" SPEC_CODE = 0x01 block_head_encoding = '=LLHHL' block_tail_encoding = '=L' UNPACK_DISPATCH_TABLE = util.dict_merge_all([ option.Comment.dispatch_entry(), option.CustomStringCopyable.dispatch_entry(), option.CustomBinaryCopyable.dispatch_entry(), option.CustomStringNonCopyable.dispatch_entry(), option.CustomBinaryNonCopyable.dispatch_entry(), option.IdbName.dispatch_entry(), option.IdbDescription.dispatch_entry(), option.IdbIpv4Addr.dispatch_entry(), option.IdbIpv6Addr.dispatch_entry(), option.IdbMacAddr.dispatch_entry(), option.IdbEuiAddr.dispatch_entry(), option.IdbSpeed.dispatch_entry(), option.IdbTsResol.dispatch_entry(), option.IdbTZone.dispatch_entry(), option.IdbFilter.dispatch_entry(), option.IdbOs.dispatch_entry(), option.IdbFcsLen.dispatch_entry(), option.IdbTsOffset.dispatch_entry(), ]) @staticmethod def is_idb_option(obj): "Internal method to validate legal IDB options" result = (isinstance(obj, option.Comment) | isinstance(obj, option.CustomOption) | isinstance(obj, option.IdbOption)) return result def __init__( self, link_type=linktype.LINKTYPE_ETHERNET, #todo temp testing default options_lst=[]): "Creates an IDB object with the specified options" #todo need test valid linktype for opt in options_lst: assert self.is_idb_option(opt) self.options_lst = options_lst self.link_type = link_type self.reserved = 0 # spec req zeros self.snaplen = 0 # 0 => no limit def to_map(self): "Converts an IDB object to a map representation" return util.select_keys(self.__dict__, ['link_type', 'options_lst', 'snaplen']) def __repr__(self): return str(self.to_map()) def __eq__(self, other): return self.to_map() == other.to_map() def __ne__(self, other): return (not __eq__(self, other)) @staticmethod def dispatch_entry(): return {InterfaceDescBlock.SPEC_CODE: InterfaceDescBlock.unpack} def pack(self): "Serialize a IDB object into packed bytes" options_bytes = option.pack_all(self.options_lst) util.assert_type_bytes(options_bytes) util.assert_block32_length(options_bytes) block_total_len = ( 4 + # block type 4 + # block total length 2 + 2 + # linktype + reserved 4 + # snaplen len(options_bytes) + 4) # block total length packed_bytes = (struct.pack( self.block_head_encoding, self.SPEC_CODE, block_total_len, self.link_type, self.reserved, self.snaplen) + options_bytes + struct.pack(self.block_tail_encoding, block_total_len)) return packed_bytes @staticmethod def unpack(block_bytes): #todo verify block type & all fields "Deserialize a IDB object from packed bytes" util.assert_type_bytes(block_bytes) (block_type, block_total_len, link_type, reserved, snaplen) = struct.unpack(InterfaceDescBlock.block_head_encoding, block_bytes[:16]) (block_total_len_end, ) = struct.unpack( InterfaceDescBlock.block_tail_encoding, block_bytes[-4:]) assert block_type == InterfaceDescBlock.SPEC_CODE assert block_total_len == block_total_len_end == len(block_bytes) assert reserved == 0 assert snaplen == 0 options_bytes = block_bytes[16:-4] options_lst = option.unpack_all( InterfaceDescBlock.UNPACK_DISPATCH_TABLE, options_bytes) result_obj = InterfaceDescBlock(link_type, options_lst) return result_obj
util.assert_type_bytes(packed_bytes) cbc_obj = CustomBlockCopyable.unpack(packed_bytes) assert cbc_obj.block_type == CustomBlockCopyable.SPEC_CODE assert cbc_obj.pen_val == pcapng.pen.BROCADE_PEN assert cbc_obj.options_lst == CustomMrtIsisBlock.cmib_options mrt_info = mrt.mrt_isis_block_unpack(cbc_obj.content) return mrt_info #----------------------------------------------------------------------------- #todo make a BlockList class? UNPACK_DISPATCH_TABLE = util.dict_merge_all([ SectionHeaderBlock.dispatch_entry(), InterfaceDescBlock.dispatch_entry(), SimplePacketBlock.dispatch_entry(), EnhancedPacketBlock.dispatch_entry(), CustomBlockCopyable.dispatch_entry(), CustomBlockNonCopyable.dispatch_entry(), ]) def unpack_dispatch(packed_bytes): "Invokes the proper parsing function given the packed bytes for a PCAPNG block." (blk_type, blk_total_len, stripped_bytes) = strip_header(packed_bytes) dispatch_fn = UNPACK_DISPATCH_TABLE[blk_type] if (dispatch_fn != None): result = dispatch_fn(packed_bytes) return result else: #todo exception? # raise Exception( 'unpack_dispatch(): unrecognized block blk_type={}'.format(blk_type))