Skip to content

Commit b0298c7

Browse files
committed
[wip] SCTP common header, common chunk header and data chunk parsing
1 parent 5d60e89 commit b0298c7

File tree

2 files changed

+393
-0
lines changed

2 files changed

+393
-0
lines changed

microschc/protocol/sctp.py

+261
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
"""
2+
SCTP header declarations
3+
4+
Declarations for the SCTP protocol header as defined in RFC9260 [1].
5+
6+
Note 1: Hop by Hop Options, Routing header parsing is not implemented yet.
7+
Note 2: Fragment header parsing is not implemented as fragmentation and reassembly
8+
are handled by SCHC-RF.
9+
Note 3: Authentication and Encapsulating Security payload parsing is not implemented yet.
10+
11+
[1] "RFC9260: Stream Control Transmission Protocol, R. Stewart et al.
12+
"""
13+
14+
15+
from enum import Enum
16+
17+
SCTP_HEADER_ID = 'SCTP'
18+
19+
class SCTPFields(str, Enum):
20+
SOURCE_PORT_NUMBER = f'{SCTP_HEADER_ID}:Source Port Number'
21+
DESTINATION_PORT_NUMBER = f'{SCTP_HEADER_ID}:Destination Port Number'
22+
VERIFICATION_TAG = f'{SCTP_HEADER_ID}:Verification Tag'
23+
CHECKSUM = f'{SCTP_HEADER_ID}:Checksum'
24+
CHUNK_TYPE = f'{SCTP_HEADER_ID}:Chunk Type'
25+
CHUNK_FLAGS = f'{SCTP_HEADER_ID}:Chunk Flags'
26+
CHUNK_LENGTH = f'{SCTP_HEADER_ID}:Chunk Length'
27+
DATA_RES = f'{SCTP_HEADER_ID}:Data Res'
28+
DATA_I = f'{SCTP_HEADER_ID}:Data I'
29+
DATA_U = f'{SCTP_HEADER_ID}:Data U'
30+
DATA_B = f'{SCTP_HEADER_ID}:Data B'
31+
DATA_E = f'{SCTP_HEADER_ID}:Data E'
32+
DATA_LENGTH = f'{SCTP_HEADER_ID}:Data Length'
33+
DATA_TSN = f'{SCTP_HEADER_ID}:Data TSN'
34+
DATA_STREAM_IDENTIFIER = f'{SCTP_HEADER_ID}:Data Stream Identifier S'
35+
DATA_STREAM_SEQUENCE_NUMBER = f'{SCTP_HEADER_ID}:Data Stream Sequence Number n'
36+
DATA_PAYLOAD_PROTOCOL_IDENTIFIER = f'{SCTP_HEADER_ID}:Data Payload Protocol Identifier'
37+
INIT_CHUNK_FLAGS = f'{SCTP_HEADER_ID}:Chunk Flags'
38+
INIT_CHUNK_LENGTH = f'{SCTP_HEADER_ID}:Chunk Length'
39+
INIT_INITIATE_TAG = f'{SCTP_HEADER_ID}:Initiate Tag'
40+
INIT_ADVERTISED_RECEIVER_WINDOW_CREDIT = f'{SCTP_HEADER_ID}:Advertised Receiver Window Credit'
41+
INIT_NUMBER_OF_OUTBOUND_STREAMS = f'{SCTP_HEADER_ID}:Number of Outbound Streams'
42+
INIT_NUMBER_OF_INBOUND_STREAMS = f'{SCTP_HEADER_ID}:Number of Inbound Streams'
43+
INIT_INITIAL_TSN = f'{SCTP_HEADER_ID}:Initial TSN'
44+
45+
INIT_ACK_CHUNK_FLAGS = f'{SCTP_HEADER_ID}:Chunk Flags'
46+
INIT_ACK_CHUNK_LENGTH = f'{SCTP_HEADER_ID}:Chunk Length'
47+
INIT_ACK_INITIATE_TAG = f'{SCTP_HEADER_ID}:Initiate Tag'
48+
INIT_ACK_ADVERTISED_RECEIVER_WINDOW_CREDIT = f'{SCTP_HEADER_ID}:Advertised Receiver Window Credit'
49+
INIT_ACK_NUMBER_OF_OUTBOUND_STREAMS = f'{SCTP_HEADER_ID}:Number of Outbound Streams'
50+
INIT_ACK_NUMBER_OF_INBOUND_STREAMS = f'{SCTP_HEADER_ID}:Number of Inbound Streams'
51+
INIT_ACK_INITIAL_TSN = f'{SCTP_HEADER_ID}:Initial TSN'
52+
53+
54+
from enum import Enum
55+
from typing import Dict, List, Tuple
56+
from microschc.binary.buffer import Buffer
57+
from microschc.parser import HeaderParser, ParserError
58+
from microschc.rfc8724 import FieldDescriptor, HeaderDescriptor
59+
60+
SCTP_HEADER_ID = 'SCTP'
61+
62+
class SCTPFields(str, Enum):
63+
SOURCE_PORT = f'{SCTP_HEADER_ID}:Source Port'
64+
DESTINATION_PORT = f'{SCTP_HEADER_ID}:Destination Port'
65+
VERIFICATION_TAG = f'{SCTP_HEADER_ID}:Verification Tag'
66+
CHECKSUM = f'{SCTP_HEADER_ID}:Checksum'
67+
CHUNK_TYPE = f'{SCTP_HEADER_ID}:Chunk Type'
68+
CHUNK_FLAGS = f'{SCTP_HEADER_ID}:Chunk Flags'
69+
CHUNK_LENGTH = f'{SCTP_HEADER_ID}:Chunk Length'
70+
CHUNK_VALUE = f'{SCTP_HEADER_ID}:Chunk Value'
71+
CHUNK_PADDING = f'{SCTP_HEADER_ID}:Chunk Padding'
72+
73+
CHUNK_DATA_TSN = f'{SCTP_HEADER_ID}:Data TSN'
74+
CHUNK_DATA_STREAM_IDENTIFIER = f'{SCTP_HEADER_ID}:Data Stream Identifier S'
75+
CHUNK_DATA_STREAM_SEQUENCE_NUMBER = f'{SCTP_HEADER_ID}:Data Stream Sequence Number n'
76+
CHUNK_DATA_PAYLOAD_PROTOCOL_IDENTIFIER = f'{SCTP_HEADER_ID}:Data Payload Protocol Identifier'
77+
CHUNK_DATA_PAYLOAD = f'{SCTP_HEADER_ID}:Data Payload'
78+
79+
class SCTPChunkTypes(int, Enum):
80+
# ID Value Chunk Type
81+
# ----- ----------
82+
# 0 - Payload Data (DATA)
83+
# 1 - Initiation (INIT)
84+
# 2 - Initiation Acknowledgement (INIT ACK)
85+
# 3 - Selective Acknowledgement (SACK)
86+
# 4 - Heartbeat Request (HEARTBEAT)
87+
# 5 - Heartbeat Acknowledgement (HEARTBEAT ACK)
88+
# 6 - Abort (ABORT)
89+
# 7 - Shutdown (SHUTDOWN)
90+
# 8 - Shutdown Acknowledgement (SHUTDOWN ACK)
91+
# 9 - Operation Error (ERROR)
92+
# 10 - State Cookie (COOKIE ECHO)
93+
# 11 - Cookie Acknowledgement (COOKIE ACK)
94+
# 12 - Reserved for Explicit Congestion Notification Echo (ECNE)
95+
# 13 - Reserved for Congestion Window Reduced (CWR)
96+
# 14 - Shutdown Complete (SHUTDOWN COMPLETE)
97+
# 15 to 62 - reserved by IETF
98+
# 63 - IETF-defined Chunk Extensions
99+
# 64 to 126 - reserved by IETF
100+
# 127 - IETF-defined Chunk Extensions
101+
# 128 to 190 - reserved by IETF
102+
# 191 - IETF-defined Chunk Extensions
103+
# 192 to 254 - reserved by IETF
104+
# 255 - IETF-defined Chunk Extensions
105+
DATA = 0
106+
INIT = 1
107+
INIT_ACK = 2
108+
SACK = 3
109+
HEARTBEAT = 4
110+
HEARTBEAT_ACK = 5
111+
ABORT = 6
112+
SHUTDOWN = 7
113+
SHUTDOWN_ACK = 8
114+
ERROR = 9
115+
COOKIE_ECHO = 10
116+
COOKIE_ACK = 11
117+
ECNE = 12
118+
CWR = 13
119+
SHUTDOWN_COMPLETE = 14
120+
121+
122+
123+
class SCTPParser(HeaderParser):
124+
def __init__(self) -> None:
125+
super().__init__(name=SCTP_HEADER_ID)
126+
127+
def match(self, buffer: Buffer) -> bool:
128+
return buffer.length >= 12 * 8 # SCTP header is at least 12 bytes
129+
130+
def parse(self, buffer: Buffer) -> HeaderDescriptor:
131+
"""
132+
0 1 2 3
133+
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
134+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
135+
| Source Port Number | Destination Port Number |
136+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
137+
| Verification Tag |
138+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
139+
| Checksum |
140+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
141+
| |
142+
| Chunk #1 |
143+
| |
144+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
145+
| |
146+
| Chunk #2 |
147+
| |
148+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
149+
"""
150+
if buffer.length < 12 * 8:
151+
raise ParserError(buffer=buffer, message=f'length too short: {buffer.length} < 96')
152+
153+
# Source Port: 16 bits
154+
source_port: Buffer = buffer[0:16]
155+
# Destination Port: 16 bits
156+
destination_port: Buffer = buffer[16:32]
157+
# Verification Tag: 32 bits
158+
verification_tag: Buffer = buffer[32:64]
159+
# Checksum: 32 bits
160+
checksum: Buffer = buffer[64:96]
161+
162+
header_fields: List[FieldDescriptor] = [
163+
FieldDescriptor(id=SCTPFields.SOURCE_PORT, position=0, value=source_port),
164+
FieldDescriptor(id=SCTPFields.DESTINATION_PORT, position=0, value=destination_port),
165+
FieldDescriptor(id=SCTPFields.VERIFICATION_TAG, position=0, value=verification_tag),
166+
FieldDescriptor(id=SCTPFields.CHECKSUM, position=0, value=checksum),
167+
]
168+
169+
chunks: Buffer = buffer[96:]
170+
171+
while chunks.length > 0:
172+
chunks_fields, chunks_bits_consumed = self._parse_chunk(chunks)
173+
chunks = chunks[chunks_bits_consumed:]
174+
header_fields.extend(chunks_fields)
175+
176+
177+
header_descriptor: HeaderDescriptor = HeaderDescriptor(
178+
id=SCTP_HEADER_ID,
179+
length=buffer.length,
180+
fields=header_fields
181+
)
182+
return header_descriptor
183+
184+
185+
def _parse_chunk(self, buffer: Buffer) -> tuple[List[FieldDescriptor], int]:
186+
fields: List[FieldDescriptor] = []
187+
188+
# Chunk Type: 8 bits
189+
chunk_type: Buffer = buffer[0:8]
190+
fields.append(FieldDescriptor(id=SCTPFields.CHUNK_TYPE, position=0, value=chunk_type))
191+
192+
# Chunk Flags: 8 bits
193+
chunk_flags: Buffer = buffer[8:16]
194+
fields.append(FieldDescriptor(id=SCTPFields.CHUNK_FLAGS, position=0, value=chunk_flags))
195+
196+
# Chunk Length: 16 bits
197+
chunk_length: Buffer = buffer[16:32]
198+
fields.append(FieldDescriptor(id=SCTPFields.CHUNK_LENGTH, position=0, value=chunk_length))
199+
200+
chunk_length_value: int = chunk_length.value(type='unsigned int') * 8
201+
202+
# Chunk Value: variable length
203+
chunk_value_length = chunk_length_value - 32 # Length includes the 4 bytes of type, flags, and length
204+
if chunk_value_length > 0:
205+
chunk_type_value: int = chunk_type.value()
206+
chunk_value: Buffer = buffer[32: 32 + chunk_value_length]
207+
208+
if chunk_type_value == SCTPChunkTypes.DATA:
209+
chunk_fields: List[FieldDescriptor] = self._parse_chunk_data(chunk_value)
210+
fields.extend(chunk_fields)
211+
else:
212+
fields.append(FieldDescriptor(id=SCTPFields.CHUNK_VALUE, position=0, value=chunk_value))
213+
214+
chunk_padding_length: int = (32 - chunk_length_value%32)%32
215+
if chunk_padding_length > 0:
216+
chunk_padding: Buffer = buffer[chunk_length_value: chunk_length_value+chunk_padding_length]
217+
fields.append(FieldDescriptor(id=SCTPFields.CHUNK_PADDING, position=0, value=chunk_padding))
218+
219+
220+
bits_consumed = chunk_length_value + chunk_padding_length
221+
222+
return fields, bits_consumed
223+
224+
225+
def _parse_chunk_data(self, buffer: Buffer) -> List[FieldDescriptor]:
226+
"""
227+
0 1 2 3
228+
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
229+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
230+
| Type = 0 | Reserved|U|B|E| Length |
231+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
232+
| TSN |
233+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
234+
| Stream Identifier S | Stream Sequence Number n |
235+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
236+
| Payload Protocol Identifier |
237+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
238+
\ \
239+
/ User Data (seq n of Stream S) /
240+
\ \
241+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
242+
"""
243+
fields: List[FieldDescriptor] = []
244+
tsn: Buffer = buffer[0:32]
245+
stream_identifier_s: Buffer = buffer[32:48]
246+
stream_sequence_number_n: Buffer = buffer[48:64]
247+
payload_protocol_identifier: Buffer = buffer[64:96]
248+
user_data: Buffer = buffer[96:]
249+
250+
fields.extend(
251+
[
252+
FieldDescriptor(id=SCTPFields.CHUNK_DATA_TSN, value=tsn, position=0),
253+
FieldDescriptor(id=SCTPFields.CHUNK_DATA_STREAM_IDENTIFIER, value=stream_identifier_s, position=0),
254+
FieldDescriptor(id=SCTPFields.CHUNK_DATA_STREAM_SEQUENCE_NUMBER, value=stream_sequence_number_n, position=0),
255+
FieldDescriptor(id=SCTPFields.CHUNK_DATA_PAYLOAD_PROTOCOL_IDENTIFIER, value=payload_protocol_identifier, position=0),
256+
FieldDescriptor(id=SCTPFields.CHUNK_DATA_PAYLOAD, value=user_data, position=0)
257+
]
258+
)
259+
return fields
260+
261+

0 commit comments

Comments
 (0)