Example #1
0
class EnhancedPacket(BasePacketBlock):
    magic_number = 0x00000006
    schema = [
        ('interface_id', IntField(32, False)),
        ('timestamp_high', IntField(32, False)),
        ('timestamp_low', IntField(32, False)),
        ('packet_payload_info', PacketDataField()),
        ('options', OptionsField([
            (2, 'epb_flags'),  # todo: is this endianness dependent?
            (3, 'epb_hash'),  # todo: process the hash value
            (4, 'epb_dropcount', 'u64'),
        ]))
    ]

    @property
    def captured_len(self):
        return self.packet_payload_info[0]

    @property
    def packet_len(self):
        return self.packet_payload_info[1]

    @property
    def packet_data(self):
        return self.packet_payload_info[2]
Example #2
0
class InterfaceStatistics(SectionMemberBlock, BlockWithTimestampMixin,
                          BlockWithInterfaceMixin):
    """
    "The Interface Statistics Block (ISB) contains the capture statistics for a
    given interface [...]. The statistics are referred to the interface defined
    in the current Section identified by the Interface ID field."
    - pcapng spec, section 4.6. Other quoted citations are from this section
    unless otherwise noted.
    """
    magic_number = 0x00000005
    __slots__ = []
    schema = [
        ('interface_id', IntField(32, False), 0),
        ('timestamp_high', IntField(32, False), 0),
        ('timestamp_low', IntField(32, False), 0),
        (
            'options',
            OptionsField([
                Option(2, 'isb_starttime', 'u64'),  # todo: consider resolution
                Option(3, 'isb_endtime', 'u64'),
                Option(4, 'isb_ifrecv', 'u64'),
                Option(5, 'isb_ifdrop', 'u64'),
                Option(6, 'isb_filteraccept', 'u64'),
                Option(7, 'isb_osdrop', 'u64'),
                Option(8, 'isb_usrdeliv', 'u64'),
            ]),
            None),
    ]
Example #3
0
class Packet(BasePacketBlock):
    magic_number = 0x00000002
    schema = [
        ('interface_id', IntField(16, False)),
        ('drops_count', IntField(16, False)),
        ('timestamp_high', IntField(32, False)),
        ('timestamp_low', IntField(32, False)),
        ('packet_payload_info', PacketDataField()),
        ('options', OptionsField([
            (2, 'epb_flags', 'u32'),  # A flag!
            (3, 'epb_hash'),  # Variable size!
        ]))
    ]

    @property
    def captured_len(self):
        return self.packet_payload_info[0]

    @property
    def packet_len(self):
        return self.packet_payload_info[1]

    @property
    def packet_data(self):
        return self.packet_payload_info[2]
Example #4
0
class EnhancedPacket(BasePacketBlock, BlockWithTimestampMixin):
    """
    "An Enhanced Packet Block (EPB) is the standard container for storing the
    packets coming from the network."
    - pcapng spec, section 4.3. Other quoted citations are from this section
    unless otherwise noted.
    """
    magic_number = 0x00000006
    __slots__ = []
    schema = [
        ('interface_id', IntField(32, False), 0),
        ('timestamp_high', IntField(32, False), 0),
        ('timestamp_low', IntField(32, False), 0),
        ('captured_len', IntField(32, False), 0),
        ('packet_len', IntField(32, False), 0),
        ('packet_data', PacketBytes('captured_len'), b''),
        (
            'options',
            OptionsField([
                Option(2, 'epb_flags', 'epb_flags'),
                Option(3, 'epb_hash', 'type+bytes',
                       multiple=True),  # todo: process the hash value
                Option(4, 'epb_dropcount', 'u64'),
            ]),
            None)
    ]
Example #5
0
class NameResolution(SectionMemberBlock):
    """
    "The Name Resolution Block (NRB) is used to support the correlation of
    numeric addresses (present in the captured packets) and their corresponding
    canonical names [...]. Having the literal names saved in the file prevents
    the need for performing name resolution at a later time, when the
    association between names and addresses may be different from the one in
    use at capture time."
    - pcapng spec, section 4.5. Other quoted citations are from this section
    unless otherwise noted.
    """

    magic_number = 0x00000004
    __slots__ = []
    schema = [
        ("records", ListField(NameResolutionRecordField()), []),
        (
            "options",
            OptionsField(
                [
                    Option(2, "ns_dnsname", "string"),
                    Option(3, "ns_dnsIP4addr", "ipv4"),
                    Option(4, "ns_dnsIP6addr", "ipv6"),
                ]
            ),
            None,
        ),
    ]
