Source code for pcapkit.toolkit.pyshark

# -*- coding: utf-8 -*-
"""PyShark Tools
===================

:mod:`pcapkit.toolkit.pyshark` contains the adapters for the `PyShark`_ engine.
The reassembly and flow tracing adapters return the data their
:mod:`pcapkit.foundation` counterpart consumes, or :data:`None` if the frame
cannot be used for it.

.. note::

   Due to the lack of functionality of `PyShark`_, some
   functions of :mod:`pcapkit` may not be available with
   the `PyShark`_ engine.

"""
import decimal
import ipaddress
from typing import TYPE_CHECKING

from pcapkit.const.reg.linktype import LinkType as Enum_LinkType
from pcapkit.foundation.traceflow.data.tcp import Packet as TF_TCP_Packet
from pcapkit.utilities.exceptions import MissingKeyError, ProtocolError

if TYPE_CHECKING:
    from typing import Any

    from pyshark.packet.packet import Packet

__all__ = ['packet2dict', 'tcp_traceflow', 'ENCAP_TYPE_TO_LINKTYPE', 'FILTER_NAME_TO_LINKTYPE']

#: ``frame.encap_type`` -- Wireshark's internal ``WTAP_ENCAP_*`` number, which |pyshark|_
#: exposes as ``packet.frame_info.encap_type`` -- to the matching
#: :class:`~pcapkit.const.reg.linktype.LinkType` member. This is :func:`tcp_traceflow`'s primary
#: link-type source; ``FILTER_NAME_TO_LINKTYPE`` below is only its fallback.
#:
#: A ``WTAP_ENCAP_*`` number is **not** a DLT: it is 25 where the DLT is 113, 15 where it is 0, and
#: 2 where it is 6, so it needs this table rather than a cast. What it does do is distinguish
#: encapsulations that the PDML root layer's *filter* name cannot, which is the whole reason this
#: table exists: ``sll`` is both ``LINUX_SLL`` (113) and ``LINUX_SLL2`` (276), ``null`` is both
#: ``NULL`` (0) and ``LOOP`` (108), ``raw`` is ``RAW`` (101), ``IPV4`` (228) and ``IPV6`` (229),
#: ``ppp`` is ``PPP`` (9) and ``PPP_WITH_DIR`` (204), ``lapd`` is ``LAPD`` (203) and
#: ``LINUX_LAPD`` (177). ``frame.encap_type`` separates every one of those pairs.
#:
#: Every entry was measured, not transcribed, with ``editcap``/``tshark`` 4.6.9::
#:
#:     editcap -F pcap -T <encap> examples/captures/in.pcap out.pcap
#:     # value := the network field at offset 20 of out.pcap's pcap file header
#:     # key   := the frame.encap_type that `tshark -r out.pcap -c 1 -T pdml` then reports
#:
#: so each pair is one round trip through Wireshark's own ``wiretap/pcap-common.c``, in the read
#: direction |pyshark|_ actually exercises. In the source, the trailing comment on each line
#: names the ``editcap -T`` encapsulation that produced it.
#:
#: What the sweep does **not** cover, since that bounds this table: of the 226 encapsulations
#: ``editcap -T`` accepts, 157 could be written as pcap from the Ethernet source above and the
#: other 69 refused the rewrite, so they are untested. Those 157 yielded 153 distinct
#: ``frame.encap_type`` keys, with no key mapping to two DLTs and no DLT reading back as two keys.
#: Each is below except ``frame.encap_type`` 32 (``editcap -T hhdlc``, DLT 121), left out because
#: :class:`~pcapkit.const.reg.linktype.LinkType` has no member for 121. An encapsulation absent
#: from this table raises rather than resolving to a near-miss DLT.
ENCAP_TYPE_TO_LINKTYPE = {
    1: Enum_LinkType.ETHERNET,                     # ether
    2: Enum_LinkType.IEEE802_5,                    # tr
    3: Enum_LinkType.SLIP,                         # slip
    4: Enum_LinkType.PPP,                          # ppp
    6: Enum_LinkType.FDDI,                         # fddi,fddi-nettl,fddi-swapped
    7: Enum_LinkType.RAW,                          # rawip
    8: Enum_LinkType.ARCNET_BSD,                   # arcnet
    9: Enum_LinkType.ARCNET_LINUX,                 # arcnet_linux
    10: Enum_LinkType.ATM_RFC1483,                 # atm-rfc1483
    11: Enum_LinkType.ATM_CLIP,                    # linux-atm-clip
    13: Enum_LinkType.SUNATM,                      # atm-pdus
    15: Enum_LinkType.NULL,                        # null
    18: Enum_LinkType.IP_OVER_FC,                  # ip-over-fc
    19: Enum_LinkType.PPP_WITH_DIR,                # ppp-with-direction
    20: Enum_LinkType.IEEE802_11,                  # ieee-802-11,ieee-802-11-radio
    21: Enum_LinkType.IEEE802_11_PRISM,            # ieee-802-11-prism
    23: Enum_LinkType.IEEE802_11_RADIOTAP,         # ieee-802-11-radiotap
    24: Enum_LinkType.IEEE802_11_AVS,              # ieee-802-11-avs
    25: Enum_LinkType.LINUX_SLL,                   # linux-sll
    26: Enum_LinkType.FRELAY,                      # frelay,frelay-with-direction
    28: Enum_LinkType.C_HDLC,                      # chdlc
    29: Enum_LinkType.CISCO_IOS,                   # ios
    30: Enum_LinkType.LTALK,                       # ltalk
    33: Enum_LinkType.DOCSIS,                      # docsis
    36: Enum_LinkType.SDLC,                        # sdlc
    37: Enum_LinkType.TZSP,                        # tzsp
    38: Enum_LinkType.ENC,                         # enc
    39: Enum_LinkType.PFLOG,                       # pflog
    41: Enum_LinkType.BLUETOOTH_HCI_H4,            # bluetooth-h4
    42: Enum_LinkType.MTP2,                        # mtp2
    43: Enum_LinkType.MTP3,                        # mtp3
    44: Enum_LinkType.LINUX_IRDA,                  # irda
    45: Enum_LinkType.USER0,                       # user0
    46: Enum_LinkType.USER1,                       # user1
    47: Enum_LinkType.USER2,                       # user2
    48: Enum_LinkType.USER3,                       # user3
    49: Enum_LinkType.USER4,                       # user4
    50: Enum_LinkType.USER5,                       # user5
    51: Enum_LinkType.USER6,                       # user6
    52: Enum_LinkType.USER7,                       # user7
    53: Enum_LinkType.USER8,                       # user8
    54: Enum_LinkType.USER9,                       # user9
    55: Enum_LinkType.USER10,                      # user10
    56: Enum_LinkType.USER11,                      # user11
    57: Enum_LinkType.USER12,                      # user12
    58: Enum_LinkType.USER13,                      # user13
    59: Enum_LinkType.USER14,                      # user14
    60: Enum_LinkType.USER15,                      # user15
    61: Enum_LinkType.SYMANTEC_FIREWALL,           # symantec
    62: Enum_LinkType.APPLE_IP_OVER_IEEE1394,      # ap1394
    63: Enum_LinkType.BACNET_MS_TP,                # bacnet-ms-tp
    66: Enum_LinkType.GPRS_LLC,                    # gprs-llc
    67: Enum_LinkType.JUNIPER_ATM1,                # juniper-atm1
    68: Enum_LinkType.JUNIPER_ATM2,                # juniper-atm2
    69: Enum_LinkType.REDBACK_SMARTEDGE,           # redback
    75: Enum_LinkType.MTP2_WITH_PHDR,              # mtp2-with-phdr
    76: Enum_LinkType.JUNIPER_PPPOE,               # juniper-pppoe
    77: Enum_LinkType.GCOM_T1E1,                   # gcom-tie1
    78: Enum_LinkType.GCOM_SERIAL,                 # gcom-serial
    81: Enum_LinkType.JUNIPER_MLPPP,               # juniper-mlppp
    82: Enum_LinkType.JUNIPER_MLFR,                # juniper-mlfr
    83: Enum_LinkType.JUNIPER_ETHER,               # juniper-ether
    84: Enum_LinkType.JUNIPER_PPP,                 # juniper-ppp
    85: Enum_LinkType.JUNIPER_FRELAY,              # juniper-frelay
    86: Enum_LinkType.JUNIPER_CHDLC,               # juniper-chdlc
    87: Enum_LinkType.JUNIPER_GGSN,                # juniper-ggsn
    88: Enum_LinkType.LINUX_LAPD,                  # linux-lapd
    91: Enum_LinkType.JUNIPER_VP,                  # juniper-vp
    92: Enum_LinkType.USB_FREEBSD,                 # usb-freebsd
    93: Enum_LinkType.IEEE802_16_MAC_CPS,          # ieee-802-16-mac-cps
    95: Enum_LinkType.USB_LINUX,                   # usb-linux
    97: Enum_LinkType.PPI,                         # ppi
    98: Enum_LinkType.ERF,                         # erf
    99: Enum_LinkType.BLUETOOTH_HCI_H4_WITH_PHDR,  # bluetooth-h4-linux
    100: Enum_LinkType.SITA,                       # sita-wan
    101: Enum_LinkType.SCCP,                       # sccp
    103: Enum_LinkType.IPMB_KONTRON,               # ipmb-kontron
    104: Enum_LinkType.IEEE802_15_4_WITHFCS,       # wpan
    105: Enum_LinkType.X2E_XORAYA,                 # x2e-xoraya
    106: Enum_LinkType.FLEXRAY,                    # flexray
    107: Enum_LinkType.LIN,                        # lin
    108: Enum_LinkType.MOST,                       # most
    109: Enum_LinkType.CAN20B,                     # can20b
    111: Enum_LinkType.X2E_SERIAL,                 # x2e-serial
    112: Enum_LinkType.I2C_LINUX,                  # i2c-linux
    113: Enum_LinkType.IEEE802_15_4_NONASK_PHY,    # wpan-nonask-phy
    115: Enum_LinkType.USB_LINUX_MMAPPED,          # usb-linux-mmap
    121: Enum_LinkType.FC_2,                       # fc2
    122: Enum_LinkType.FC_2_WITH_FRAME_DELIMS,     # fc2sof
    124: Enum_LinkType.IPNET,                      # ipnet
    125: Enum_LinkType.CAN_SOCKETCAN,              # socketcan
    127: Enum_LinkType.IEEE802_15_4_NOFCS,         # wpan-nofcs
    129: Enum_LinkType.IPV4,                       # rawip4
    130: Enum_LinkType.IPV6,                       # rawip6
    131: Enum_LinkType.LAPD,                       # lapd
    132: Enum_LinkType.DVB_CI,                     # dvbci
    133: Enum_LinkType.MUX27010,                   # mux27010
    135: Enum_LinkType.NETANALYZER,                # netanalyzer
    136: Enum_LinkType.NETANALYZER_TRANSPARENT,    # netanalyzer-transparent
    138: Enum_LinkType.MPEG_2_TS,                  # mp2ts
    139: Enum_LinkType.PPP_ETHER,                  # pppoes
    140: Enum_LinkType.NFC_LLCP,                   # nfc-llcp
    141: Enum_LinkType.NFLOG,                      # nflog
    146: Enum_LinkType.DBUS,                       # dbus
    147: Enum_LinkType.AX25_KISS,                  # ax25-kiss
    148: Enum_LinkType.AX25,                       # ax25
    149: Enum_LinkType.SCTP,                       # sctp
    151: Enum_LinkType.JUNIPER_SERVICES,           # juniper-svcs
    152: Enum_LinkType.USBPCAP,                    # usb-usbpcap
    153: Enum_LinkType.RTAC_SERIAL,                # rtac-serial
    154: Enum_LinkType.BLUETOOTH_LE_LL,            # bluetooth-le-ll
    155: Enum_LinkType.WIRESHARK_UPPER_PDU,        # wireshark-upper-pdu
    157: Enum_LinkType.STANAG_5066_D_PDU,          # s5066-dpdu
    158: Enum_LinkType.NETLINK,                    # netlink
    159: Enum_LinkType.BLUETOOTH_LINUX_MONITOR,    # bluetooth-linux-monitor
    160: Enum_LinkType.BLUETOOTH_BREDR_BB,         # bluetooth-bredr-bb-rf
    161: Enum_LinkType.BLUETOOTH_LE_LL_WITH_PHDR,  # bluetooth-le-ll-rf
    171: Enum_LinkType.PKTAP,                      # pktap
    172: Enum_LinkType.EPON,                       # epon
    173: Enum_LinkType.IPMI_HPM_2,                 # ipmi-trace
    174: Enum_LinkType.LOOP,                       # loop
    177: Enum_LinkType.ISO_14443,                  # iso14443
    178: Enum_LinkType.GPF_T,                      # gfp-t
    179: Enum_LinkType.GPF_F,                      # gfp-f
    180: Enum_LinkType.IPOIB,                      # ip-ib
    181: Enum_LinkType.A429,                       # juniper-vn
    182: Enum_LinkType.USB_DARWIN,                 # usb-darwin
    183: Enum_LinkType.LORATAP,                    # loratap
    184: Enum_LinkType.EXP_ETHERNET,               # xeth
    185: Enum_LinkType.VSOCK,                      # vsock
    186: Enum_LinkType.NORDIC_BLE,                 # nordic_ble
    197: Enum_LinkType.JUNIPER_ST,                 # juniper-st
    198: Enum_LinkType.ETHERNET_MPACKET,           # ether-mpacket
    199: Enum_LinkType.DOCSIS31_XRA31,             # docsis31_xra31
    200: Enum_LinkType.DISPLAYPORT_AUX,            # dpauxmon
    204: Enum_LinkType.EBHSCR,                     # ebhscr
    205: Enum_LinkType.VPP_DISPATCH,               # vpp
    206: Enum_LinkType.IEEE802_15_4_TAP,           # wpan-tap
    208: Enum_LinkType.USB_2_0,                    # usb-20
    210: Enum_LinkType.LINUX_SLL2,                 # linux-sll2
    211: Enum_LinkType.Z_WAVE_SERIAL,              # zwave-serial
    212: Enum_LinkType.ETW,                        # etw
    214: Enum_LinkType.ZBOSS_NCP,                  # zbncp
    215: Enum_LinkType.USB_2_0_LOW_SPEED,          # usb-20-low
    216: Enum_LinkType.USB_2_0_FULL_SPEED,         # usb-20-full
    217: Enum_LinkType.USB_2_0_HIGH_SPEED,         # usb-20-high
    219: Enum_LinkType.AUERSWALD_LOG,              # auerlog
    220: Enum_LinkType.ATSC_ALP,                   # alp
    221: Enum_LinkType.FIRA_UCI,                   # fira-uci
    222: Enum_LinkType.SILABS_DEBUG_CHANNEL,       # silabs-dch
    223: Enum_LinkType.MDB,                        # mdb
    225: Enum_LinkType.DECT_NR,                    # dect_nr
}  # type: dict[int, Enum_LinkType]

