Skip to content

Commit a0793fd

Browse files
committed
[wip] SCTO selective ack chunk parsing (gap blocks and duplicate tsns not tested)
1 parent a76243c commit a0793fd

File tree

2 files changed

+177
-5
lines changed

2 files changed

+177
-5
lines changed

microschc/protocol/sctp.py

+82-5
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,14 @@ class SCTPFields(str, Enum):
4747
CHUNK_INIT_ACK_NUMBER_OF_OUTBOUND_STREAMS = f'{SCTP_HEADER_ID}:Init Ack Number of Outbound Streams'
4848
CHUNK_INIT_ACK_NUMBER_OF_INBOUND_STREAMS = f'{SCTP_HEADER_ID}:Init Ack Number of Inbound Streams'
4949
CHUNK_INIT_ACK_INITIAL_TSN = f'{SCTP_HEADER_ID}:Init Ack Initial TSN'
50+
51+
CHUNK_SACK_CUMULATIVE_TSN_ACK = f'{SCTP_HEADER_ID}:Selective Ack Cumulative TSN Ack'
52+
CHUNK_SACK_ADVERTISED_RECEIVER_WINDOW_CREDIT = f'{SCTP_HEADER_ID}:Selective Ack Advertised Receiver Window Credit'
53+
CHUNK_SACK_NUMBER_GAP_ACK_BLOCKS = f'{SCTP_HEADER_ID}:Selective Ack Number Gap Ack Blocks'
54+
CHUNK_SACK_NUMBER_DUPLICATE_TSNS = f'{SCTP_HEADER_ID}:Selective Ack Number Duplicate TSNs'
55+
CHUNK_SACK_GAP_ACK_BLOCK_START = f'{SCTP_HEADER_ID}:Selective Ack Gap Ack BLock Start'
56+
CHUNK_SACK_GAP_ACK_BLOCK_END = f'{SCTP_HEADER_ID}:Selective Ack Gap Ack BLock End'
57+
CHUNK_SACK_DUPLICATE_TSN = f'{SCTP_HEADER_ID}:Selective Ack Duplicate TSN'
5058

5159
PARAMETER_TYPE = f'{SCTP_HEADER_ID}:Parameter Type'
5260
PARAMETER_LENGTH = f'{SCTP_HEADER_ID}:Parameter Length'
@@ -186,16 +194,17 @@ def _parse_chunk(self, buffer: Buffer) -> Tuple[List[FieldDescriptor], int]:
186194

187195
if chunk_type_value == SCTPChunkTypes.DATA:
188196
chunk_fields: List[FieldDescriptor] = self._parse_chunk_data(chunk_value)
189-
fields.extend(chunk_fields)
197+
190198
elif chunk_type_value == SCTPChunkTypes.INIT:
191199
chunk_fields: List[FieldDescriptor] = self._parse_chunk_init(chunk_value)
192-
fields.extend(chunk_fields)
193200
elif chunk_type_value == SCTPChunkTypes.INIT_ACK:
194201
chunk_fields: List[FieldDescriptor] = self._parse_chunk_init_ack(chunk_value)
195-
fields.extend(chunk_fields)
202+
elif chunk_type_value == SCTPChunkTypes.SACK:
203+
chunk_fields: List[FieldDescriptor] = self._parse_chunk_selective_ack(chunk_value)
196204
else:
197-
fields.append(FieldDescriptor(id=SCTPFields.CHUNK_VALUE, position=0, value=chunk_value))
198-
205+
chunk_fields: List[FieldDescriptor] = [FieldDescriptor(id=SCTPFields.CHUNK_VALUE, position=0, value=chunk_value)]
206+
fields.extend(chunk_fields)
207+
199208
chunk_padding_length: int = (32 - chunk_length_value%32)%32
200209
if chunk_padding_length > 0:
201210
chunk_padding: Buffer = buffer[chunk_length_value: chunk_length_value+chunk_padding_length]
@@ -328,6 +337,74 @@ def _parse_chunk_init_ack(self, buffer: Buffer) -> List[FieldDescriptor]:
328337
parameters = parameters[bits_consumed:]
329338