Example #6
0
class EnhancedPacket(BasePacketBlock, BlockWithTimestampMixin):
    """
    "An Enhanced Packet Block (EPB) is the standard container for storing the
    packets coming from the network."
    - pcapng spec, section 4.3. Other quoted citations are from this section
    unless otherwise noted.
    """

    magic_number = 0x00000006
    __slots__ = []
    schema = [
        ("interface_id", IntField(32, False), 0),
        ("timestamp_high", IntField(32, False), 0),
        ("timestamp_low", IntField(32, False), 0),
        ("captured_len", IntField(32, False), 0),
        ("packet_len", IntField(32, False), 0),
        ("packet_data", PacketBytes("captured_len"), b""),
        (
            "options",
            OptionsField(
                [
                    Option(2, "epb_flags", "epb_flags"),
                    Option(3, "epb_hash", "type+bytes", multiple=True),  # todo: process
                    Option(4, "epb_dropcount", "u64"),
                ]
            ),
            None,
        ),
    ]
Example #7
0
class ObsoletePacket(BasePacketBlock, BlockWithTimestampMixin):
    """
    "The Packet Block is obsolete, and MUST NOT be used in new files. [...] A
    Packet Block was a container for storing packets coming from the network."
    - pcapng spec, Appendix A. Other quoted citations are from this appendix
    unless otherwise noted.
    """

    magic_number = 0x00000002
    __slots__ = []
    schema = [
        ("interface_id", IntField(16, False), 0),
        ("drops_count", IntField(16, False), 0),
        ("timestamp_high", IntField(32, False), 0),
        ("timestamp_low", IntField(32, False), 0),
        ("captured_len", IntField(32, False), 0),
        ("packet_len", IntField(32, False), 0),
        ("packet_data", PacketBytes("captured_len"), None),
        (
            # The options have the same definitions as their epb_ equivalents
            "options",
            OptionsField(
                [
                    Option(2, "pack_flags", "epb_flags"),
                    Option(3, "pack_hash", "type+bytes", multiple=True),
                ]
            ),
            None,
        ),
    ]

    def enhanced(self):
        """Return an EnhancedPacket with this block's attributes."""
        opts_dict = dict(self.options)
        opts_dict["epb_dropcount"] = self.drops_count
        for a in ("flags", "hash"):
            try:
                opts_dict["epb_" + a] = opts_dict.pop("pack_" + a)
            except KeyError:
                pass
        return self.section.new_member(
            EnhancedPacket,
            interface_id=self.interface_id,
            timestamp_high=self.timestamp_high,
            timestamp_low=self.timestamp_low,
            packet_len=self.packet_len,
            packet_data=self.packet_data,
            options=opts_dict,
        )

    # Do this check in _write() instead of _encode() to ensure the block gets written
    # with the correct magic number.
    def _write(self, outstream):
        strictness.problem("Packet Block is obsolete and must not be used")
        if strictness.should_fix():
            self.enhanced()._write(outstream)
        else:
            super(ObsoletePacket, self)._write(outstream)
Example #8
0
class SectionHeader(Block):
    magic_number = 0x0a0d0d0a
    schema = [('version_major', IntField(16, False)),
              ('version_minor', IntField(16, False)),
              ('section_length', IntField(64, True)),
              ('options',
               OptionsField([
                   (2, 'shb_hardware', 'string'),
                   (3, 'shb_os', 'string'),
                   (4, 'shb_userappl', 'string'),
               ]))]

    def __init__(self, raw=None, endianness="<"):
        self._raw = raw
        self._decoded = None
        self.endianness = endianness
        self._interfaces_id = itertools.count(0)
        self.interfaces = {}
        self.interface_stats = {}

    def _decode(self):
        return struct_decode(self.schema,
                             io.BytesIO(self._raw),
                             endianness=self.endianness)

    def _encode(self, outstream):
        write_int(0x1A2B3C4D, outstream, 32)
        struct_encode(self.schema, self, outstream, endianness=self.endianness)

    def register_interface(self, interface):
        """Helper method to register an interface within this section"""
        assert isinstance(interface, InterfaceDescription)
        interface_id = next(self._interfaces_id)
        interface.interface_id = interface_id
        self.interfaces[interface_id] = interface

    def add_interface_stats(self, interface_stats):
        """Helper method to register interface stats within this section"""
        assert isinstance(interface_stats, InterfaceStatistics)
        self.interface_stats[interface_stats.interface_id] = interface_stats

    @property
    def version(self):
        return (self.version_major, self.version_minor)

    @property
    def length(self):
        return self.section_length

    def __repr__(self):
        return ('<{name} version={version} endianness={endianness} '
                'length={length} options={options}>').format(
                    name=self.__class__.__name__,
                    version='.'.join(str(x) for x in self.version),
                    endianness=repr(self.endianness),
                    length=self.length,
                    options=repr(self.options))