#: Wireshark display-filter name -> :class:`~pcapkit.const.reg.linktype.LinkType` member, used by
#: :func:`tcp_traceflow` **only** when the frame layer carries no ``frame.encap_type`` field for
#: ``ENCAP_TYPE_TO_LINKTYPE`` above to key on -- every capture measured for that table did carry
#: one, so this is a defensive path rather than the usual one. PyShark takes the name verbatim from
#: the PDML
#: ``<proto name=...>`` attribute, which is Wireshark's own dissector *filter* name -- the third
#: argument to ``proto_register_protocol()`` in the relevant ``epan/dissectors/packet-*.c`` -- and
#: that vocabulary is not :class:`~pcapkit.const.reg.linktype.LinkType`'s: Ethernet's filter
#: name is ``eth``, never
#: ``ETHERNET``.
#:
#: Every entry comes from the same sweep as ``ENCAP_TYPE_TO_LINKTYPE`` above: the root ``<proto
#: name=...>`` of ``tshark -r out.pcap -c 1 -T pdml`` against the DLT in ``out.pcap``'s own file
#: header. In the source, the trailing comment on each line names the ``editcap -T`` encapsulation
#: that produced it, so an entry can be re-checked, or corrected when upstream moves, by rerunning
#: that one rewrite. Names are listed even where they already spell their
#: :class:`~pcapkit.const.reg.linktype.LinkType` member
#: (``docsis``, ``fddi``, ``pflog``, ...): there is deliberately **no** fallback onto a like-named
#: member, because upper-casing the name is exactly what would answer 101 for a ``rawip6`` capture
#: and 0 for a ``DLT_LOOP`` one -- valid DLTs, wrong ones, and silent (:issue:`843`).
#:
#: These classes of name are absent, each measured rather than assumed:
#:
#: * **Ambiguous** -- one name, several DLTs, and the PDML node does not say which arrived. These
#:   cannot be mapped at any size: ``user_dlt`` (16 DLTs), ``bluetooth`` (6), ``usb`` (5),
#:   ``usbll`` (4), ``raw`` (3), and ``arcnet``, ``gfp``, ``i2c``, ``lapd``, ``mtp2``,
#:   ``netanalyzer``, ``null``, ``ppp``, ``sll``, ``wlan``, ``wpan`` (2 each).
#: * **Pseudo-protocols** -- ``fake-field-wrapper``, which |pyshark|_ reports as ``data`` and
#:   which was the root for 29 of the swept encapsulations (no dissector), and ``_ws.malformed``,
#:   the root for the one capture ``tshark`` could not parse. Neither names a link type.
#: * **Unswept** -- the 69 encapsulations ``editcap -T`` would not write as pcap from an Ethernet
#:   source. No evidence either way was gathered for them.
#:
#: Caveats on what "unambiguous" means here. It means unambiguous *within* the 157 swept
#: encapsulations: one of the 69 unswept could still root at the same name (``ether-nettl`` and
#: ``tr-nettl`` are the obvious candidates, for ``eth`` and ``tr``). And some entries -- ``llc``,
#: ``sctp``, ``pppoes``, ``fpp`` and ``irlap`` -- name a *payload* dissector that happened to be the
#: outermost PDML node for exactly one encapsulation, rather than a link-layer dissector
#: registered against a ``WTAP_ENCAP_*``. These caveats are why this table is the fallback and
#: ``frame.encap_type`` is the primary key; ``eth`` and ``tr`` additionally check out against
#: Wireshark's own registrations (``packet-eth.c`` on ``WTAP_ENCAP_ETHERNET``, ``packet-tr.c`` on
#: ``WTAP_ENCAP_TOKEN_RING``).
FILTER_NAME_TO_LINKTYPE = {
    'alp': Enum_LinkType.ATSC_ALP,                             # alp
    'ap1394': Enum_LinkType.APPLE_IP_OVER_IEEE1394,            # ap1394
    'atm': Enum_LinkType.SUNATM,                               # atm-pdus
    'ax25': Enum_LinkType.AX25,                                # ax25
    'ax25_kiss': Enum_LinkType.AX25_KISS,                      # ax25-kiss
    'can': Enum_LinkType.CAN_SOCKETCAN,                        # socketcan
    'chdlc': Enum_LinkType.C_HDLC,                             # chdlc
    'clip': Enum_LinkType.ATM_CLIP,                            # linux-atm-clip
    'dbus': Enum_LinkType.DBUS,                                # dbus
    'dect_nr': Enum_LinkType.DECT_NR,                          # dect_nr
    'docsis': Enum_LinkType.DOCSIS,                            # docsis
    'dpauxmon': Enum_LinkType.DISPLAYPORT_AUX,                 # dpauxmon
    'ebhscr': Enum_LinkType.EBHSCR,                            # ebhscr
    'enc': Enum_LinkType.ENC,                                  # enc
    'epon': Enum_LinkType.EPON,                                # epon
    'erf': Enum_LinkType.ERF,                                  # erf
    'eth': Enum_LinkType.ETHERNET,                             # ether
    'exported_pdu': Enum_LinkType.WIRESHARK_UPPER_PDU,         # wireshark-upper-pdu
    'fc': Enum_LinkType.FC_2,                                  # fc2
    'fcsof': Enum_LinkType.FC_2_WITH_FRAME_DELIMS,             # fc2sof
    'fddi': Enum_LinkType.FDDI,                                # fddi,fddi-nettl,fddi-swapped
    'flexray': Enum_LinkType.FLEXRAY,                          # flexray
    'fpp': Enum_LinkType.ETHERNET_MPACKET,                     # ether-mpacket
    'fr': Enum_LinkType.FRELAY,                                # frelay,frelay-with-direction
    'ipfc': Enum_LinkType.IP_OVER_FC,                          # ip-over-fc
    'ipmi.trace': Enum_LinkType.IPMI_HPM_2,                    # ipmi-trace
    'ipnet': Enum_LinkType.IPNET,                              # ipnet
    'ipoib': Enum_LinkType.IPOIB,                              # ip-ib
    'irlap': Enum_LinkType.LINUX_IRDA,                         # irda
    'lin': Enum_LinkType.LIN,                                  # lin
    'llap': Enum_LinkType.LTALK,                               # ltalk
    'llc': Enum_LinkType.ATM_RFC1483,                          # atm-rfc1483
    'llcgprs': Enum_LinkType.GPRS_LLC,                         # gprs-llc
    'loratap': Enum_LinkType.LORATAP,                          # loratap
    'mstp': Enum_LinkType.BACNET_MS_TP,                        # bacnet-ms-tp
    'mtp3': Enum_LinkType.MTP3,                                # mtp3
    'mux27010': Enum_LinkType.MUX27010,                        # mux27010
    'nflog': Enum_LinkType.NFLOG,                              # nflog
    'nordic_ble': Enum_LinkType.NORDIC_BLE,                    # nordic_ble
    'pflog': Enum_LinkType.PFLOG,                              # pflog
    'pktap': Enum_LinkType.PKTAP,                              # pktap
    'ppi': Enum_LinkType.PPI,                                  # ppi
    'pppoes': Enum_LinkType.PPP_ETHER,                         # pppoes
    'radiotap': Enum_LinkType.IEEE802_11_RADIOTAP,             # ieee-802-11-radiotap
    'redback': Enum_LinkType.REDBACK_SMARTEDGE,                # redback
    'rtacser': Enum_LinkType.RTAC_SERIAL,                      # rtac-serial
    'sccp': Enum_LinkType.SCCP,                                # sccp
    'sctp': Enum_LinkType.SCTP,                                # sctp
    'sdlc': Enum_LinkType.SDLC,                                # sdlc
    'silabs-dch': Enum_LinkType.SILABS_DEBUG_CHANNEL,          # silabs-dch
    'sita': Enum_LinkType.SITA,                                # sita-wan
    'tr': Enum_LinkType.IEEE802_5,                             # tr
    'tzsp': Enum_LinkType.TZSP,                                # tzsp
    'uci': Enum_LinkType.FIRA_UCI,                             # fira-uci
    'vpp': Enum_LinkType.VPP_DISPATCH,                         # vpp
    'vsock': Enum_LinkType.VSOCK,                              # vsock
    'wpan-nonask-phy': Enum_LinkType.IEEE802_15_4_NONASK_PHY,  # wpan-nonask-phy
    'xra': Enum_LinkType.DOCSIS31_XRA31,                       # docsis31_xra31
}  # type: dict[str, Enum_LinkType]