330339
return fields
340+
341+
342+
def _parse_chunk_selective_ack(self, buffer: Buffer) -> List[FieldDescriptor]:
343+
"""
344+
0 1 2 3
345+
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
346+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
347+
| Type = 3 | Chunk Flags | Chunk Length |
348+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
349+
| Cumulative TSN Ack |
350+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
351+
| Advertised Receiver Window Credit (a_rwnd) |
352+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
353+
| Number of Gap Ack Blocks = N | Number of Duplicate TSNs = M |
354+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
355+
| Gap Ack Block #1 Start | Gap Ack Block #1 End |
356+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
357+
/ /
358+
\ ... \
359+
/ /
360+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
361+
| Gap Ack Block #N Start | Gap Ack Block #N End |
362+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
363+
| Duplicate TSN 1 |
364+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
365+
/ /
366+
\ ... \
367+
/ /
368+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
369+
| Duplicate TSN M |
370+
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
371+
"""
372+
fields: List[FieldDescriptor] = []
373+
cumulative_tsn_ack: Buffer = buffer[0:32]
374+
advertised_receiver_window_credit: Buffer = buffer[32:64]
375+
number_gap_ack_blocks: Buffer = buffer[64:80]
376+
number_duplicate_tsns: Buffer = buffer[80:96]
377+
378+
fields.extend([
379+
FieldDescriptor(id=SCTPFields.CHUNK_SACK_CUMULATIVE_TSN_ACK, value=cumulative_tsn_ack, position=0),
380+
FieldDescriptor(id=SCTPFields.CHUNK_SACK_ADVERTISED_RECEIVER_WINDOW_CREDIT, value=advertised_receiver_window_credit, position=0),
381+
FieldDescriptor(id=SCTPFields.CHUNK_SACK_NUMBER_GAP_ACK_BLOCKS, value=number_gap_ack_blocks, position=0),
382+
FieldDescriptor(id=SCTPFields.CHUNK_SACK_NUMBER_DUPLICATE_TSNS, value=number_duplicate_tsns, position=0)
383+
])
384+
385+
remainer: Buffer = buffer[96:]
386+
# Gap ack Blocks
387+
number_gap_ack_blocks_value: int = number_gap_ack_blocks.value()
388+
389+
for _ in range(number_gap_ack_blocks_value):
390+
gap_ack_block_start: Buffer = remainer[0:16]
391+
gap_ack_block_end: Buffer = remainer[16:32]
392+
fields.extend([
393+
FieldDescriptor(id=SCTPFields.CHUNK_SACK_GAP_ACK_BLOCK_START, value=gap_ack_block_start),
394+
FieldDescriptor(id=SCTPFields.CHUNK_SACK_GAP_ACK_BLOCK_END, value=gap_ack_block_end)
395+
])
396+
remainer = remainer[32:]
397+
398+
# Duplicate TSNs
399+
number_duplicate_tsns_value: int = number_duplicate_tsns.value()
400+
for _ in range(number_duplicate_tsns_value):
401+
duplicate_tsn: Buffer = remainer[0:32]
402+
fields.extend([
403+
FieldDescriptor(id=SCTPFields.CHUNK_SACK_DUPLICATE_TSN, value=duplicate_tsn),
404+
])
405+
remainer = remainer[32:]
406+
407+
return fields
331408