Example #9
0
class InterfaceDescription(SectionMemberBlock):
    magic_number = 0x00000001
    schema = [
        ('link_type', IntField(16, False)),  # todo: enc/decode
        ('reserved', RawBytes(2)),
        ('snaplen', IntField(32, False)),
        (
            'options',
            OptionsField([
                (2, 'if_name', 'string'),
                (3, 'if_description', 'string'),
                (4, 'if_IPv4addr', 'ipv4+mask'),
                (5, 'if_IPv6addr', 'ipv6+prefix'),
                (6, 'if_MACaddr', 'macaddr'),
                (7, 'if_EUIaddr', 'euiaddr'),
                (8, 'if_speed', 'u64'),
                (9, 'if_tsresol'),  # Just keep the raw data
                (10, 'if_tzone', 'u32'),
                (11, 'if_filter', 'string'),
                (12, 'if_os', 'string'),
                (13, 'if_fcslen', 'u8'),
                (14, 'if_tsoffset', 'i64'),
            ]))
    ]

    @property  # todo: cache this property
    def timestamp_resolution(self):
        # ------------------------------------------------------------
        # Resolution of timestamps. If the Most Significant Bit is
        # equal to zero, the remaining bits indicates the resolution
        # of the timestamp as as a negative power of 10 (e.g. 6 means
        # microsecond resolution, timestamps are the number of
        # microseconds since 1/1/1970). If the Most Significant Bit is
        # equal to one, the remaining bits indicates the resolution as
        # as negative power of 2 (e.g. 10 means 1/1024 of second). If
        # this option is not present, a resolution of 10^-6 is assumed
        # (i.e. timestamps have the same resolution of the standard
        # 'libpcap' timestamps).
        # ------------------------------------------------------------

        if 'if_tsresol' in self.options:
            return unpack_timestamp_resolution(self.options['if_tsresol'])

        return 1e-6

    @property
    def statistics(self):
        # todo: ensure we always have an interface id -> how??
        return self.section.interface_stats.get(self.interface_id)

    @property
    def link_type_description(self):
        try:
            return link_types.LINKTYPE_DESCRIPTIONS[self.link_type]
        except KeyError:
            return 'Unknown link type: 0x{0:04x}'.format(self.link_type)
Example #10
0
class NameResolution(SectionMemberBlock):
    magic_number = 0x00000004
    schema = [
        ('records', ListField(NameResolutionRecordField())),
        ('options', OptionsField([
            (2, 'ns_dnsname', 'string'),
            (3, 'ns_dnsIP4addr', 'ipv4'),
            (4, 'ns_dnsIP6addr', 'ipv6'),
        ])),
    ]