#: PyShark's text for a boolean field -> its value. tshark 4.x spells one ``'True'``/``'False'``
#: (measured for ``tcp.flags_syn``, ``tcp.flags_fin`` and ``tcp.flags_reset`` with PyShark 0.6
#: on tshark 4.6.9); ``'1'``/``'0'`` is the spelling this module first parsed (#1514).
_BOOLEAN_FIELD = {'1': True, '0': False, 'True': True, 'False': False}  # type: dict[str, bool]


def _parse_flag(value: 'Any', name: 'str') -> 'bool':
    """Parse a boolean PyShark field, such as ``tcp.flags_syn``.

    Args:
        value: The field as PyShark reports it, a ``LayerFieldsContainer``
            (a :obj:`str` subclass).
        name: The field's name, for the error message.

    Returns:
        The flag's value.

    Raises:
        ProtocolError: If ``value`` is not a :obj:`str` spelling ``'1'``,
            ``'0'``, ``'True'`` or ``'False'``.

    """
    if isinstance(value, str):
        flag = _BOOLEAN_FIELD.get(str(value))
        if flag is not None:
            return flag
    raise ProtocolError(f'invalid PyShark boolean field {name}: {value!r}')


def _parse_epoch(value: 'Any') -> 'float':
    """Parse PyShark's ``frame.time_epoch``, such as ``'1500000000.000638000'``.

    Args:
        value: The field as PyShark reports it, decimal text.

    Returns:
        The timestamp, as the :obj:`float` nearest the decimal value.

    Raises:
        ProtocolError: If ``value`` is not decimal text.

    """
    try:
        return float(decimal.Decimal(str(value)))
    except decimal.InvalidOperation:
        raise ProtocolError(f'invalid PyShark frame.time_epoch: {value!r}') from None


