Skip to content

Commit f5351f7

Browse files
committed
[feat] predict encapsulated protocol based on port/next header/protocol ID fields
1 parent cae6c75 commit f5351f7

File tree

8 files changed

+304
-56
lines changed

8 files changed

+304
-56
lines changed

microschc/parser/parser.py

+6-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import List
1+
from typing import Dict, List, Optional, Tuple, Union
22
from microschc.rfc8724 import DirectionIndicator, FieldDescriptor, HeaderDescriptor, PacketDescriptor
33
from microschc.binary.buffer import Buffer
44
from microschc.rfc8724extras import ParserDefinitions
@@ -11,15 +11,16 @@ class HeaderParser:
1111
1212
1313
"""
14-
def __init__(self, name: str) -> None:
15-
self.name = name
14+
def __init__(self, name: str, predict_next: bool = False) -> None:
15+
self.name: str = name
16+
self.predict_next: bool = False
1617

1718
def parse(self, buffer: Buffer) -> HeaderDescriptor:
1819
raise NotImplementedError
1920

2021

2122
class PacketParser:
22-
"""Abstract Base Class for packet parsers.
23+
"""Base Class for packet parsers.
2324
2425
A packet parser is fed bytearrays and returns a PacketDescriptor.
2526
It may call several header parser to parse the bytearray.
@@ -36,10 +37,8 @@ def parse(self, buffer: Buffer) -> PacketDescriptor:
3637
for parser in self.parsers:
3738
header_descriptor = parser.parse(buffer=buffer)
3839
header_descriptors.append(header_descriptor)
39-
4040
# update buffer to pass on to the next parser
41-
buffer = buffer[header_descriptor.length:]
42-
41+
buffer = buffer[header_descriptor.length:]
4342
packet_fields: List[FieldDescriptor] = []
4443

4544
for header_descriptor in header_descriptors:

microschc/protocol/coap.py

+6-2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
from typing import List, Tuple
2626
from microschc.binary.buffer import Buffer
2727
from microschc.parser import HeaderParser, ParserError
28+
from microschc.protocol.registry import REGISTER_PARSER, ProtocolsIDs
2829
from microschc.rfc8724 import FieldDescriptor, HeaderDescriptor
2930

3031

@@ -74,8 +75,9 @@ class CoAPDefinitions(bytes, Enum):
7475

7576
class CoAPParser(HeaderParser):
7677

77-
def __init__(self, interpret_options=False) -> None:
78+
def __init__(self, predict_next=False, interpret_options=False) -> None:
7879
super().__init__(name=COAP_HEADER_ID)
80+
self.predict_next = predict_next
7981

8082
def match(self, buffer: Buffer) -> bool:
8183
return (buffer.length >= 32)
@@ -231,4 +233,6 @@ def _parse_options(buffer: Buffer) -> Tuple[List[FieldDescriptor], int]:
231233
fields.append(FieldDescriptor(id=CoAPFields.PAYLOAD_MARKER, position=0, value=Buffer(content=b'\xff', length=8)))
232234

233235
# return CoAP fields descriptors list
234-
return (fields, cursor)
236+
return (fields, cursor)
237+
238+
REGISTER_PARSER(protocol_id=ProtocolsIDs.COAP, parser_class=CoAPParser)

microschc/protocol/ipv4.py

+17-2
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,10 @@
1010
"""
1111

1212
from enum import Enum
13-
from typing import List, Tuple
13+
from typing import List, Type
1414
from microschc.binary.buffer import Buffer
1515
from microschc.parser import HeaderParser, ParserError
16+
from microschc.protocol.registry import PARSERS, REGISTER_PARSER, ProtocolsIDs
1617
from microschc.rfc8724 import FieldDescriptor, HeaderDescriptor
1718

1819
IPV4_HEADER_ID = 'IPv4'
@@ -37,6 +38,11 @@ class IPv4Fields(str, Enum):
3738
# OPTION_LENGTH = f'{IPV4_HEADER_ID}:Option Length'
3839
# OPTION_VALUE = f'{IPV4_HEADER_ID}:Option Value'
3940
# PADDING = f'{IPV4_HEADER_ID}:Padding'
41+
42+
IPV4_SUPPORTED_PAYLOAD_PROTOCOLS: List[ProtocolsIDs] = [
43+
ProtocolsIDs.UDP,
44+
ProtocolsIDs.SCTP
45+
]
4046