Example #11
0
class ObsoletePacket(BasePacketBlock, BlockWithTimestampMixin):
    """
    "The Packet Block is obsolete, and MUST NOT be used in new files. [...] A
    Packet Block was a container for storing packets coming from the network."
    - pcapng spec, Appendix A. Other quoted citations are from this appendix
    unless otherwise noted.
    """
    magic_number = 0x00000002
    __slots__ = []
    schema = [
        ('interface_id', IntField(16, False), 0),
        ('drops_count', IntField(16, False), 0),
        ('timestamp_high', IntField(32, False), 0),
        ('timestamp_low', IntField(32, False), 0),
        ('captured_len', IntField(32, False), 0),
        ('packet_len', IntField(32, False), 0),
        ('packet_data', PacketBytes('captured_len'), b''),
        (
            'options',
            OptionsField([
                Option(2, 'pack_flags',
                       'epb_flags'),  # Same definition as epb_flags
                Option(3, 'pack_hash', 'type+bytes',
                       multiple=True),  # Same definition as epb_hash
            ]),
            None)
    ]

    def enhanced(self):
        """Return an EnhancedPacket with this block's attributes."""
        opts_dict = dict(self.options)
        opts_dict['epb_dropcount'] = self.drops_count
        for a in ('flags', 'hash'):
            try:
                opts_dict['epb_' + a] = opts_dict.pop('pack_' + a)
            except KeyError:
                pass
        return self.section.new_member(EnhancedPacket,
                                       interface_id=self.interface_id,
                                       timestamp_high=self.timestamp_high,
                                       timestamp_low=self.timestamp_low,
                                       packet_len=self.packet_len,
                                       packet_data=self.packet_data,
                                       options=opts_dict)

    def write(self, outstream):
        strictness.problem("Packet Block is obsolete and must not be used")
        if strictness.should_fix():
            self.enhanced().write(outstream)
        else:
            super(ObsoletePacket, self).write(outstream)
Example #12
0
class InterfaceStatistics(SectionMemberBlock, BlockWithTimestampMixin,
                          BlockWithInterfaceMixin):
    magic_number = 0x00000005
    schema = [
        ('interface_id', IntField(32, False)),
        ('timestamp_high', IntField(32, False)),
        ('timestamp_low', IntField(32, False)),
        ('options', OptionsField([
            (2, 'isb_starttime', 'u64'),  # todo: consider resolution
            (3, 'isb_endtime', 'u64'),
            (4, 'isb_ifrecv', 'u64'),
            (5, 'isb_ifdrop', 'u64'),
            (6, 'isb_filteraccept', 'u64'),
            (7, 'isb_osdrop', 'u64'),
            (8, 'isb_usrdeliv', 'u64'),
        ])),
    ]
Example #13
0
def test_unpack_dummy_packet():
    schema = [
        ('a_string', RawBytes(8), ''),
        ('a_number', IntField(32, False), 0),
        ('options', OptionsField([]), None),
        ('pb_captured_len', IntField(32, False), 0),
        ('pb_orig_len', IntField(32, False), 0),
        ('packet_data', PacketBytes('pb_captured_len'), b''),
        ('spb_orig_len', IntField(32, False), 0),
        ('simple_packet_data', PacketBytes('spb_orig_len'), b''),
        ('name_res', ListField(NameResolutionRecordField()), []),
        ('another_number', IntField(32, False), 0),
    ]

    # Note: NULLs are for padding!
    data = io.BytesIO(
        b'\x01\x23\x45\x67\x89\xab\xcd\xef'
        b'\x00\x00\x01\x00'

        # Options
        b'\x00\x01\x00\x0cHello world!'
        b'\x00\x01\x00\x0fSpam eggs bacon\x00'
        b'\x00\x02\x00\x0fSome other text\x00'
        b'\x00\x00\x00\x00'

        # Enhanced Packet data
        b'\x00\x00\x00\x12'
        b'\x00\x01\x00\x00'
        b'These are 18 bytes\x00\x00'

        # Simple packet data
        b'\x00\x00\x00\x0d'
        b'Simple packet\x00\x00\x00'

        # List of name resolution items
        b'\x00\x01'  # IPv4
        b'\x00\x13'  # Length: 19bytes
        b'\x0a\x22\x33\x44www.example.com\x00'  # 19 bytes (10.34.51.68)

        b'\x00\x01'  # IPv4
        b'\x00\x13'  # Length: 19bytes
        b'\xc0\xa8\x14\x01www.example.org\x00'  # 19 bytes (192.168.20.1)

        b'\x00\x02'  # IPv6
        b'\x00\x1e'  # 30 bytes
        b'\x00\x11\x22\x33\x44\x55\x66\x77'
        b'\x88\x99\xaa\xbb\xcc\xdd\xee\xff'
        b'v6.example.net\x00\x00'

        b'\x00\x00\x00\x00'  # End marker

        # Another number, to check end
        b'\xaa\xbb\xcc\xdd'
    )

    unpacked = struct_decode(schema, data, endianness='>')
    assert unpacked['a_string'] == b'\x01\x23\x45\x67\x89\xab\xcd\xef'
    assert unpacked['a_number'] == 0x100

    assert isinstance(unpacked['options'], Options)
    assert len(unpacked['options']) == 2
    assert unpacked['options']['opt_comment'] == 'Hello world!'
    assert unpacked['options'][2] == b'Some other text'

    assert unpacked['pb_captured_len'] == 0x12
    assert unpacked['pb_orig_len'] == 0x10000
    assert unpacked['packet_data'] == b'These are 18 bytes'

    assert unpacked['spb_orig_len'] == 13
    assert unpacked['simple_packet_data'] == b'Simple packet'

    assert unpacked['name_res'] == [
        {'address': '10.34.51.68',
         'names': ['www.example.com'], 'type': 1},
        {'address': '192.168.20.1',
         'names': ['www.example.org'], 'type': 1},
        {'type': 2,
            'address': '11:2233:4455:6677:8899:aabb:ccdd:eeff',
         'names': ['v6.example.net']}]