def _absolute(tcp: 'Any', name: 'str') -> 'int':
    """Read the absolute TCP sequence or acknowledgement number.

    Args:
        tcp: PyShark TCP layer.
        name: ``'seq'`` or ``'ack'``.

    Returns:
        ``tcp.<name>_raw``, the number on the wire, or ``tcp.<name>`` where
        tshark does not report the former.

    Note:
        tshark's ``tcp.seq`` and ``tcp.ack`` are *relative* to the flow's first
        number by default (``0`` where the wire says ``832175175``).
        ``tcp.seq_raw`` and ``tcp.ack_raw`` exist from Wireshark 3.2: they are
        absent from 3.0.0's ``epan/dissectors/packet-tcp.c`` and present in
        3.2.0's and 4.2.2's. On an older tshark the fallback is relative unless
        its ``tcp.relative_sequence_numbers`` preference is off.

    """
    value = getattr(tcp, f'{name}_raw', None)
    if value is None:
        value = getattr(tcp, name)
    return int(value)


def _frame_layer(packet: 'Packet') -> 'Any':
    """Find the ``frame`` layer of a PyShark packet.

    Args:
        packet: PyShark packet.

    Returns:
        The layer named ``frame``, or :data:`None` if the record has none.

    Note:
        PyShark takes the second PDML ``<proto>`` as ``frame_info``. On a packet
        with a comment that is ``pkt_comment``, and the frame layer is
        ``layers[0]`` (measured on ``test.pcapng`` frames 1 and 6 with tshark
        4.6.9), so the layer is looked up by name, as the engine does (:issue:`1515`).

    """
    for layer in (packet.frame_info, *packet.layers):
        if layer.layer_name == 'frame':
            return layer
    return None