4147
class IPv4Parser(HeaderParser):
4248

@@ -131,8 +137,16 @@ def parse(self, buffer:bytes) -> HeaderDescriptor:
131137
FieldDescriptor(id=IPv4Fields.DST_ADDRESS, position=0, value=destination_address),
132138
]
133139
)
140+
if self.predict_next is True:
141+
next_header_value: int = protocol.value(type='unsigned int')
142+
if next_header_value in IPV4_SUPPORTED_PAYLOAD_PROTOCOLS:
143+
next_parser_class: Type[HeaderParser] = PARSERS[next_header_value]
144+
next_parser: HeaderParser = next_parser_class(predict_next=True)
145+
next_header_descriptor: HeaderDescriptor = next_parser.parse(buffer[160:])
146+
header_descriptor.fields.extend(next_header_descriptor.fields)
147+
header_descriptor.length += next_header_descriptor.length
134148
return header_descriptor
135-
149+
136150
# def _parse_options(buffer: bytes) -> Tuple[List[FieldDescriptor], int]:
137151
# """
138152
# 0 1 2 3 4 5 6 7
@@ -194,3 +208,4 @@ def parse(self, buffer:bytes) -> HeaderDescriptor:
194208
# cursor += option_offset
195209

196210

211+
REGISTER_PARSER(protocol_id=ProtocolsIDs.IPV4, parser_class=IPv4Parser)

microschc/protocol/ipv6.py

+31-15
Original file line numberDiff line numberDiff line change
@@ -13,29 +13,35 @@
1313

1414
from enum import Enum
1515
from functools import reduce
16-
from typing import Callable, Dict, List, Tuple
16+
from typing import Callable, Dict, List, Tuple, Type
1717
from microschc.binary.buffer import Buffer, Padding
1818
from microschc.parser import HeaderParser, ParserError
1919
from microschc.protocol.compute import ComputeFunctionDependenciesType, ComputeFunctionType
20-
from microschc.rfc8724 import FieldDescriptor, HeaderDescriptor, RuleFieldDescriptor
20+
from microschc.protocol.registry import ProtocolsIDs, REGISTER_PARSER, PARSERS
21+
from microschc.rfc8724 import FieldDescriptor, HeaderDescriptor
2122

22-
IPv6_HEADER_ID = 'IPv6'
23+
IPV6_HEADER_ID = 'IPv6'
2324

2425
class IPv6Fields(str, Enum):
25-
VERSION = f'{IPv6_HEADER_ID}:Version'
26-
TRAFFIC_CLASS = f'{IPv6_HEADER_ID}:Traffic Class'
27-
FLOW_LABEL = f'{IPv6_HEADER_ID}:Flow Label'
28-
PAYLOAD_LENGTH = f'{IPv6_HEADER_ID}:Payload Length'
29-
NEXT_HEADER = f'{IPv6_HEADER_ID}:Next Header'
30-
HOP_LIMIT = f'{IPv6_HEADER_ID}:Hop Limit'
31-
SRC_ADDRESS = f'{IPv6_HEADER_ID}:Source Address'
32-
DST_ADDRESS = f'{IPv6_HEADER_ID}:Destination Address'
33-
26+
VERSION = f'{IPV6_HEADER_ID}:Version'
27+
TRAFFIC_CLASS = f'{IPV6_HEADER_ID}:Traffic Class'
28+
FLOW_LABEL = f'{IPV6_HEADER_ID}:Flow Label'
29+
PAYLOAD_LENGTH = f'{IPV6_HEADER_ID}:Payload Length'
30+
NEXT_HEADER = f'{IPV6_HEADER_ID}:Next Header'
31+
HOP_LIMIT = f'{IPV6_HEADER_ID}:Hop Limit'
32+
SRC_ADDRESS = f'{IPV6_HEADER_ID}:Source Address'
33+
DST_ADDRESS = f'{IPV6_HEADER_ID}:Destination Address'
34+
35+
IPV6_SUPPORTED_PAYLOAD_PROTOCOLS: List[ProtocolsIDs] = [
36+
ProtocolsIDs.UDP,
37+
ProtocolsIDs.SCTP
38+
]
3439

3540
class IPv6Parser(HeaderParser):
3641

37-
def __init__(self) -> None:
38-
super().__init__(name=IPv6_HEADER_ID)
42+
def __init__(self, predict_next:bool=False) -> None:
43+
super().__init__(name=IPV6_HEADER_ID, predict_next=predict_next)
44+
self.predict_next: bool = predict_next
3945