Example #14
0
class InterfaceDescription(SectionMemberBlock):
    """
    "An Interface Description Block (IDB) is the container for information
    describing an interface on which packet data is captured."
    - pcapng spec, section 4.2. Other quoted citations are from this section
    unless otherwise noted.
    """
    magic_number = 0x00000001
    __slots__ = []
    schema = [
        ('link_type', IntField(16, False), 0),  # todo: enc/decode
        ('reserved', IntField(16, False), 0),
        ('snaplen', IntField(32, False), 0),
        (
            'options',
            OptionsField([
                Option(2, 'if_name', 'string'),
                Option(3, 'if_description', 'string'),
                Option(4, 'if_IPv4addr', 'ipv4+mask', multiple=True),
                Option(5, 'if_IPv6addr', 'ipv6+prefix', multiple=True),
                Option(6, 'if_MACaddr', 'macaddr'),
                Option(7, 'if_EUIaddr', 'euiaddr'),
                Option(8, 'if_speed', 'u64'),
                Option(9, 'if_tsresol', 'bytes'),  # Just keep the raw data
                Option(10, 'if_tzone', 'u32'),
                Option(11, 'if_filter', 'type+bytes'),
                Option(12, 'if_os', 'string'),
                Option(13, 'if_fcslen', 'u8'),
                Option(14, 'if_tsoffset', 'i64'),
                Option(15, 'if_hardware', 'string'),
            ]),
            None)
    ]

    @property  # todo: cache this property
    def timestamp_resolution(self):
        # ------------------------------------------------------------
        # Resolution of timestamps. If the Most Significant Bit is
        # equal to zero, the remaining bits indicates the resolution
        # of the timestamp as as a negative power of 10 (e.g. 6 means
        # microsecond resolution, timestamps are the number of
        # microseconds since 1/1/1970). If the Most Significant Bit is
        # equal to one, the remaining bits indicates the resolution as
        # as negative power of 2 (e.g. 10 means 1/1024 of second). If
        # this option is not present, a resolution of 10^-6 is assumed
        # (i.e. timestamps have the same resolution of the standard
        # 'libpcap' timestamps).
        # ------------------------------------------------------------

        if 'if_tsresol' in self.options:
            return unpack_timestamp_resolution(self.options['if_tsresol'])

        return 1e-6

    @property
    def statistics(self):
        # todo: ensure we always have an interface id -> how??
        return self.section.interface_stats.get(self.interface_id)

    @property
    def link_type_description(self):
        try:
            return link_types.LINKTYPE_DESCRIPTIONS[self.link_type]
        except KeyError:
            return 'Unknown link type: 0x{0:04x}'.format(self.link_type)