[docs] def packet2dict(packet: 'Packet') -> 'dict[str, Any]': """Convert PyShark packet into :obj:`dict`. Args: packet: PyShark packet. Returns: A :obj:`dict` mapping of packet data, with the ``frame`` layer's fields at the top level and each later layer nested in the one before it. A packet comment, which PyShark reports as ``frame_info`` in place of the frame layer, is kept under its own ``PKT_COMMENT`` key. A record with no ``frame`` layer keeps PyShark's ``frame_info`` at the top level. """ dict_ = {} # type: dict[str, Any] frame = _frame_layer(packet) if frame is None: frame = packet.frame_info for field in frame.field_names: dict_[field] = getattr(frame, field) if packet.frame_info is not frame: note = packet.frame_info dict_[note.layer_name.upper()] = {field: getattr(note, field) for field in note.field_names} tempdict = dict_ for layer in packet.layers: if layer is frame: continue tempdict[layer.layer_name.upper()] = {} tempdict = tempdict[layer.layer_name.upper()] for field in layer.field_names: tempdict[field] = getattr(layer, field) return dict_
[docs] def _carrier(packet: 'Packet') -> 'tuple[Any, Any]': """Fetch the TCP layer of a PyShark packet and the IP layer that carries it. Args: packet: PyShark packet. Returns: The ``ip`` or ``ipv6`` layer nearest above the packet's first ``tcp`` layer, and that TCP layer; :data:`None` for either the packet lacks. Note: The nearest is the innermost IP layer of an IP-in-IP tunnel (:rfc:`2003`, :rfc:`2473`, :rfc:`4213`), whose addresses are the TCP endpoints'. ``packet.ip`` is the first ``ip`` layer instead, which is the tunnel's (:issue:`1581`). """ ip = None # type: Any for layer in packet.layers: name = layer.layer_name.lower() if name == 'tcp': return ip, layer if name in ('ip', 'ipv6'): ip = layer return ip, None
[docs] def _has_fixed_header(tcp: 'Any') -> 'bool': """Whether tshark dissected the whole fixed TCP header of a PyShark TCP layer. Args: tcp: PyShark TCP layer. Returns: Whether the layer carries ``tcp.urgent_pointer``. Note: A segment is traced only if its 20-octet fixed header (:rfc:`9293#section-3.1`) was captured and its Data Offset is at least 5. tshark omits fields in both other cases, measured with tshark 4.6.9 (:issue:`1611`): * a Data Offset below 5 stops the dissection at ``tcp.hdr_len``, so ``tcp.ack`` and the ``tcp.flags`` fields are absent; * a header cut at octet *k* keeps the fields that fit: no TCP layer below 4, the ports only below 14, and no ``tcp.urgent_pointer`` below 20. ``tcp.urgent_pointer`` covers octets 18 and 19, the last of the fixed header. ``dissect_tcp`` in :file:`epan/dissectors/packet-tcp.c` adds it unconditionally once the Data Offset check passes, in 2.6.0, 3.0.0 and 4.6.0, after every field :func:`tcp_traceflow` reads. """ return 'urgent_pointer' in tcp.field_names
[docs] def _has_addresses(ip: 'Any') -> 'bool': """Whether tshark reported both addresses of a PyShark IP layer. Args: ip: PyShark ``ip`` or ``ipv6`` layer. Returns: Whether the layer carries its ``src`` and ``dst`` fields. Note: tshark reports no ``ip.dst`` behind an LSRR or SSRR option whose route data is no whole number of addresses ("Suboption would go past end of option"), measured with tshark 4.6.9. Reading it raised a bare :exc:`AttributeError` out of :func:`tcp_traceflow`, which ended the extraction (:issue:`1671`). """ names = ip.field_names return 'src' in names and 'dst' in names
[docs] def tcp_traceflow(packet: 'Packet', *, count: 'int' = -1) -> 'TF_TCP_Packet | None': """Trace packet flow for TCP. A packet can be traced if it contains a TCP layer with a whole fixed header (see :func:`_has_fixed_header`), keyed by the IP layer that carries it -- in an IP-in-IP tunnel, the innermost (see :func:`_carrier`) -- if tshark reports both its addresses (see :func:`_has_addresses`). Args: packet: PyShark packet. count: Packet index. If not provided, defaults to ``-1``. The engine passes its own frame count, not tshark's ``frame.number``, which also numbers records that are not packets (:issue:`1515`). Returns: Data for TCP flow tracing (:term:`trace.tcp.packet`) if the ``packet`` can be traced; otherwise :data:`None`. Raises: MissingKeyError: If the frame's link type cannot be resolved. ProtocolError: If the packet has no ``frame`` layer, a TCP flag field is not a boolean tshark spelling, or ``frame.time_epoch`` is not decimal text. See Also: :class:`pcapkit.foundation.traceflow.tcp.TCP` .. versionchanged:: 1.5.0 The link type is resolved from ``frame.encap_type`` through :data:`~pcapkit.toolkit.pyshark.ENCAP_TYPE_TO_LINKTYPE`, or, for a frame without that field, from the root layer's name through :data:`~pcapkit.toolkit.pyshark.FILTER_NAME_TO_LINKTYPE`. One that cannot be resolved either way raises :exc:`~pcapkit.utilities.exceptions.MissingKeyError`; 1.4.x looked up the root layer's name only, which gave the wrong DLT for ``ppp``, ``null``, ``lapd`` and ``raw`` (:issue:`843`). """ ip, tcp = _carrier(packet) if ip is not None and tcp is not None and _has_fixed_header(tcp) and _has_addresses(ip): # NOTE: the link type is keyed on ``frame.encap_type`` -- Wireshark's # internal ``WTAP_ENCAP_*`` number -- through # ``ENCAP_TYPE_TO_LINKTYPE`` (module level, above), and only on the # PDML root layer's *filter* name when the frame layer has no such # field. That name cannot do the job on its own: one filter name # serves several DLTs, so keying on it would answer ``RAW`` (101) for a # ``rawip6`` capture and ``NULL`` (0) for a ``DLT_LOOP`` one -- valid # DLTs, wrong ones, and silent (#843). ``frame.encap_type`` # distinguishes them; see that table's comment for the measurements. # # PyShark reports every field as ``LayerFieldsContainer``, a ``str`` # subclass, so the number arrives as text and needs ``int()``; and a # field the frame layer does not carry raises ``AttributeError`` out # of ``pyshark.packet.layers.base.BaseLayer.__getattr__``, which is # what the ``getattr`` default absorbs here. # # Neither path substitutes a DLT on a miss. A lookup raises on an # unresolvable key rather than minting one (#775), and # NULL and RAW are genuine DLTs -- BSD loopback and raw IP framing, # respectively -- each meant to go with its own handler protocol # class, so neither is an honest stand-in for "unknown link type". # The bare ``KeyError``/``ValueError`` is re-raised as # :exc:`~pcapkit.utilities.exceptions.MissingKeyError` -- this # package's own house exception for a lookup miss -- rather than # letting it escape this public function. # # The frame layer is found by name, not taken as ``frame_info``, which # is ``pkt_comment`` on a commented packet; the root layer is then the # first layer that is not the frame layer. frame = _frame_layer(packet) if frame is None: raise ProtocolError(f'PyShark packet {packet.number} has no frame layer') encap_type = getattr(frame, 'encap_type', None) if encap_type is None: name = [layer for layer in packet.layers if layer is not frame][0].layer_name try: protocol = FILTER_NAME_TO_LINKTYPE[name.lower()] except KeyError: raise MissingKeyError(name) from None else: try: protocol = ENCAP_TYPE_TO_LINKTYPE[int(encap_type)] except (KeyError, ValueError): raise MissingKeyError('frame.encap_type=%s' % encap_type) from None # A Simple Packet Block carries no timestamp, so tshark reports no # ``frame.time_epoch`` for it; the PDML ``geninfo`` timestamp PyShark # keeps as ``sniff_timestamp`` is then ``'0.000000000'``, the UNIX epoch # (measured on ``test.pcapng`` frame 4). epoch = getattr(frame, 'time_epoch', None) if epoch is None: epoch = packet.sniff_timestamp data = TF_TCP_Packet( # type: ignore[type-var] protocol=protocol, # data link type index=count, # frame number frame=packet2dict(packet), # extracted packet syn=_parse_flag(tcp.flags_syn, 'tcp.flags_syn'), # TCP synchronise (SYN) flag fin=_parse_flag(tcp.flags_fin, 'tcp.flags_fin'), # TCP finish (FIN) flag rst=_parse_flag(tcp.flags_reset, 'tcp.flags_reset'), # TCP reset (RST) flag src=ipaddress.ip_address(ip.src), # source IP dst=ipaddress.ip_address(ip.dst), # destination IP srcport=int(tcp.srcport), # TCP source port dstport=int(tcp.dstport), # TCP destination port timestamp=_parse_epoch(epoch), # timestamp seq=_absolute(tcp, 'seq'), # TCP sequence number ack=_absolute(tcp, 'ack'), # TCP acknowledgement number # NOTE: PyShark reports dissected *fields*, not the octets behind # them, so there is no header or payload to hand over -- which is # the same reason this module carries no ``tcp_reassembly`` at all. # ``Extractor`` refuses ``trace_analyse=True`` on this engine, so # nothing reads them. header=b'', # unavailable payload=bytearray(), # unavailable ) return data return None