332409
def _parse_parameter(self, buffer: Buffer) -> Tuple[List[FieldDescriptor], int]:
333410
"""

tests/protocol/test_sctp.py

+95
Original file line numberDiff line numberDiff line change
@@ -424,5 +424,100 @@ def test_sctp_parser_parse_init_ack():
424424
assert parameter_length_fd.id == SCTPFields.PARAMETER_LENGTH
425425
assert parameter_length_fd.position == 0
426426
assert parameter_length_fd.value == Buffer(content=b'\x00\x04', length=16)
427+
428+
429+
def test_sctp_parser_selective_ack():
430+
"""test: SCTP header parser parses SCTP Header with SACK chunk
431+
432+
The packet is made of a SCTP header with the following fields:
433+
- id='Source Port Number' length=16 position=0 value=b'\x00\x07'
434+
- id='Destination Port Number' length=16 position=0 value=b'\x00\x07'
435+
- id='Verification Tag' length=32 position=0 value=b'\x00\x00\x0e\xb0'
436+
- id='Checksum' length=32 position=0 value=b'\xba\x04\x32\x58'
437+
- id='Chunk Type' length=8 position=0 value=b'\x03'
438+
- id='Chunk Flags' length=8 position=0 value=b'\x00'
439+
- id='Chunk Length' length=16 position=0 value=b'\x00\x10'
440+
- id='Cumulative TSN Ack' length=32 position=0 value=b'\x00\x00\x36\x1c'
441+
- id='Advertised Receiver Window Credit' length=32 position=0 value=b'\x00\x00\xff\xff'
442+
- id='Number of Gap Ack Blocks' length=16 position=0 value=b'\x00\x00'
443+
- id='Number of Duplicate TSNs' length=16 position=0 value=b'\x00\x00'
444+
"""
445+
#TODO: Find SACK chunk with Gap Ack Blocks and Duplicate TSNs.
446+
447+
valid_sctp_packet:bytes = bytes(b'\x00\x07\x00\x07\x00\x00\x0e\xb0\xba\x04\x32\x58\x03\x00\x00\x10'
448+
b'\x00\x00\x36\x1c\x00\x00\xff\xff\x00\x00\x00\x00')
449+
valid_sctp_packet_buffer: Buffer = Buffer(content=valid_sctp_packet, length=len(valid_sctp_packet)*8)
450+
parser:SCTPParser = SCTPParser()
451+
452+
sctp_header_descriptor: HeaderDescriptor = parser.parse(buffer=valid_sctp_packet_buffer)
453+
454+
# test sctp_header_descriptor type
455+
assert isinstance(sctp_header_descriptor, HeaderDescriptor)
456+
457+
# test for sctp_header_descriptor.fields length
458+
assert len(sctp_header_descriptor.fields) == 11
459+
460+
# test for sctp_header_descriptor.fields types
461+
for field in sctp_header_descriptor.fields:
462+
assert isinstance(field, FieldDescriptor)
463+
464+
# assert field descriptors match SCTP header content
465+
# - common header fields
466+
source_port_fd:FieldDescriptor = sctp_header_descriptor.fields[0]
467+
assert source_port_fd.id == SCTPFields.SOURCE_PORT
468+
assert source_port_fd.position == 0
469+
assert source_port_fd.value == Buffer(content=b'\x00\x07', length=16)
470+
471+
destination_port_fd:FieldDescriptor = sctp_header_descriptor.fields[1]
472+
assert destination_port_fd.id == SCTPFields.DESTINATION_PORT
473+
assert destination_port_fd.position == 0
474+
assert destination_port_fd.value == Buffer(content=b'\x00\x07', length=16)
475+
476+
verification_tag_fd:FieldDescriptor = sctp_header_descriptor.fields[2]
477+
assert verification_tag_fd.id == SCTPFields.VERIFICATION_TAG
478+
assert verification_tag_fd.position == 0
479+
assert verification_tag_fd.value == Buffer(content=b'\x00\x00\x0e\xb0', length=32)
480+
481+
checksum_fd:FieldDescriptor = sctp_header_descriptor.fields[3]
482+
assert checksum_fd.id == SCTPFields.CHECKSUM
483+
assert checksum_fd.position == 0
484+
assert checksum_fd.value == Buffer(content=b'\xba\x04\x32\x58', length=32)
485+
486+
487+
# - chunk common header fields
488+
chunk_type_fd:FieldDescriptor = sctp_header_descriptor.fields[4]
489+
assert chunk_type_fd.id == SCTPFields.CHUNK_TYPE
490+
assert chunk_type_fd.position == 0
491+
assert chunk_type_fd.value == Buffer(content=b'\x03', length=8)
492+
493+
chunk_flags_fd:FieldDescriptor = sctp_header_descriptor.fields[5]
494+
assert chunk_flags_fd.id == SCTPFields.CHUNK_FLAGS
495+
assert chunk_flags_fd.position == 0
496+
assert chunk_flags_fd.value == Buffer(content=b'\x00', length=8)
497+
498+
chunk_length_fd:FieldDescriptor = sctp_header_descriptor.fields[6]
499+
assert chunk_length_fd.id == SCTPFields.CHUNK_LENGTH
500+
assert chunk_length_fd.position == 0
501+
assert chunk_length_fd.value == Buffer(content=b'\x00\x10', length=16)
502+
503+
cumulative_tsn_ack_fd:FieldDescriptor = sctp_header_descriptor.fields[7]
504+
assert cumulative_tsn_ack_fd.id == SCTPFields.CHUNK_SACK_CUMULATIVE_TSN_ACK
505+
assert cumulative_tsn_ack_fd.position == 0
506+
assert cumulative_tsn_ack_fd.value == Buffer(content=b'\x00\x00\x36\x1c', length=32)
507+
508+
advertised_receiver_window_credit_fd:FieldDescriptor = sctp_header_descriptor.fields[8]
509+
assert advertised_receiver_window_credit_fd.id == SCTPFields.CHUNK_SACK_ADVERTISED_RECEIVER_WINDOW_CREDIT
510+
assert advertised_receiver_window_credit_fd.position == 0
511+
assert advertised_receiver_window_credit_fd.value == Buffer(content=b'\x00\x00\xff\xff', length=32)
512+
513+
number_gap_ack_blocks_fd:FieldDescriptor = sctp_header_descriptor.fields[9]
514+
assert number_gap_ack_blocks_fd.id == SCTPFields.CHUNK_SACK_NUMBER_GAP_ACK_BLOCKS
515+
assert number_gap_ack_blocks_fd.position == 0
516+
assert number_gap_ack_blocks_fd.value == Buffer(content=b'\x00\x00', length=16)
517+
518+
number_inbound_streams_fd:FieldDescriptor = sctp_header_descriptor.fields[10]
519+
assert number_inbound_streams_fd.id == SCTPFields.CHUNK_SACK_NUMBER_DUPLICATE_TSNS
520+
assert number_inbound_streams_fd.position == 0
521+
assert number_inbound_streams_fd.value == Buffer(content=b'\x00\x00', length=16)
427522

428523

0 commit comments

Comments
 (0)