Example #15
0
class SectionHeader(Block):
    """
    "The Section Header Block (SHB) is mandatory. It identifies the beginning
    of a section of the capture file. The Section Header Block does not contain
    data but it rather identifies a list of blocks (interfaces, packets) that
    are logically correlated."
    - pcapng spec, section 4.1. Other quoted citations are from this section
    unless otherwise noted.
    """
    magic_number = 0x0a0d0d0a
    __slots__ = [
        'endianness',
        '_interfaces_id',
        'interfaces',
        'interface_stats',
    ]
    schema = [('version_major', IntField(16, False), 1),
              ('version_minor', IntField(16, False), 0),
              ('section_length', IntField(64, True), -1),
              ('options',
               OptionsField([
                   Option(2, 'shb_hardware', 'string'),
                   Option(3, 'shb_os', 'string'),
                   Option(4, 'shb_userappl', 'string'),
               ]), None)]

    def __init__(self, endianness="<", **kwargs):
        super(SectionHeader, self).__init__(**kwargs)
        self.endianness = endianness
        self._interfaces_id = itertools.count(0)
        self.interfaces = {}
        self.interface_stats = {}

    def _encode(self, outstream):
        write_int(0x1A2B3C4D, outstream, 32, endianness=self.endianness)
        super(SectionHeader, self)._encode(outstream)

    def new_member(self, cls, **kwargs):
        """Helper method to create a block that's a member of this section"""
        assert issubclass(cls, SectionMemberBlock)
        blk = cls(section=self, **kwargs)
        # Some blocks (eg. SPB) don't have options
        if any([x[0] == 'options' for x in blk.schema]):
            blk.options.endianness = self.endianness
        if isinstance(blk, InterfaceDescription):
            self.register_interface(blk)
        elif isinstance(blk, InterfaceStatistics):
            self.add_interface_stats(blk)
        return blk

    def register_interface(self, interface):
        """Helper method to register an interface within this section"""
        assert isinstance(interface, InterfaceDescription)
        interface_id = next(self._interfaces_id)
        interface.interface_id = interface_id
        self.interfaces[interface_id] = interface

    def add_interface_stats(self, interface_stats):
        """Helper method to register interface stats within this section"""
        assert isinstance(interface_stats, InterfaceStatistics)
        self.interface_stats[interface_stats.interface_id] = interface_stats

    @property
    def version(self):
        return (self.version_major, self.version_minor)

    @property
    def length(self):
        return self.section_length

    # Block.decode() assumes all blocks have sections -- technically true...
    @property
    def section(self):
        return self

    def __repr__(self):
        return ('<{name} version={version} endianness={endianness} '
                'length={length} options={options}>').format(
                    name=self.__class__.__name__,
                    version='.'.join(str(x) for x in self.version),
                    endianness=repr(self.endianness),
                    length=self.length,
                    options=repr(self.options))
Example #16
0
def test_unpack_dummy_packet():
    schema = [
        ('a_string', RawBytes(8)),
        ('a_number', IntField(32, False)),
        ('options', OptionsField([])),
        ('packet_data', PacketDataField()),
        ('simple_packet_data', SimplePacketDataField()),
        ('name_res', ListField(NameResolutionRecordField())),
        ('another_number', IntField(32, False)),
    ]

    # Note: NULLs are for padding!
    data = io.BytesIO(
        '\x01\x23\x45\x67\x89\xab\xcd\xef'
        '\x00\x00\x01\x00'

        # Options
        '\x00\x01\x00\x0cHello world!'
        '\x00\x01\x00\x0fSpam eggs bacon\x00'
        '\x00\x02\x00\x0fSome other text\x00'
        '\x00\x00\x00\x00'

        # Enhanced Packet data
        '\x00\x00\x00\x12'
        '\x00\x01\x00\x00'
        'These are 18 bytes\x00\x00'

        # Simple packet data
        '\x00\x00\x00\x0d'
        'Simple packet\x00\x00\x00'

        # List of name resolution items
        '\x00\x01'  # IPv4
        '\x00\x13'  # Length: 19bytes
        '\x0a\x22\x33\x44www.example.com\x00'  # 19 bytes (10.34.51.68)

        '\x00\x01'  # IPv4
        '\x00\x13'  # Length: 19bytes
        '\xc0\xa8\x14\x01www.example.org\x00'  # 19 bytes (192.168.20.1)

        '\x00\x02'  # IPv6
        '\x00\x1e'  # 30 bytes
        '\x00\x11\x22\x33\x44\x55\x66\x77'
        '\x88\x99\xaa\xbb\xcc\xdd\xee\xff'
        'v6.example.net\x00\x00'

        '\x00\x00\x00\x00'  # End marker

        # Another number, to check end
        '\xaa\xbb\xcc\xdd'
    )

    unpacked = struct_decode(schema, data, endianness='>')
    assert unpacked['a_string'] == '\x01\x23\x45\x67\x89\xab\xcd\xef'
    assert unpacked['a_number'] == 0x100

    assert isinstance(unpacked['options'], Options)
    assert len(unpacked['options']) == 2
    assert unpacked['options']['opt_comment'] == 'Hello world!'
    assert unpacked['options'][2] == 'Some other text'

    assert unpacked['packet_data'] == (0x12, 0x10000, 'These are 18 bytes')

    assert unpacked['simple_packet_data'] == (13, 'Simple packet')

    assert unpacked['name_res'] == [
        {'address': '\x0a\x22\x33\x44', 'name': 'www.example.com', 'type': 1},
        {'address': '\xc0\xa8\x14\x01', 'name': 'www.example.org', 'type': 1},
        {'type': 2,
         'address': '\x00\x11\x22\x33\x44\x55\x66\x77'
                    '\x88\x99\xaa\xbb\xcc\xdd\xee\xff',
         'name': 'v6.example.net'}]
