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