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