Example #17
0
def test_unpack_dummy_packet():
    schema = [
        ("a_string", RawBytes(8), ""),
        ("a_number", IntField(32, False), 0),
        ("options", OptionsField([]), None),
        ("pb_captured_len", IntField(32, False), 0),
        ("pb_orig_len", IntField(32, False), 0),
        ("packet_data", PacketBytes("pb_captured_len"), b""),
        ("spb_orig_len", IntField(32, False), 0),
        ("simple_packet_data", PacketBytes("spb_orig_len"), b""),
        ("name_res", ListField(NameResolutionRecordField()), []),
        ("another_number", IntField(32, False), 0),
    ]

    # Note: NULLs are for padding!
    data = io.BytesIO(
        b"\x01\x23\x45\x67\x89\xab\xcd\xef"
        b"\x00\x00\x01\x00"
        # Options
        b"\x00\x01\x00\x0cHello world!"
        b"\x00\x01\x00\x0fSpam eggs bacon\x00"
        b"\x00\x02\x00\x0fSome other text\x00"
        b"\x00\x00\x00\x00"
        # Enhanced Packet data
        b"\x00\x00\x00\x12"
        b"\x00\x01\x00\x00"
        b"These are 18 bytes\x00\x00"
        # Simple packet data
        b"\x00\x00\x00\x0d"
        b"Simple packet\x00\x00\x00"
        # List of name resolution items
        b"\x00\x01"  # IPv4
        b"\x00\x13"  # Length: 19bytes
        b"\x0a\x22\x33\x44www.example.com\x00"  # 19 bytes (10.34.51.68)
        b"\x00\x01"  # IPv4
        b"\x00\x13"  # Length: 19bytes
        b"\xc0\xa8\x14\x01www.example.org\x00"  # 19 bytes (192.168.20.1)
        b"\x00\x02"  # IPv6
        b"\x00\x1e"  # 30 bytes
        b"\x00\x11\x22\x33\x44\x55\x66\x77"
        b"\x88\x99\xaa\xbb\xcc\xdd\xee\xff"
        b"v6.example.net\x00\x00"
        b"\x00\x00\x00\x00"  # End marker
        # Another number, to check end
        b"\xaa\xbb\xcc\xdd"
    )

    unpacked = struct_decode(schema, data, endianness=">")
    assert unpacked["a_string"] == b"\x01\x23\x45\x67\x89\xab\xcd\xef"
    assert unpacked["a_number"] == 0x100

    assert isinstance(unpacked["options"], Options)
    assert len(unpacked["options"]) == 2
    assert unpacked["options"]["opt_comment"] == "Hello world!"
    assert unpacked["options"][2] == b"Some other text"

    assert unpacked["pb_captured_len"] == 0x12
    assert unpacked["pb_orig_len"] == 0x10000
    assert unpacked["packet_data"] == b"These are 18 bytes"

    assert unpacked["spb_orig_len"] == 13
    assert unpacked["simple_packet_data"] == b"Simple packet"

    assert unpacked["name_res"] == [
        {"address": "10.34.51.68", "names": ["www.example.com"], "type": 1},
        {"address": "192.168.20.1", "names": ["www.example.org"], "type": 1},
        {
            "type": 2,
            "address": "11:2233:4455:6677:8899:aabb:ccdd:eeff",
            "names": ["v6.example.net"],
        },
    ]