1# SPDX-License-Identifier: GPL-2.0-or-later
2# This file is part of Scapy
3# See https://scapy.net/ for more information
4# Original PPI author: <jellch@harris.com>
5
6# scapy.contrib.description = CACE Per-Packet Information (PPI) header
7# scapy.contrib.status = loads
8
9"""
10CACE Per-Packet Information (PPI) header.
11
12A method for adding metadata to link-layer packets.
13
14For example, one can tag an 802.11 packet with GPS coordinates of where it
15was captured, and include it in the PCAP file.
16
17New PPI types should:
18
19 * Make their packet a subclass of ``PPI_Element``
20 * Call ``bind_layers(PPI_Hdr, ExamplePPI, pfh_type=0xffff)``
21
22See ``layers/contrib/ppi_cace.py`` for an example.
23"""
24
25from scapy.config import conf
26from scapy.data import DLT_PPI, PPI_TYPES
27from scapy.error import warning
28from scapy.packet import Packet
29from scapy.fields import ByteField, FieldLenField, LEIntField, \
30 PacketListField, LEShortEnumField, LenField
31
32
33class PPI_Hdr(Packet):
34 name = 'PPI Header'
35 fields_desc = [
36 LEShortEnumField('pfh_type', 0, PPI_TYPES),
37 LenField('pfh_length', None, fmt='<H'),
38 ]
39
40 def mysummary(self):
41 return self.sprintf('PPI %pfh_type%')
42
43
44class PPI_Element(Packet):
45 """Superclass for all PPI types."""
46 name = 'PPI Element'
47
48 def extract_padding(self, s):
49 return b'', s
50
51 @staticmethod
52 def length_from(pkt):
53 if not pkt.underlayer:
54 warning('Missing under-layer')
55 return 0
56
57 return pkt.underlayer.len
58
59
60class PPI(Packet):
61 name = 'Per-Packet Information header (PPI)'
62 fields_desc = [
63 ByteField('version', 0),
64 ByteField('flags', 0),
65 FieldLenField('len', None, length_of='headers', fmt='<H',
66 adjust=lambda p, x: x + 8), # length of this packet
67 LEIntField('dlt', None),
68 PacketListField('headers', [], PPI_Hdr,
69 length_from=lambda p: p.len - 8),
70 ]
71
72 def add_payload(self, payload):
73 Packet.add_payload(self, payload)
74
75 # Update the DLT if not set
76 if self.getfieldval('dlt') is None and isinstance(payload, Packet):
77 self.setfieldval('dlt', conf.l2types.get(payload.__class__))
78
79 def guess_payload_class(self, payload):
80 # Pass DLT handling to conf.l2types.
81 return conf.l2types.get(
82 self.getfieldval('dlt'), Packet.guess_payload_class(self, payload))
83
84
85conf.l2types.register(DLT_PPI, PPI)