Skip to content

Commit 6827423

Browse files
committed
[wip] SCTP init chunk parsing and parameters
1 parent 8997158 commit 6827423

File tree

2 files changed

+236
-8
lines changed

2 files changed

+236
-8
lines changed

microschc/protocol/sctp.py

+95-6
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,6 @@
33
44
Declarations for the SCTP protocol header as defined in RFC9260 [1].
55
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-
116
[1] "RFC9260: Stream Control Transmission Protocol, R. Stewart et al.
127
"""
138

@@ -76,6 +71,19 @@ class SCTPFields(str, Enum):
7671
CHUNK_DATA_PAYLOAD_PROTOCOL_IDENTIFIER = f'{SCTP_HEADER_ID}:Data Payload Protocol Identifier'
7772
CHUNK_DATA_PAYLOAD = f'{SCTP_HEADER_ID}:Data Payload'
7873

74+
CHUNK_INIT_INITIATE_TAG = f'{SCTP_HEADER_ID}:Init Initiate Tag'
75+
CHUNK_INIT_ADVERTISED_RECEIVER_WINDOW_CREDIT = f'{SCTP_HEADER_ID}:Init Advertised Receiver Window Credit'
76+
CHUNK_INIT_NUMBER_OF_OUTBOUND_STREAMS = f'{SCTP_HEADER_ID}:Init Number of Outbound Streams'
77+
CHUNK_INIT_NUMBER_OF_INBOUND_STREAMS = f'{SCTP_HEADER_ID}:Init Number of Inbound Streams'
78+
CHUNK_INIT_INITIAL_TSN = f'{SCTP_HEADER_ID}:Init Initial TSN'
79+
80+
PARAMETER_TYPE = f'{SCTP_HEADER_ID}:Parameter Type'
81+
PARAMETER_LENGTH = f'{SCTP_HEADER_ID}:Parameter Length'
82+
PARAMETER_VALUE = f'{SCTP_HEADER_ID}:Parameter Value'
83+
PARAMETER_PADDING = f'{SCTP_HEADER_ID}:Parameter Padding'
84+
85+
86+
7987
class SCTPChunkTypes(int, Enum):
8088
# ID Value Chunk Type
8189
# ----- ----------
@@ -208,6 +216,9 @@ def _parse_chunk(self, buffer: Buffer) -> Tuple[List[FieldDescriptor], int]:
208216
if chunk_type_value == SCTPChunkTypes.DATA:
209217
chunk_fields: List[FieldDescriptor] = self._parse_chunk_data(chunk_value)
210218
fields.extend(chunk_fields)
219+
elif chunk_type_value == SCTPChunkTypes.INIT:
220+
chunk_fields: List[FieldDescriptor] = self._parse_chunk_init(chunk_value)
221+
fields.extend(chunk_fields)
211222
else:
212223
fields.append(FieldDescriptor(id=SCTPFields.CHUNK_VALUE, position=0, value=chunk_value))
213224

@@ -257,5 +268,83 @@ def _parse_chunk_data(self, buffer: Buffer) -> List[FieldDescriptor]:
257268
]
258269
)
259270
return fields
271+
272+
def _parse_chunk_init(self, buffer: Buffer) -> List[FieldDescriptor]:
273+
"""
274+
0 1 2 3
275+
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
276+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
277+
| Type = 1 | Chunk Flags | Chunk Length |
278+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
279+
| Initiate Tag |
280+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
281+
| Advertised Receiver Window Credit (a_rwnd) |
282+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
283+
| Number of Outbound Streams | Number of Inbound Streams |
284+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
285+
| Initial TSN |
286+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
287+
\ \
288+
/ Optional/Variable-Length Parameters /
289+
\ \
290+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
291+
"""
292+
fields: List[FieldDescriptor] = []
293+
initiate_tag: Buffer = buffer[0:32]
294+
advertised_receiver_window_credit: Buffer = buffer[32:64]
295+
number_outbound_streams: Buffer = buffer[64:80]
296+
number_inbound_streams: Buffer = buffer[80:96]
297+
initial_tsn: Buffer = buffer[96:128]
298+
299+
fields.extend([
300+
FieldDescriptor(id=SCTPFields.CHUNK_INIT_INITIATE_TAG, value=initiate_tag, position=0),
301+
FieldDescriptor(id=SCTPFields.CHUNK_INIT_ADVERTISED_RECEIVER_WINDOW_CREDIT, value=advertised_receiver_window_credit, position=0),
302+
FieldDescriptor(id=SCTPFields.CHUNK_INIT_NUMBER_OF_OUTBOUND_STREAMS, value=number_outbound_streams, position=0),
303+
FieldDescriptor(id=SCTPFields.CHUNK_INIT_NUMBER_OF_INBOUND_STREAMS, value=number_inbound_streams, position=0),
304+
FieldDescriptor(id=SCTPFields.CHUNK_INIT_INITIAL_TSN, value=initial_tsn, position=0)
305+
])
306+
307+
parameters = buffer[128:]
308+
while parameters.length > 0:
309+
parameter_fields, bits_consumed = self._parse_parameter(parameters)
310+
fields.extend(parameter_fields)
311+
parameters = parameters[bits_consumed:]
260312

261-
313+
return fields
314+
315+
def _parse_parameter(self, buffer: Buffer) -> Tuple[List[FieldDescriptor], int]:
316+
"""
317+
0 1 2 3
318+
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
319+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
320+
| Parameter Type | Parameter Length |
321+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
322+
\ \
323+
/ Parameter Value /
324+
\ \
325+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
326+
"""
327+
fields: List[FieldDescriptor] = []
328+
parameter_type: Buffer = buffer[0:16]
329+
parameter_length: Buffer = buffer[16:32]
330+
331+
fields.extend([
332+
FieldDescriptor(id=SCTPFields.PARAMETER_TYPE, value=parameter_type, position=0),
333+
FieldDescriptor(id=SCTPFields.PARAMETER_LENGTH, value=parameter_length, position=0),
334+
])
335+
336+
parameter_length_value: int = parameter_length.value() * 8
337+
parameter_value_length: int = parameter_length_value - 32
338+
if parameter_value_length > 0:
339+
parameter_value: Buffer = buffer[32: parameter_length_value]
340+
fields.append(FieldDescriptor(id=SCTPFields.PARAMETER_VALUE, value=parameter_value, position=0))
341+
342+
parameter_padding_length: int = (32 - parameter_value_length%32)%32
343+
if parameter_padding_length > 0:
344+
parameter_padding: Buffer = buffer[parameter_length_value: parameter_length_value + parameter_padding_length]
345+
fields.append(
346+
FieldDescriptor(id=SCTPFields.PARAMETER_PADDING, value=parameter_padding, position=0)
347+
)
348+
return fields, parameter_length_value + parameter_padding_length
349+
350+

tests/protocol/test_sctp.py

+141-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ def test_sctp_parser_import():
1111
parser = SCTPParser()
1212
assert( isinstance(parser, SCTPParser) )
1313

14-
def test_sctp_parser_parse():
14+
def test_sctp_parser_parse_data():
1515
"""test: SCTP header parser parses SCTP Header
1616
1717
The packet is made of a SCTP header with the following fields:
@@ -129,4 +129,143 @@ def test_sctp_parser_parse():
129129
chunk_padding_fd:FieldDescriptor = sctp_header_descriptor.fields[12]
130130
assert chunk_padding_fd.id == SCTPFields.CHUNK_PADDING
131131
assert chunk_padding_fd.position == 0
132-
assert chunk_padding_fd.value == Buffer(content=b'\x00\x00\x00', length=24)
132+
assert chunk_padding_fd.value == Buffer(content=b'\x00\x00\x00', length=24)
133+
134+
135+
def test_sctp_parser_parse_init():
136+
"""test: SCTP header parser parses SCTP Header
137+
138+
The packet is made of a SCTP header with the following fields:
139+
- id='Source Port Number' length=16 position=0 value=b'\x00\x07'
140+
- id='Destination Port Number' length=16 position=0 value=b'\x00\x07'
141+
- id='Verification Tag' length=32 position=0 value=b'\x00\x00\x00\x00'
142+
- id='Checksum' length=32 position=0 value=b'\x37\x61\xa7\x46'
143+
- id='Chunk Type' length=8 position=0 value=b'\x01'
144+
- id='Chunk Flags' length=8 position=0 value=b'\x00'
145+
- id='Chunk Length' length=16 position=0 value=b'\x00\x20'
146+
- id='Initiate Tag length=32 position=0 value=b'\x43\x23\x25\x44'
147+
- id='Advertised Receiver Window Credit' length=32 position=0 value=b'\x00\x00\xff\xff'
148+
- id='Number of Outbound Streams' length=16 position=0 value=b'\x00\x11'
149+
- id='Number of Inbound Streams' length=16 position=0 value=b'\x00\x11'
150+
- id='Initial TSN length=32 position=0 value=b'\x5c\xfe\x37\x9f'
151+
- id='Parameter Type' length=16 position=0 value=b'\xc0\x00'
152+
- id='Parameter Length' length=16 position=0 value=b'\x00\x04'
153+
- id='Parameter Type' length=16 position=0 value=b'\x00\x0c'
154+
- id='Parameter Length' length=16 position=0 value=b'\x00\x06'
155+
- id='Parameter Value' length=16 position=0 value=b'\x00\x05'
156+
- id='Parameter Padding' length=16 position=0 value=b'\x00\x00'
157+
158+
"""
159+
160+
valid_sctp_packet:bytes = bytes(b'\x00\x07\x00\x07\x00\x00\x00\x00\x37\x61\xa7\x46\x01\x00\x00\x20'
161+
b'\x43\x23\x25\x44\x00\x00\xff\xff\x00\x11\x00\x11\x5c\xfe\x37\x9f'
162+
b'\xc0\x00\x00\x04\x00\x0c\x00\x06\x00\x05\x00\x00'
163+
)
164+
valid_sctp_packet_buffer: Buffer = Buffer(content=valid_sctp_packet, length=len(valid_sctp_packet)*8)
165+
parser:SCTPParser = SCTPParser()
166+
167+
sctp_header_descriptor: HeaderDescriptor = parser.parse(buffer=valid_sctp_packet_buffer)
168+
169+
# test sctp_header_descriptor type
170+
assert isinstance(sctp_header_descriptor, HeaderDescriptor)
171+
172+
# test for sctp_header_descriptor.fields length
173+
assert len(sctp_header_descriptor.fields) == 18
174+
175+
# test for sctp_header_descriptor.fields types
176+
for field in sctp_header_descriptor.fields:
177+
assert isinstance(field, FieldDescriptor)
178+
179+
# assert field descriptors match SCTP header content
180+
# - common header fields
181+
source_port_fd:FieldDescriptor = sctp_header_descriptor.fields[0]
182+
assert source_port_fd.id == SCTPFields.SOURCE_PORT
183+
assert source_port_fd.position == 0
184+
assert source_port_fd.value == Buffer(content=b'\x00\x07', length=16)
185+
186+
destination_port_fd:FieldDescriptor = sctp_header_descriptor.fields[1]
187+
assert destination_port_fd.id == SCTPFields.DESTINATION_PORT
188+
assert destination_port_fd.position == 0
189+
assert destination_port_fd.value == Buffer(content=b'\x00\x07', length=16)
190+
191+
verification_tag_fd:FieldDescriptor = sctp_header_descriptor.fields[2]
192+
assert verification_tag_fd.id == SCTPFields.VERIFICATION_TAG
193+
assert verification_tag_fd.position == 0
194+
assert verification_tag_fd.value == Buffer(content=b'\x00\x00\x00\x00', length=32)
195+
196+
checksum_fd:FieldDescriptor = sctp_header_descriptor.fields[3]
197+
assert checksum_fd.id == SCTPFields.CHECKSUM
198+
assert checksum_fd.position == 0
199+
assert checksum_fd.value == Buffer(content=b'\x37\x61\xa7\x46', length=32)
200+
201+
202+
# - chunk common header fields
203+
chunk_type_fd:FieldDescriptor = sctp_header_descriptor.fields[4]
204+
assert chunk_type_fd.id == SCTPFields.CHUNK_TYPE
205+
assert chunk_type_fd.position == 0
206+
assert chunk_type_fd.value == Buffer(content=b'\x01', length=8)
207+
208+
chunk_flags_fd:FieldDescriptor = sctp_header_descriptor.fields[5]
209+
assert chunk_flags_fd.id == SCTPFields.CHUNK_FLAGS
210+
assert chunk_flags_fd.position == 0
211+
assert chunk_flags_fd.value == Buffer(content=b'\x00', length=8)
212+
213+
chunk_length_fd:FieldDescriptor = sctp_header_descriptor.fields[6]
214+
assert chunk_length_fd.id == SCTPFields.CHUNK_LENGTH
215+
assert chunk_length_fd.position == 0
216+
assert chunk_length_fd.value == Buffer(content=b'\x00\x20', length=16)
217+
218+
initiate_tag_fd:FieldDescriptor = sctp_header_descriptor.fields[7]
219+
assert initiate_tag_fd.id == SCTPFields.CHUNK_INIT_INITIATE_TAG
220+
assert initiate_tag_fd.position == 0
221+
assert initiate_tag_fd.value == Buffer(content=b'\x43\x23\x25\x44', length=32)
222+
223+
advertised_receiver_window_credit_fd:FieldDescriptor = sctp_header_descriptor.fields[8]
224+
assert advertised_receiver_window_credit_fd.id == SCTPFields.CHUNK_INIT_ADVERTISED_RECEIVER_WINDOW_CREDIT
225+
assert advertised_receiver_window_credit_fd.position == 0
226+
assert advertised_receiver_window_credit_fd.value == Buffer(content=b'\x00\x00\xff\xff', length=32)
227+
228+
number_outbound_streams_fd:FieldDescriptor = sctp_header_descriptor.fields[9]
229+
assert number_outbound_streams_fd.id == SCTPFields.CHUNK_INIT_NUMBER_OF_OUTBOUND_STREAMS
230+
assert number_outbound_streams_fd.position == 0
231+
assert number_outbound_streams_fd.value == Buffer(content=b'\x00\x11', length=16)
232+
233+
number_inbound_streams_fd:FieldDescriptor = sctp_header_descriptor.fields[10]
234+
assert number_inbound_streams_fd.id == SCTPFields.CHUNK_INIT_NUMBER_OF_INBOUND_STREAMS
235+
assert number_inbound_streams_fd.position == 0
236+
assert number_inbound_streams_fd.value == Buffer(content=b'\x00\x11', length=16)
237+
238+
initial_tsn_fd:FieldDescriptor = sctp_header_descriptor.fields[11]
239+
assert initial_tsn_fd.id == SCTPFields.CHUNK_INIT_INITIAL_TSN
240+
assert initial_tsn_fd.position == 0
241+
assert initial_tsn_fd.value == Buffer(content=b'\x5c\xfe\x37\x9f', length=32)
242+
243+
parameter_type_fd:FieldDescriptor = sctp_header_descriptor.fields[12]
244+
assert parameter_type_fd.id == SCTPFields.PARAMETER_TYPE
245+
assert parameter_type_fd.position == 0
246+
assert parameter_type_fd.value == Buffer(content=b'\xc0\x00', length=16)
247+
248+
parameter_length_fd:FieldDescriptor = sctp_header_descriptor.fields[13]
249+
assert parameter_length_fd.id == SCTPFields.PARAMETER_LENGTH
250+
assert parameter_length_fd.position == 0
251+
assert parameter_length_fd.value == Buffer(content=b'\x00\x04', length=16)
252+
253+
parameter_type_fd:FieldDescriptor = sctp_header_descriptor.fields[14]
254+
assert parameter_type_fd.id == SCTPFields.PARAMETER_TYPE
255+
assert parameter_type_fd.position == 0
256+
assert parameter_type_fd.value == Buffer(content=b'\x00\x0c', length=16)
257+
258+
parameter_length_fd:FieldDescriptor = sctp_header_descriptor.fields[15]
259+
assert parameter_length_fd.id == SCTPFields.PARAMETER_LENGTH
260+
assert parameter_length_fd.position == 0
261+
assert parameter_length_fd.value == Buffer(content=b'\x00\x06', length=16)
262+
263+
parameter_value_fd:FieldDescriptor = sctp_header_descriptor.fields[16]
264+
assert parameter_value_fd.id == SCTPFields.PARAMETER_VALUE
265+
assert parameter_value_fd.position == 0
266+
assert parameter_value_fd.value == Buffer(content=b'\x00\x05', length=16)
267+
268+
parameter_padding_fd:FieldDescriptor = sctp_header_descriptor.fields[17]
269+
assert parameter_padding_fd.id == SCTPFields.PARAMETER_PADDING
270+
assert parameter_padding_fd.position == 0
271+
assert parameter_padding_fd.value == Buffer(content=b'\x00\x00', length=16)

0 commit comments

Comments
 (0)