Skip to main content

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}