sctp_proto/config.rs
1use crate::util::{AssociationIdGenerator, RandomAssociationIdGenerator};
2
3use alloc::boxed::Box;
4use alloc::sync::Arc;
5use bytes::Bytes;
6use core::fmt;
7
8/// MTU for inbound packet (from DTLS)
9pub(crate) const RECEIVE_MTU: usize = 8192;
10/// initial MTU for outgoing packets (to DTLS)
11pub(crate) const INITIAL_MTU: u32 = 1228;
12pub(crate) const INITIAL_RECV_BUF_SIZE: u32 = 1024 * 1024;
13pub(crate) const COMMON_HEADER_SIZE: u32 = 12;
14pub(crate) const DATA_CHUNK_HEADER_SIZE: u32 = 16;
15pub(crate) const DEFAULT_MAX_MESSAGE_SIZE: u32 = 65536;
16
17// Default RTO values in milliseconds (RFC 4960)
18pub(crate) const RTO_INITIAL: u64 = 3000;
19pub(crate) const RTO_MIN: u64 = 1000;
20pub(crate) const RTO_MAX: u64 = 60000;
21
22// Default max retransmit value (RFC 4960 Section 15)
23const DEFAULT_MAX_INIT_RETRANS: usize = 8;
24
25/// Config collects the arguments to create_association construction into
26/// a single structure
27#[derive(Debug)]
28pub struct TransportConfig {
29 max_receive_buffer_size: u32,
30 max_num_outbound_streams: u16,
31 max_num_inbound_streams: u16,
32
33 /// Maximum message size we will SEND (respects remote's advertised limit)
34 /// Can be updated after association creation via set_max_send_message_size()
35 max_send_message_size: u32,
36
37 /// Maximum message size we will RECEIVE (what we advertise in SDP)
38 /// Enforced during reassembly - messages exceeding this are rejected
39 max_receive_message_size: u32,
40
41 /// Maximum number of retransmissions for INIT chunks during handshake.
42 /// Set to `None` for unlimited retries (recommended for WebRTC).
43 /// Default: Some(8)
44 max_init_retransmits: Option<usize>,
45
46 /// Maximum number of retransmissions for DATA chunks.
47 /// Set to `None` for unlimited retries (recommended for WebRTC).
48 /// Default: None (unlimited)
49 max_data_retransmits: Option<usize>,
50
51 /// Initial retransmission timeout in milliseconds.
52 /// Default: 3000
53 rto_initial_ms: u64,
54
55 /// Minimum retransmission timeout in milliseconds.
56 /// Default: 1000
57 rto_min_ms: u64,
58
59 /// Maximum retransmission timeout in milliseconds.
60 /// Default: 60000
61 rto_max_ms: u64,
62}
63
64impl Default for TransportConfig {
65 fn default() -> Self {
66 TransportConfig {
67 max_receive_buffer_size: INITIAL_RECV_BUF_SIZE,
68 max_send_message_size: DEFAULT_MAX_MESSAGE_SIZE,
69 max_receive_message_size: DEFAULT_MAX_MESSAGE_SIZE,
70 max_num_outbound_streams: u16::MAX,
71 max_num_inbound_streams: u16::MAX,
72 max_init_retransmits: Some(DEFAULT_MAX_INIT_RETRANS),
73 max_data_retransmits: None,
74 rto_initial_ms: RTO_INITIAL,
75 rto_min_ms: RTO_MIN,
76 rto_max_ms: RTO_MAX,
77 }
78 }
79}
80
81impl TransportConfig {
82 pub fn with_max_receive_buffer_size(mut self, value: u32) -> Self {
83 self.max_receive_buffer_size = value;
84 self
85 }
86
87 pub fn with_max_send_message_size(mut self, value: u32) -> Self {
88 self.max_send_message_size = value;
89 self
90 }
91
92 /// Set maximum size of messages we will accept
93 pub fn with_max_receive_message_size(mut self, value: u32) -> Self {
94 self.max_receive_message_size = value;
95 self
96 }
97
98 #[deprecated(note = "Use with_max_send_message_size instead")]
99 pub fn with_max_message_size(self, value: u32) -> Self {
100 self.with_max_send_message_size(value)
101 }
102
103 pub fn with_max_num_outbound_streams(mut self, value: u16) -> Self {
104 self.max_num_outbound_streams = value;
105 self
106 }
107
108 pub fn with_max_num_inbound_streams(mut self, value: u16) -> Self {
109 self.max_num_inbound_streams = value;
110 self
111 }
112
113 pub(crate) fn max_receive_buffer_size(&self) -> u32 {
114 self.max_receive_buffer_size
115 }
116
117 pub(crate) fn max_send_message_size(&self) -> u32 {
118 self.max_send_message_size
119 }
120
121 pub(crate) fn max_receive_message_size(&self) -> u32 {
122 self.max_receive_message_size
123 }
124
125 pub(crate) fn max_num_outbound_streams(&self) -> u16 {
126 self.max_num_outbound_streams
127 }
128
129 pub(crate) fn max_num_inbound_streams(&self) -> u16 {
130 self.max_num_inbound_streams
131 }
132
133 /// Set maximum INIT retransmissions. `None` means unlimited.
134 pub fn with_max_init_retransmits(mut self, value: Option<usize>) -> Self {
135 self.max_init_retransmits = value;
136 self
137 }
138
139 /// Set maximum DATA retransmissions. `None` means unlimited.
140 pub fn with_max_data_retransmits(mut self, value: Option<usize>) -> Self {
141 self.max_data_retransmits = value;
142 self
143 }
144
145 /// Set initial RTO in milliseconds.
146 pub fn with_rto_initial_ms(mut self, value: u64) -> Self {
147 self.rto_initial_ms = value;
148 self
149 }
150
151 /// Set minimum RTO in milliseconds.
152 pub fn with_rto_min_ms(mut self, value: u64) -> Self {
153 self.rto_min_ms = value;
154 self
155 }
156
157 /// Set maximum RTO in milliseconds.
158 pub fn with_rto_max_ms(mut self, value: u64) -> Self {
159 self.rto_max_ms = value;
160 self
161 }
162
163 pub(crate) fn max_init_retransmits(&self) -> Option<usize> {
164 self.max_init_retransmits
165 }
166
167 pub(crate) fn max_data_retransmits(&self) -> Option<usize> {
168 self.max_data_retransmits
169 }
170
171 pub(crate) fn rto_initial_ms(&self) -> u64 {
172 self.rto_initial_ms
173 }
174
175 pub(crate) fn rto_min_ms(&self) -> u64 {
176 self.rto_min_ms
177 }
178
179 pub(crate) fn rto_max_ms(&self) -> u64 {
180 self.rto_max_ms
181 }
182}
183
184/// Global configuration for the endpoint, affecting all associations
185///
186/// Default values should be suitable for most internet applications.
187#[derive(Clone)]
188pub struct EndpointConfig {
189 pub(crate) max_payload_size: u32,
190
191 /// AID generator factory
192 ///
193 /// Create a aid generator for local aid in Endpoint struct
194 pub(crate) aid_generator_factory:
195 Arc<dyn Fn() -> Box<dyn AssociationIdGenerator> + Send + Sync>,
196}
197
198impl Default for EndpointConfig {
199 fn default() -> Self {
200 Self::new()
201 }
202}
203
204impl EndpointConfig {
205 /// Create a default config
206 pub fn new() -> Self {
207 let aid_factory: fn() -> Box<dyn AssociationIdGenerator> =
208 || Box::<RandomAssociationIdGenerator>::default();
209 Self {
210 max_payload_size: INITIAL_MTU - (COMMON_HEADER_SIZE + DATA_CHUNK_HEADER_SIZE),
211 aid_generator_factory: Arc::new(aid_factory),
212 }
213 }
214
215 /// Supply a custom Association ID generator factory
216 ///
217 /// Called once by each `Endpoint` constructed from this configuration to obtain the AID
218 /// generator which will be used to generate the AIDs used for incoming packets on all
219 /// associations involving that `Endpoint`. A custom AID generator allows applications to embed
220 /// information in local association IDs, e.g. to support stateless packet-level load balancers.
221 ///
222 /// `EndpointConfig::new()` applies a default random AID generator factory. This functions
223 /// accepts any customized AID generator to reset AID generator factory that implements
224 /// the `AssociationIdGenerator` trait.
225 pub fn aid_generator<F: Fn() -> Box<dyn AssociationIdGenerator> + Send + Sync + 'static>(
226 &mut self,
227 factory: F,
228 ) -> &mut Self {
229 self.aid_generator_factory = Arc::new(factory);
230 self
231 }
232
233 /// Maximum payload size accepted from peers.
234 ///
235 /// The default is suitable for typical internet applications. Applications which expect to run
236 /// on networks supporting Ethernet jumbo frames or similar should set this appropriately.
237 pub fn max_payload_size(&mut self, value: u32) -> &mut Self {
238 self.max_payload_size = value;
239 self
240 }
241
242 /// Get the current value of `max_payload_size`
243 ///
244 /// While most parameters don't need to be readable, this must be exposed to allow higher-level
245 /// layers to determine how large a receive buffer to allocate to
246 /// support an externally-defined `EndpointConfig`.
247 ///
248 /// While `get_` accessors are typically unidiomatic in Rust, we favor concision for setters,
249 /// which will be used far more heavily.
250 #[doc(hidden)]
251 pub fn get_max_payload_size(&self) -> u32 {
252 self.max_payload_size
253 }
254}
255
256impl fmt::Debug for EndpointConfig {
257 fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
258 fmt.debug_struct("EndpointConfig")
259 .field("max_payload_size", &self.max_payload_size)
260 .field("aid_generator_factory", &"[ elided ]")
261 .finish()
262 }
263}
264
265/// Parameters governing incoming associations
266///
267/// Default values should be suitable for most internet applications.
268#[derive(Debug, Clone)]
269pub struct ServerConfig {
270 /// Transport configuration to use for incoming associations
271 pub transport: Arc<TransportConfig>,
272
273 /// Maximum number of concurrent associations
274 pub(crate) concurrent_associations: u32,
275}
276
277impl Default for ServerConfig {
278 fn default() -> Self {
279 ServerConfig {
280 transport: Arc::new(TransportConfig::default()),
281 concurrent_associations: 100_000,
282 }
283 }
284}
285
286impl ServerConfig {
287 /// Create a default config with a particular handshake token key
288 pub fn new() -> Self {
289 ServerConfig::default()
290 }
291}
292
293/// Default SCTP source/destination port (conventional for WebRTC data channels).
294pub const DEFAULT_SCTP_PORT: u16 = 5000;
295
296/// Maximum allowed size (in bytes) of a serialized SNAP token (INIT chunk)
297/// accepted via out-of-band negotiation. A typical token is well under
298/// 100 bytes; this limit prevents accidentally feeding megabytes of
299/// untrusted signaling data into the parser.
300pub const MAX_SNAP_INIT_BYTES: usize = 2048;
301
302/// Configuration for outgoing associations.
303///
304/// Default values should be suitable for most internet applications.
305#[derive(Debug, Clone)]
306pub struct ClientConfig {
307 /// Transport configuration to use
308 pub transport: Arc<TransportConfig>,
309 /// Local SNAP token (INIT chunk) bytes.
310 ///
311 /// Generated via [`generate_snap_token`]. When both `local_sctp_init` and
312 /// `remote_sctp_init` are set, the association skips the SCTP 4-way
313 /// handshake (RFC 4960 Section 5.1) and immediately transitions to the
314 /// ESTABLISHED state.
315 ///
316 /// If only one side is set (e.g. the peer does not support SNAP), the
317 /// association falls back to the normal SCTP handshake.
318 ///
319 /// See [draft-hancke-tsvwg-snap-01](https://datatracker.ietf.org/doc/draft-hancke-tsvwg-snap/).
320 pub(crate) local_sctp_init: Option<Bytes>,
321 /// Remote SNAP token (INIT chunk) bytes.
322 ///
323 /// Received from the peer via a signaling channel (e.g., SDP `a=sctp-init`
324 /// attribute). Must be provided together with `local_sctp_init` to enable
325 /// SNAP.
326 ///
327 /// See [draft-hancke-tsvwg-snap-01](https://datatracker.ietf.org/doc/draft-hancke-tsvwg-snap/).
328 pub(crate) remote_sctp_init: Option<Bytes>,
329}
330
331impl Default for ClientConfig {
332 fn default() -> Self {
333 ClientConfig {
334 transport: Arc::new(TransportConfig::default()),
335 local_sctp_init: None,
336 remote_sctp_init: None,
337 }
338 }
339}
340
341impl ClientConfig {
342 /// Create a default config with a particular cryptographic config
343 pub fn new() -> Self {
344 ClientConfig::default()
345 }
346
347 /// Enable SNAP (SCTP Negotiation Acceleration Protocol).
348 ///
349 /// Both a local and remote SNAP token (INIT chunk) must be provided.
350 /// The local token should be generated via [`generate_snap_token`] and
351 /// exchanged with the remote peer through a signaling channel (e.g.,
352 /// SDP `a=sctp-init` attribute). The remote token is the peer's
353 /// corresponding bytes received via signaling.
354 ///
355 /// When both are set, the association skips the SCTP 4-way handshake
356 /// (RFC 4960 Section 5.1) and immediately transitions to the ESTABLISHED
357 /// state.
358 ///
359 /// **Note:** When using SNAP, **both** peers must call
360 /// [`Endpoint::connect`](crate::Endpoint::connect) — there is no
361 /// server-side SNAP via [`Endpoint::handle`](crate::Endpoint::handle).
362 ///
363 /// See [draft-hancke-tsvwg-snap-01](https://datatracker.ietf.org/doc/draft-hancke-tsvwg-snap/).
364 pub fn with_snap(mut self, local_sctp_init: Bytes, remote_sctp_init: Bytes) -> Self {
365 self.local_sctp_init = Some(local_sctp_init);
366 self.remote_sctp_init = Some(remote_sctp_init);
367 self
368 }
369}
370
371/// Generate a SNAP token (INIT chunk) for out-of-band negotiation.
372///
373/// Creates a serialized SCTP INIT **chunk** (not a full SCTP packet — no
374/// common header or IP/UDP framing) with random `initiate_tag` and
375/// `initial_tsn` values, using the receiver window from the provided
376/// [`TransportConfig`]. Stream counts are set to `u16::MAX` so that the
377/// actual limit is determined by the peer's offer during negotiation.
378///
379/// The returned bytes are suitable for exchange via a signaling channel
380/// (e.g., SDP `a=sctp-init`) as described in
381/// [draft-hancke-tsvwg-snap-01](https://datatracker.ietf.org/doc/draft-hancke-tsvwg-snap/).
382///
383/// Each call generates fresh random values. The caller must hold onto the
384/// returned bytes and pass them to [`ClientConfig::with_snap`] alongside
385/// the remote peer's token.
386pub fn generate_snap_token(config: &TransportConfig) -> Result<Bytes, crate::error::Error> {
387 use crate::chunk::{Chunk, chunk_init::ChunkInit};
388 use core::num::NonZeroU32;
389 use rand::random;
390
391 let mut init = ChunkInit {
392 initiate_tag: random::<NonZeroU32>().get(),
393 initial_tsn: random::<NonZeroU32>().get(),
394 num_outbound_streams: u16::MAX,
395 num_inbound_streams: u16::MAX,
396 advertised_receiver_window_credit: config.max_receive_buffer_size(),
397 ..Default::default()
398 };
399 init.set_supported_extensions();
400 init.check()?;
401 init.marshal()
402}