4046
def parse(self, buffer:Buffer) -> HeaderDescriptor:
4147
"""
@@ -87,7 +93,7 @@ def parse(self, buffer:Buffer) -> HeaderDescriptor:
8793
destination_address:Buffer = buffer[192:320]
8894

8995
header_descriptor:HeaderDescriptor = HeaderDescriptor(
90-
id=IPv6_HEADER_ID,
96+
id=IPV6_HEADER_ID,
9197
length=320,
9298
fields=[
9399
FieldDescriptor(id=IPv6Fields.VERSION, position=0, value=version),
@@ -100,6 +106,15 @@ def parse(self, buffer:Buffer) -> HeaderDescriptor:
100106
FieldDescriptor(id=IPv6Fields.DST_ADDRESS, position=0, value=destination_address)
101107
]
102108
)
109+
110+
if self.predict_next is True:
111+
next_header_value: int = next_header.value(type='unsigned int')
112+
if next_header_value in IPV6_SUPPORTED_PAYLOAD_PROTOCOLS:
113+
next_parser_class: Type[HeaderParser] = PARSERS[next_header_value]
114+
next_parser: HeaderParser = next_parser_class(predict_next=True)
115+
next_header_descriptor: HeaderDescriptor = next_parser.parse(buffer[320:])
116+
header_descriptor.fields.extend(next_header_descriptor.fields)
117+
header_descriptor.length += next_header_descriptor.length
103118
return header_descriptor
104119

105120
def _compute_payload_length( decompressed_fields: List[Tuple[str, Buffer]], rule_field_position:int) -> Buffer:
@@ -117,3 +132,4 @@ def _compute_payload_length( decompressed_fields: List[Tuple[str, Buffer]], rule
117132
IPv6Fields.PAYLOAD_LENGTH: (_compute_payload_length, {})
118133
}
119134

135+
REGISTER_PARSER(protocol_id=ProtocolsIDs.IPV6, parser_class=IPv6Parser)

microschc/protocol/registry.py

+33-13
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,50 @@
1-
from microschc.protocol.ipv4 import IPV4_HEADER_ID, IPv4Parser
2-
from microschc.protocol.ipv6 import IPv6_HEADER_ID, IPv6Parser
3-
from microschc.protocol.udp import UDP_HEADER_ID, UDPParser
4-
from microschc.protocol.coap import COAP_HEADER_ID, CoAPParser
1+
from typing import List, Type
2+
# from microschc.protocol.ipv4 import IPV4_HEADER_ID, IPv4Parser
3+
# from microschc.protocol.ipv6 import IPV6_HEADER_ID, IPv6Parser
4+
# from microschc.protocol.udp import UDP_HEADER_ID, UDPParser
5+
# from microschc.protocol.sctp import SCTP_HEADER_ID, SCTPParser
6+
# from microschc.protocol.coap import COAP_HEADER_ID, CoAPParser
7+
58

69
from microschc.parser import PacketParser
710

811
from enum import Enum
912

10-
PROTOCOLS = {
11-
IPV4_HEADER_ID: IPv4Parser,
12-
IPv6_HEADER_ID: IPv6Parser,
13-
UDP_HEADER_ID: UDPParser,
14-
COAP_HEADER_ID: CoAPParser
13+
from microschc.parser.parser import HeaderParser
14+
15+
class ProtocolsIDs(int, Enum):
16+
IPV4 = 4
17+
IPV6 = 6
18+
UDP = 17
19+
SCTP = 132
20+
COAP = 5683
21+
22+
PARSERS = {
23+
# dynamically filled by individual parser modules
24+
# ProtocolsIDs.IPV4: IPv4Parser,
25+
# ProtocolsIDs.IPV6: IPv6Parser,
26+
# ProtocolsIDs.UDP: UDPParser,
27+
# ProtocolsIDs.COAP: CoAPParser,
28+
# ProtocolsIDs.SCTP: SCTPParser
1529
}
1630

31+
def REGISTER_PARSER(protocol_id: int, parser_class: Type[HeaderParser]):
32+
PARSERS[protocol_id] = parser_class
33+
34+
35+
1736
class Stack(str, Enum):
1837
IPV6_UDP_COAP = 'IPv6-UDP-CoAP'
1938
IPV4_UDP_COAP = 'IPv4-UDP-CoAP'
2039

2140
STACKS = {
22-
Stack.IPV6_UDP_COAP: [IPv6Parser, UDPParser, CoAPParser],
23-
Stack.IPV4_UDP_COAP: [IPv4Parser, UDPParser, CoAPParser],
41+
Stack.IPV6_UDP_COAP: [ProtocolsIDs.IPV6, ProtocolsIDs.UDP, ProtocolsIDs.COAP],
42+
Stack.IPV4_UDP_COAP: [ProtocolsIDs.IPV4, ProtocolsIDs.UDP, ProtocolsIDs.COAP],
2443
}
2544

45+
2646
def factory(stack_id: str) -> PacketParser:
27-
protocol_parsers = STACKS[stack_id]
28-
packet_parser: PacketParser = PacketParser(stack_id, [parser_class() for parser_class in protocol_parsers])
47+
protocol_ids: List[ProtocolsIDs] = STACKS[stack_id]
48+
packet_parser: PacketParser = PacketParser(stack_id, [PARSERS[protocol_id]() for protocol_id in protocol_ids])
2949
return packet_parser
3050

microschc/protocol/sctp.py

+22-12
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@
99

1010
from enum import Enum
1111

12+
from microschc.protocol.registry import PARSERS, REGISTER_PARSER, ProtocolsIDs
13+
1214
SCTP_HEADER_ID = 'SCTP'
1315

1416
from enum import Enum
15-
from typing import Dict, List, Tuple
17+
from typing import Dict, List, Tuple, Type
1618
from microschc.binary.buffer import Buffer
1719
from microschc.parser import HeaderParser, ParserError
1820
from microschc.rfc8724 import FieldDescriptor, HeaderDescriptor
@@ -65,6 +67,10 @@ class SCTPFields(str, Enum):
6567
PARAMETER_VALUE = f'{SCTP_HEADER_ID}:Parameter Value'
6668
PARAMETER_PADDING = f'{SCTP_HEADER_ID}:Parameter Padding'
6769

70+
SCTP_SUPPORTED_PAYLOAD_PROTOCOLS: List[ProtocolsIDs] = [
71+
72+
]
73+
6874

6975

7076
class SCTPChunkTypes(int, Enum):
@@ -260,17 +266,23 @@ def _parse_chunk_data(self, buffer: Buffer) -> List[FieldDescriptor]:
260266
stream_identifier_s: Buffer = buffer[32:48]
261267
stream_sequence_number_n: Buffer = buffer[48:64]
262268
payload_protocol_identifier: Buffer = buffer[64:96]
263-
user_data: Buffer = buffer[96:]
264-
265-
fields.extend(
266-
[
269+
270+
fields.extend([
267271
FieldDescriptor(id=SCTPFields.CHUNK_DATA_TSN, value=tsn, position=0),
268272
FieldDescriptor(id=SCTPFields.CHUNK_DATA_STREAM_IDENTIFIER, value=stream_identifier_s, position=0),
269273
FieldDescriptor(id=SCTPFields.CHUNK_DATA_STREAM_SEQUENCE_NUMBER, value=stream_sequence_number_n, position=0),
270-
FieldDescriptor(id=SCTPFields.CHUNK_DATA_PAYLOAD_PROTOCOL_IDENTIFIER, value=payload_protocol_identifier, position=0),
271-
FieldDescriptor(id=SCTPFields.CHUNK_DATA_PAYLOAD, value=user_data, position=0)
272-
]
273-
)
274+
FieldDescriptor(id=SCTPFields.CHUNK_DATA_PAYLOAD_PROTOCOL_IDENTIFIER, value=payload_protocol_identifier, position=0)
275+
])
276+
payload_protocol_identifier_value: int = payload_protocol_identifier.value()
277+
user_data: Buffer = buffer[96:]
278+
if self.predict_next is True and payload_protocol_identifier_value in SCTP_SUPPORTED_PAYLOAD_PROTOCOLS:
279+
next_parser_class: Type[HeaderParser] = PARSERS[payload_protocol_identifier_value]
280+
next_parser: HeaderParser = next_parser_class(predict_next=True)
281+
next_header_descriptor: HeaderDescriptor = next_parser.parse(user_data)
282+
fields.extend(next_header_descriptor.fields)
283+
else:
284+
fields.append(FieldDescriptor(id=SCTPFields.CHUNK_DATA_PAYLOAD, value=user_data, position=0))
285+
274286
return fields
275287

276288
def _parse_chunk_init(self, buffer: Buffer) -> List[FieldDescriptor]:
@@ -619,6 +631,4 @@ def _parse_parameter(self, buffer: Buffer) -> Tuple[List[FieldDescriptor], int]:
619631
)
620632
return fields, parameter_length_value + parameter_padding_length
621633

622-
623-
624-
634+
REGISTER_PARSER(protocol_id=ProtocolsIDs.SCTP, parser_class=SCTPParser)

microschc/protocol/udp.py

+23-5
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,14 @@
99

1010
from enum import Enum
1111
from functools import reduce
12-
from typing import Callable, Dict, Iterator, List, Tuple
12+
from typing import Dict, Iterator, List, Tuple, Type
1313
from microschc.parser import HeaderParser, ParserError
1414
from microschc.protocol.compute import ComputeFunctionDependenciesType, ComputeFunctionType
1515
from microschc.protocol.ipv4 import IPV4_HEADER_ID, IPv4Fields
16-
from microschc.rfc8724 import FieldDescriptor, HeaderDescriptor, RuleFieldDescriptor
16+
from microschc.protocol.registry import PARSERS, REGISTER_PARSER, ProtocolsIDs
17+
from microschc.rfc8724 import FieldDescriptor, HeaderDescriptor
1718
from microschc.binary.buffer import Buffer, Padding
18-
from microschc.protocol.ipv6 import IPv6_HEADER_ID, IPv6Fields
19+
from microschc.protocol.ipv6 import IPV6_HEADER_ID, IPv6Fields
1920

2021
UDP_HEADER_ID = 'UDP'
2122

@@ -24,12 +25,18 @@ class UDPFields(str, Enum):
2425
DESTINATION_PORT = f'{UDP_HEADER_ID}:Destination Port'
2526
LENGTH = f'{UDP_HEADER_ID}:Length'
2627
CHECKSUM = f'{UDP_HEADER_ID}:Checksum'
28+
29+
UDP_SUPPORTED_PAYLOAD_PROTOCOLS: List[ProtocolsIDs] = [
30+
ProtocolsIDs.COAP,
31+
ProtocolsIDs.SCTP
32+
]
2733

2834

2935
class UDPParser(HeaderParser):
3036

31-
def __init__(self) -> None:
37+
def __init__(self, predict_next:bool=False) -> None:
3238
super().__init__(name=UDP_HEADER_ID)
39+
self.predict_next = predict_next
3340

3441
def parse(self, buffer:Buffer) -> HeaderDescriptor:
3542
"""
@@ -71,6 +78,16 @@ def parse(self, buffer:Buffer) -> HeaderDescriptor:
7178
FieldDescriptor(id=UDPFields.CHECKSUM, position=0, value=checksum),
7279
]
7380
)
81+
82+
if self.predict_next is True:
83+
destination_port_value: int = destination_port.value(type='unsigned int')
84+
if destination_port_value in UDP_SUPPORTED_PAYLOAD_PROTOCOLS:
85+
next_parser_class: Type[HeaderParser] = PARSERS[destination_port_value]
86+
next_parser: HeaderParser = next_parser_class(predict_next=True)
87+
next_header_descriptor: HeaderDescriptor = next_parser.parse(buffer[64:])
88+
header_descriptor.fields.extend(next_header_descriptor.fields)
89+
header_descriptor.length += next_header_descriptor.length
90+
7491
return header_descriptor
7592

7693

@@ -159,7 +176,7 @@ def _compute_checksum(decompressed_fields: List[Tuple[str, Buffer]], rule_field_
159176
fields_enumeration_reversed: Iterator[Tuple[int, str]] = enumerate(fields_ids[preceding_protocol_last_position:0:-1])
160177

161178

162-
if IPv6_HEADER_ID in preceding_protocol_last_field:
179+
if IPV6_HEADER_ID in preceding_protocol_last_field:
163180
# build up the pseudo header containing the Source Address, Destination Address, Protocol ID, UDP header + payload length
164181
# 0 31
165182
# +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
@@ -252,3 +269,4 @@ def _compute_checksum(decompressed_fields: List[Tuple[str, Buffer]], rule_field_
252269
IPv4Fields.DST_ADDRESS }),
253270
}
254271

272+
REGISTER_PARSER(protocol_id=ProtocolsIDs.UDP, parser_class=UDPParser)

0 commit comments

Comments
 (0)