File
Blob: firmware/vendor/str0m/src/sctp/mod.rs
| 1 | #![allow(clippy::new_without_default)] |
| 2 | |
| 3 | use std::collections::{HashSet, VecDeque}; |
| 4 | use std::fmt; |
| 5 | use std::net::SocketAddr; |
| 6 | use std::panic::UnwindSafe; |
| 7 | use std::sync::Arc; |
| 8 | use std::time::{Duration, Instant}; |
| 9 | |
| 10 | use bytes::Bytes; |
| 11 | pub use sctp_proto::ReceiveLimits as SctpReceiveLimits; |
| 12 | use sctp_proto::{Association, AssociationHandle, DatagramEvent}; |
| 13 | use sctp_proto::{Endpoint, EndpointConfig, Stream, StreamEvent, Transmit}; |
| 14 | use sctp_proto::{Event, Payload, PayloadProtocolIdentifier, ServerConfig, TransportConfig}; |
| 15 | |
| 16 | use snap::{b64_encode, webrtc_transport_config}; |
| 17 | |
| 18 | pub use sctp_proto::Error as ProtoError; |
| 19 | use sctp_proto::ReliabilityType; |
| 20 | |
| 21 | mod snap; |
| 22 | pub use snap::SctpInitData; |
| 23 | |
| 24 | mod dcep; |
| 25 | use dcep::DcepAck; |
| 26 | use dcep::DcepOpen; |
| 27 | |
| 28 | mod error; |
| 29 | pub use error::SctpError; |
| 30 | |
| 31 | /// Bytes that can be buffered inside str0m across all streams. |
| 32 | const MAX_BUFFERED_ACROSS_STREAMS: usize = 128 * 1024; |
| 33 | |
| 34 | /// Maximum message size we advertise in SDP (what we can receive) |
| 35 | pub const LOCAL_MAX_MESSAGE_SIZE: u32 = 256 * 1024; |
| 36 | |
| 37 | /// Default max message size if remote doesn't advertise |
| 38 | pub const DEFAULT_REMOTE_MAX_MESSAGE_SIZE: u32 = 64 * 1024; |
| 39 | |
| 40 | pub(crate) struct RtcSctp { |
| 41 | state: RtcSctpState, |
| 42 | endpoint: Endpoint, |
| 43 | fake_addr: SocketAddr, |
| 44 | handle: AssociationHandle, |
| 45 | assoc: Option<Association>, |
| 46 | // Sorted by `id` so lookups can binary search. Keep it that way. |
| 47 | entries: Vec<StreamEntry>, |
| 48 | // Stream ids that still owe a `StreamEvent::ResetComplete`, |
| 49 | // thos ids needs to be held back from reuse. |
| 50 | reset_pending: HashSet<u16>, |
| 51 | // Completed resets awaiting delivery to the caller. |
| 52 | // Used to guarantee emission ordering, ResetComplete must |
| 53 | // be sent after Close. |
| 54 | reset_complete: VecDeque<u16>, |
| 55 | pushed_back_transmit: Option<VecDeque<Bytes>>, |
| 56 | receive_limits: Option<SctpReceiveLimits>, |
| 57 | last_now: Instant, |
| 58 | client: bool, |
| 59 | remote_max_message_size: u32, |
| 60 | snap_enabled: bool, |
| 61 | snap_init: Option<SctpInitData>, |
| 62 | #[cfg(test)] |
| 63 | max_payload_size: usize, |
| 64 | } |
| 65 | |
| 66 | /// This is okay because there is no way for a user of Rtc to interact with the Sctp subsystem |
| 67 | /// in a way that would allow them to observe a potentially broken invariant when catching a panic. |
| 68 | impl UnwindSafe for RtcSctp {} |
| 69 | |
| 70 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
| 71 | enum RtcSctpState { |
| 72 | Uninited, |
| 73 | AwaitRemoteAssociation, |
| 74 | AwaitAssociationEstablished, |
| 75 | Established, |
| 76 | } |
| 77 | |
| 78 | impl RtcSctpState { |
| 79 | pub fn propagate_endpoint_to_assoc(&self) -> bool { |
| 80 | matches!( |
| 81 | self, |
| 82 | RtcSctpState::AwaitAssociationEstablished | RtcSctpState::Established |
| 83 | ) |
| 84 | } |
| 85 | } |
| 86 | |
| 87 | #[derive(Debug)] |
| 88 | struct StreamEntry { |
| 89 | /// Config as provided when opening the channel. This is None if we discover |
| 90 | /// the channel from the remote peer before getting a DcepOpen or local open_stream. |
| 91 | config: Option<ChannelConfig>, |
| 92 | /// Current state |
| 93 | state: StreamEntryState, |
| 94 | /// Actual stream id. Negotiated or automatically allocated. |
| 95 | id: u16, |
| 96 | /// If we are to close this entry. |
| 97 | do_close: bool, |
| 98 | /// Deadline for retrying `open_stream` when it fails. |
| 99 | open_deadline: Option<Instant>, |
| 100 | /// If the queued outgoing data drops below this threshold, Rtc is to emit an |
| 101 | /// event to the user. |
| 102 | buffered_threshold: BufferedThresholdConfig, |
| 103 | } |
| 104 | |
| 105 | #[derive(Debug)] |
| 106 | /// Tracks the `buffered_amount_low_threshold` for a stream. |
| 107 | /// |
| 108 | /// Lets us defer applying user changes to the underlying SCTP |
| 109 | /// stream until the next poll cycle, without first querying the current |
| 110 | /// configured value. |
| 111 | enum BufferedThresholdConfig { |
| 112 | /// No threshold has been set or it was cleared after an error. |
| 113 | Unconfigured, |
| 114 | /// A user-requested threshold to apply on the next poll. |
| 115 | Desired(usize), |
| 116 | /// The threshold value currently configured in the SCTP stream. |
| 117 | Configured(usize), |
| 118 | } |
| 119 | |
| 120 | impl BufferedThresholdConfig { |
| 121 | pub fn set(&mut self, v: usize) { |
| 122 | let is_change = match self { |
| 123 | BufferedThresholdConfig::Unconfigured => true, |
| 124 | BufferedThresholdConfig::Desired(w) if v != *w => true, |
| 125 | BufferedThresholdConfig::Configured(x) if v != *x => true, |
| 126 | _ => false, |
| 127 | }; |
| 128 | if is_change { |
| 129 | *self = BufferedThresholdConfig::Desired(v); |
| 130 | } |
| 131 | } |
| 132 | } |
| 133 | |
| 134 | pub(crate) enum SctpEvent { |
| 135 | Transmit { |
| 136 | packets: VecDeque<Bytes>, |
| 137 | }, |
| 138 | Open { |
| 139 | id: u16, |
| 140 | label: String, |
| 141 | }, |
| 142 | Close { |
| 143 | id: u16, |
| 144 | /// Whether a reset handshake is outstanding for this stream id. |
| 145 | reset_pending: bool, |
| 146 | }, |
| 147 | /// The reset handshake for a closed stream completed and sctp-proto emitted |
| 148 | /// StreamEvent::ResetComplete. The stream id is now safe to be used again. |
| 149 | /// |
| 150 | /// Always reported after the `Close` for the same id. |
| 151 | StreamResetComplete { |
| 152 | id: u16, |
| 153 | }, |
| 154 | Data { |
| 155 | id: u16, |
| 156 | binary: bool, |
| 157 | data: Vec<u8>, |
| 158 | }, |
| 159 | BufferedAmountLow { |
| 160 | id: u16, |
| 161 | }, |
| 162 | AssociationLost, |
| 163 | } |
| 164 | |
| 165 | /// These are the possible paths: |
| 166 | /// ```text |
| 167 | /// local inited, in-band AwaitOpen -> AwaitDcepAck -> Open |
| 168 | /// local inited, out-of-band AwaitOpen -> Open |
| 169 | /// remote inited, in-band AwaitConfig -> (receive dcep) -> Open |
| 170 | /// remote inited, out-of-band AwaitConfig -> (open_stream) -> Open |
| 171 | /// ``` |
| 172 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] |
| 173 | enum StreamEntryState { |
| 174 | /// A new stream declared locally, not discovered from remote. |
| 175 | AwaitOpen, |
| 176 | /// A new stream, discovered from remote. It can either be in-band or out-of band |
| 177 | /// We will either receive DcepOpen in-band, or a open_stream() call out-of-band. |
| 178 | AwaitConfig, |
| 179 | /// If we have sent DcepOpen and are waiting for the ack. |
| 180 | AwaitDcepAck, |
| 181 | /// Stream is open, ready to send data. |
| 182 | Open, |
| 183 | /// If some error occurs. |
| 184 | Closed, |
| 185 | } |
| 186 | |
| 187 | /// (Low level) configuration for a data channel. |
| 188 | #[derive(Debug, Clone, PartialEq, Eq, Hash)] |
| 189 | pub struct ChannelConfig { |
| 190 | /// The label to use for the user to identify the channel. |
| 191 | pub label: String, |
| 192 | /// Whether channel is guaranteed ordered delivery of messages. |
| 193 | pub ordered: bool, |
| 194 | /// The reliability setting, which can allow to drop messages. |
| 195 | pub reliability: Reliability, |
| 196 | /// Whether channel is negotiated in-band (DCEP) or out-of-band. |
| 197 | /// None means in-band negotiated. Some(stream_id) means out-of-band. |
| 198 | pub negotiated: Option<u16>, |
| 199 | /// Protocol name. |
| 200 | /// |
| 201 | /// Defaults to "" |
| 202 | pub protocol: String, |
| 203 | } |
| 204 | |
| 205 | impl Default for ChannelConfig { |
| 206 | fn default() -> Self { |
| 207 | Self { |
| 208 | label: Default::default(), |
| 209 | ordered: true, |
| 210 | reliability: Default::default(), |
| 211 | negotiated: Default::default(), |
| 212 | protocol: Default::default(), |
| 213 | } |
| 214 | } |
| 215 | } |
| 216 | |
| 217 | /// Reliability setting of a data channel. |
| 218 | #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] |
| 219 | pub enum Reliability { |
| 220 | /// Packets are delivered in order, with retransmits. |
| 221 | #[default] |
| 222 | Reliable, |
| 223 | /// Packets delivered out of order with a max lifetime. |
| 224 | MaxPacketLifetime { |
| 225 | /// The lifetime of a packet in milliseconds. |
| 226 | lifetime: u16, |
| 227 | }, |
| 228 | /// Packets delivered out of order with a max number of retransmits. |
| 229 | MaxRetransmits { |
| 230 | /// Number of retransmits before giving up. |
| 231 | retransmits: u16, |
| 232 | }, |
| 233 | } |
| 234 | |
| 235 | impl StreamEntry { |
| 236 | fn set_state(&mut self, state: StreamEntryState) -> bool { |
| 237 | if self.state == state { |
| 238 | return false; |
| 239 | } |
| 240 | debug!("Stream {:?} -> {:?}", self.state, state); |
| 241 | self.state = state; |
| 242 | true |
| 243 | } |
| 244 | |
| 245 | #[must_use] |
| 246 | fn configure_reliability(&mut self, stream: &mut Stream) -> bool { |
| 247 | let dcep: DcepOpen = self.config.as_ref().expect("config to be set").into(); |
| 248 | |
| 249 | let ret = stream.set_reliability_params( |
| 250 | dcep.unordered, |
| 251 | dcep.channel_type, |
| 252 | dcep.reliability_parameter, |
| 253 | ); |
| 254 | |
| 255 | if let Err(e) = ret { |
| 256 | warn!( |
| 257 | "Failed to set reliability params on stream {}: {:?}", |
| 258 | self.id, e |
| 259 | ); |
| 260 | self.do_close = true; |
| 261 | return false; |
| 262 | } |
| 263 | |
| 264 | true |
| 265 | } |
| 266 | } |
| 267 | |
| 268 | /// SCTP framing per outgoing datagram: 12-byte common header + ~16-byte DATA |
| 269 | /// chunk header + some extra. |
| 270 | const SCTP_OVERHEAD: usize = 40; |
| 271 | |
| 272 | /// How long `open_stream` keeps retrying when blocked by a transient error |
| 273 | /// which could happen during reset handshake (RFC 6525). |
| 274 | /// Past this the peer is considered broken and the open gives up. |
| 275 | const STREAM_OPEN_TIMEOUT: Duration = Duration::from_secs(10); |
| 276 | |
| 277 | /// Backstop poll interval while an open is retrying. Retries are normally |
| 278 | /// driven by the network input that unblocks them, |
| 279 | /// either the reciprocal reset or RECONFIG-RESPONSE arriving. |
| 280 | /// This interval guarantees progress in case of absence of the latter messages. |
| 281 | const STREAM_OPEN_RETRY_INTERVAL: Duration = Duration::from_millis(500); |
| 282 | |
| 283 | /// Empirical: SCTP `max_payload_size` of 1200 has produced 1277-byte DTLS-wrapped |
| 284 | /// datagrams (77 bytes combined SCTP + DTLS overhead). |
| 285 | const _: () = assert!( |
| 286 | crate::io::MAX_DTLS_OVERHEAD + SCTP_OVERHEAD >= 80, |
| 287 | "MAX_DTLS_OVERHEAD + SCTP_OVERHEAD must cover observed 77-byte SCTP-over-DTLS overhead" |
| 288 | ); |
| 289 | |
| 290 | impl RtcSctp { |
| 291 | #[cfg(test)] |
| 292 | pub fn new(mtu: usize) -> Self { |
| 293 | Self::with_receive_limits(mtu, None) |
| 294 | } |
| 295 | |
| 296 | pub fn with_receive_limits(mtu: usize, receive_limits: Option<SctpReceiveLimits>) -> Self { |
| 297 | let mut config = EndpointConfig::default(); |
| 298 | let max_payload = mtu |
| 299 | .saturating_sub(crate::io::MAX_DTLS_OVERHEAD) |
| 300 | .saturating_sub(SCTP_OVERHEAD); |
| 301 | config.max_payload_size(max_payload as u32); |
| 302 | #[cfg(test)] |
| 303 | let max_payload_size = max_payload; |
| 304 | let mut server_config = ServerConfig::default(); |
| 305 | server_config.transport = webrtc_transport_config(receive_limits); |
| 306 | let endpoint = Endpoint::new(Arc::new(config), Some(Arc::new(server_config))); |
| 307 | let fake_addr = "1.1.1.1:5000".parse().unwrap(); |
| 308 | |
| 309 | RtcSctp { |
| 310 | state: RtcSctpState::Uninited, |
| 311 | endpoint, |
| 312 | fake_addr, |
| 313 | handle: AssociationHandle(0), // temporary |
| 314 | assoc: None, |
| 315 | entries: Vec::new(), |
| 316 | reset_pending: HashSet::new(), |
| 317 | reset_complete: VecDeque::new(), |
| 318 | pushed_back_transmit: None, |
| 319 | receive_limits, |
| 320 | last_now: Instant::now(), // placeholder until init() |
| 321 | client: false, |
| 322 | remote_max_message_size: DEFAULT_REMOTE_MAX_MESSAGE_SIZE, |
| 323 | snap_enabled: false, |
| 324 | snap_init: None, |
| 325 | #[cfg(test)] |
| 326 | max_payload_size, |
| 327 | } |
| 328 | } |
| 329 | |
| 330 | #[cfg(test)] |
| 331 | pub(crate) fn max_payload_size(&self) -> usize { |
| 332 | self.max_payload_size |
| 333 | } |
| 334 | |
| 335 | pub fn is_inited(&self) -> bool { |
| 336 | self.state != RtcSctpState::Uninited |
| 337 | } |
| 338 | |
| 339 | pub fn is_closing(&self) -> bool { |
| 340 | self.assoc.as_ref().is_some_and(|a| a.is_closing()) |
| 341 | } |
| 342 | |
| 343 | pub fn is_closed(&self) -> bool { |
| 344 | self.assoc.as_ref().is_none_or(|a| a.is_closed()) |
| 345 | } |
| 346 | |
| 347 | pub fn init( |
| 348 | &mut self, |
| 349 | client: bool, |
| 350 | now: Instant, |
| 351 | sctp_init_data: Option<SctpInitData>, |
| 352 | remote_max_message_size: Option<u32>, |
| 353 | ) -> Result<(), SctpError> { |
| 354 | if self.state != RtcSctpState::Uninited { |
| 355 | return Err(SctpError::Proto(ProtoError::Other( |
| 356 | "SCTP already initialized".into(), |
| 357 | ))); |
| 358 | } |
| 359 | |
| 360 | self.client = client; |
| 361 | self.last_now = now; |
| 362 | |
| 363 | if let Some(max_msg_size) = remote_max_message_size { |
| 364 | self.remote_max_message_size = max_msg_size; |
| 365 | } |
| 366 | |
| 367 | if let Some(mut snap_data) = sctp_init_data { |
| 368 | // SNAP path: both local and remote INIT chunks must be present. |
| 369 | if snap_data.local_init.is_none() || snap_data.remote_init.is_none() { |
| 370 | return Err(SctpError::Proto(ProtoError::Other( |
| 371 | "SNAP requires both local and remote SCTP INIT chunks".into(), |
| 372 | ))); |
| 373 | } |
| 374 | |
| 375 | // Enforce the local resource policy for both SDP and direct SNAP. |
| 376 | if self.receive_limits.is_some() { |
| 377 | snap_data.transport = webrtc_transport_config(self.receive_limits); |
| 378 | } |
| 379 | let config = snap_data.into_client_config(); |
| 380 | debug!( |
| 381 | "New {} association (out-of-band: true)", |
| 382 | if client { "local" } else { "server" }, |
| 383 | ); |
| 384 | let (handle, mut assoc) = self |
| 385 | .endpoint |
| 386 | .connect(config, self.fake_addr) |
| 387 | .map_err(|e| SctpError::Proto(ProtoError::Other(e.to_string())))?; |
| 388 | assoc.set_max_send_message_size(self.remote_max_message_size); |
| 389 | self.handle = handle; |
| 390 | self.assoc = Some(assoc); |
| 391 | |
| 392 | // With SNAP, both sides exchanged INIT chunks out-of-band. The |
| 393 | // sctp-proto association is already in established state (via |
| 394 | // `with_snap`). We set our state to Established immediately |
| 395 | // even though DTLS may not be connected yet. This is safe |
| 396 | // because the `dtls_connected` guard in `do_poll_output` |
| 397 | // prevents any SCTP packets from flowing until the DTLS |
| 398 | // handshake completes. |
| 399 | set_state(&mut self.state, RtcSctpState::Established); |
| 400 | } else if client { |
| 401 | // Normal client path: initiate the SCTP association. |
| 402 | let mut config = SctpInitData::default().into_client_config(); |
| 403 | let mut transport = TransportConfig::default() |
| 404 | .with_max_init_retransmits(None) |
| 405 | .with_max_data_retransmits(None) |
| 406 | .with_max_receive_message_size(LOCAL_MAX_MESSAGE_SIZE) |
| 407 | .with_max_send_message_size(self.remote_max_message_size); |
| 408 | if let Some(limits) = self.receive_limits { |
| 409 | transport = transport.with_receive_limits(limits); |
| 410 | } |
| 411 | config.transport = Arc::new(transport); |
| 412 | |
| 413 | debug!("New local association (out-of-band: false)"); |
| 414 | let (handle, assoc) = self |
| 415 | .endpoint |
| 416 | .connect(config, self.fake_addr) |
| 417 | .map_err(|e| SctpError::Proto(ProtoError::Other(e.to_string())))?; |
| 418 | self.handle = handle; |
| 419 | self.assoc = Some(assoc); |
| 420 | set_state(&mut self.state, RtcSctpState::AwaitAssociationEstablished); |
| 421 | } else { |
| 422 | // Normal server path: wait for the remote to initiate. |
| 423 | set_state(&mut self.state, RtcSctpState::AwaitRemoteAssociation); |
| 424 | } |
| 425 | |
| 426 | Ok(()) |
| 427 | } |
| 428 | |
| 429 | pub fn local_max_message_size(&self) -> u32 { |
| 430 | self.receive_limits |
| 431 | .map_or(LOCAL_MAX_MESSAGE_SIZE, SctpReceiveLimits::max_message_size) |
| 432 | } |
| 433 | |
| 434 | pub fn is_client(&self) -> bool { |
| 435 | self.client |
| 436 | } |
| 437 | |
| 438 | /// Enable SNAP by pre-populating the init data. |
| 439 | pub fn enable_snap(&mut self) { |
| 440 | self.snap_enabled = true; |
| 441 | self.snap_init |
| 442 | .get_or_insert_with(|| SctpInitData::with_optional_receive_limits(self.receive_limits)); |
| 443 | } |
| 444 | |
| 445 | /// Whether local offers should opt in to SNAP. |
| 446 | pub fn snap_enabled(&self) -> bool { |
| 447 | self.snap_enabled |
| 448 | } |
| 449 | |
| 450 | /// Ensure the local SNAP INIT chunk is generated. Returns `false` if |
| 451 | /// generation failed (degrades to non-SNAP). |
| 452 | pub fn ensure_local_snap_init(&mut self) -> bool { |
| 453 | let init_data = self |
| 454 | .snap_init |
| 455 | .get_or_insert_with(|| SctpInitData::with_optional_receive_limits(self.receive_limits)); |
| 456 | if init_data.local_init_chunk().is_err() { |
| 457 | self.snap_init = None; |
| 458 | false |
| 459 | } else { |
| 460 | true |
| 461 | } |
| 462 | } |
| 463 | |
| 464 | /// Discard pending SNAP negotiation state before SCTP starts. |
| 465 | /// |
| 466 | /// This preserves local opt-in for future offers. |
| 467 | pub fn disable_pending_snap(&mut self) { |
| 468 | if !self.is_inited() { |
| 469 | self.snap_init = None; |
| 470 | } |
| 471 | } |
| 472 | |
| 473 | /// Get the local INIT chunk as a base64 string for SDP, if applicable. |
| 474 | /// |
| 475 | /// Returns `None` when: |
| 476 | /// - SNAP is not active |
| 477 | /// - SCTP is established without SNAP (non-SNAP association) |
| 478 | pub fn local_sctp_init_for_sdp(&self) -> Option<String> { |
| 479 | let d = self.snap_init.as_ref()?; |
| 480 | if self.is_inited() && d.remote_init.is_none() { |
| 481 | // Established non-SNAP association - MUST NOT inject sctp-init. |
| 482 | return None; |
| 483 | } |
| 484 | d.local_init.as_ref().map(|b| b64_encode(b)) |
| 485 | } |
| 486 | |
| 487 | /// Get the cached remote INIT string, if set. |
| 488 | pub fn snap_remote_init_string(&self) -> Option<String> { |
| 489 | self.snap_init.as_ref().and_then(|d| d.remote_init_string()) |
| 490 | } |
| 491 | |
| 492 | /// Whether this is an established SNAP association (has remote init). |
| 493 | pub fn is_snap_established(&self) -> bool { |
| 494 | self.is_inited() |
| 495 | && self |
| 496 | .snap_init |
| 497 | .as_ref() |
| 498 | .and_then(|d| d.remote_init.as_ref()) |
| 499 | .is_some() |
| 500 | } |
| 501 | |
| 502 | /// Set the remote SNAP INIT from a base64 string. Returns `Ok(true)` if |
| 503 | /// accepted, `Ok(false)` on decode error (degrades to non-SNAP). |
| 504 | pub fn set_remote_snap_init_string(&mut self, value: &str) -> bool { |
| 505 | let init_data = self |
| 506 | .snap_init |
| 507 | .get_or_insert_with(|| SctpInitData::with_optional_receive_limits(self.receive_limits)); |
| 508 | match init_data.set_remote_init_string(value) { |
| 509 | Ok(()) => true, |
| 510 | Err(_) => { |
| 511 | self.disable_pending_snap(); |
| 512 | false |
| 513 | } |
| 514 | } |
| 515 | } |
| 516 | |
| 517 | /// Build a cloned `SctpInitData` for passing to `init()`, if both local |
| 518 | /// and remote INIT chunks are present. |
| 519 | pub fn build_snap_init_data(&self) -> Option<SctpInitData> { |
| 520 | let d = self.snap_init.as_ref()?; |
| 521 | if d.local_init.is_none() || d.remote_init.is_none() { |
| 522 | return None; |
| 523 | } |
| 524 | Some(d.clone()) |
| 525 | } |
| 526 | |
| 527 | /// Opens a new stream. |
| 528 | pub fn open_stream(&mut self, id: u16, config: ChannelConfig) { |
| 529 | // The channel might already have arrived via SCTP, and if it is negotiated out-of-band |
| 530 | // we are waiting for the configuration. |
| 531 | let entry = stream_entry( |
| 532 | &mut self.entries, |
| 533 | id, |
| 534 | StreamEntryState::AwaitOpen, |
| 535 | "open_stream", |
| 536 | ); |
| 537 | |
| 538 | let in_band = config.negotiated.is_none(); |
| 539 | |
| 540 | // Stream should not already have a config, we are either waiting for DcepOpen, or this is |
| 541 | // out-of-band configuration, in which case this call is setting the config. |
| 542 | if entry.config.is_some() { |
| 543 | warn!("Stream is already configured: {}", id); |
| 544 | entry.do_close = true; |
| 545 | return; |
| 546 | } else { |
| 547 | entry.config = Some(config); |
| 548 | } |
| 549 | |
| 550 | // If we are in AwaitConfig, the stream was discovered from the remote peer before |
| 551 | // we got to do open_stream. This means we _must_ be in the out-of-band track, |
| 552 | // since we shouldn't call open_stream on remotely started in-band. |
| 553 | if entry.state == StreamEntryState::AwaitConfig { |
| 554 | if in_band { |
| 555 | warn!("open_stream in-band negotiation for remote stream: {}", id); |
| 556 | entry.do_close = true; |
| 557 | } else { |
| 558 | // out-of-band where remote started. We can go to Open, but must configure the local |
| 559 | // stream for it first. |
| 560 | |
| 561 | // The stream can already be gone even though our entry is |
| 562 | // still AwaitConfig. Close the channel gracefully instead of panicking. |
| 563 | let Some(assoc) = self.assoc.as_mut() else { |
| 564 | entry.do_close = true; |
| 565 | return; |
| 566 | }; |
| 567 | let Ok(mut stream) = assoc.stream(entry.id) else { |
| 568 | entry.do_close = true; |
| 569 | return; |
| 570 | }; |
| 571 | |
| 572 | if !entry.configure_reliability(&mut stream) { |
| 573 | return; |
| 574 | } |
| 575 | |
| 576 | entry.set_state(StreamEntryState::Open); |
| 577 | } |
| 578 | } |
| 579 | } |
| 580 | |
| 581 | /// Close stream. |
| 582 | pub fn close_stream(&mut self, id: u16) { |
| 583 | if let Some(entry) = entry_by_id_mut(&mut self.entries, id) { |
| 584 | entry.do_close = true; |
| 585 | |
| 586 | // Explicitly close the sctp stream to allow re-use of the same id. |
| 587 | let _ = self.sctp_propagate_close(id); |
| 588 | } |
| 589 | } |
| 590 | |
| 591 | pub fn close(&mut self) -> Result<(), SctpError> { |
| 592 | let Some(assoc) = &mut self.assoc else { |
| 593 | return Ok(()); |
| 594 | }; |
| 595 | |
| 596 | if assoc.is_closing() || assoc.is_closed() { |
| 597 | return Ok(()); |
| 598 | } |
| 599 | |
| 600 | Ok(assoc.shutdown()?) |
| 601 | } |
| 602 | |
| 603 | pub fn is_open(&self, id: u16) -> bool { |
| 604 | if self.state != RtcSctpState::Established { |
| 605 | return false; |
| 606 | } |
| 607 | |
| 608 | let Some(rec) = entry_by_id(&self.entries, id) else { |
| 609 | return false; |
| 610 | }; |
| 611 | |
| 612 | rec.state == StreamEntryState::Open |
| 613 | } |
| 614 | |
| 615 | // TODO: fix sctp-proto so we don't need &mut here. |
| 616 | pub fn available(&mut self) -> usize { |
| 617 | let Some(assoc) = &mut self.assoc else { |
| 618 | return 0; |
| 619 | }; |
| 620 | |
| 621 | // The amount currently buffered. |
| 622 | let total: usize = self |
| 623 | .entries |
| 624 | .iter() |
| 625 | .filter_map(|e| { |
| 626 | assoc |
| 627 | .stream(e.id) |
| 628 | .ok() |
| 629 | .and_then(|s| s.buffered_amount().ok()) |
| 630 | }) |
| 631 | .sum(); |
| 632 | |
| 633 | MAX_BUFFERED_ACROSS_STREAMS.saturating_sub(total) |
| 634 | } |
| 635 | |
| 636 | pub fn write(&mut self, id: u16, binary: bool, buf: &[u8]) -> Result<usize, SctpError> { |
| 637 | if self.state != RtcSctpState::Established || self.is_closing() || self.is_closed() { |
| 638 | return Err(SctpError::WriteBeforeEstablished); |
| 639 | } |
| 640 | |
| 641 | let assoc = self |
| 642 | .assoc |
| 643 | .as_mut() |
| 644 | .ok_or(SctpError::WriteBeforeEstablished)?; |
| 645 | |
| 646 | let rec = entry_by_id(&self.entries, id).expect("stream entry for write"); |
| 647 | |
| 648 | if rec.state != StreamEntryState::Open { |
| 649 | return Err(SctpError::WriteBeforeEstablished); |
| 650 | } |
| 651 | |
| 652 | let mut stream = assoc.stream(id)?; |
| 653 | |
| 654 | let ppi = if binary { |
| 655 | if buf.is_empty() { |
| 656 | PayloadProtocolIdentifier::BinaryEmpty |
| 657 | } else { |
| 658 | PayloadProtocolIdentifier::Binary |
| 659 | } |
| 660 | } else if buf.is_empty() { |
| 661 | PayloadProtocolIdentifier::StringEmpty |
| 662 | } else { |
| 663 | PayloadProtocolIdentifier::String |
| 664 | }; |
| 665 | |
| 666 | Ok(stream.write_with_ppi(buf, ppi)?) |
| 667 | } |
| 668 | |
| 669 | pub fn buffered_amount(&mut self, id: u16) -> usize { |
| 670 | let Some(assoc) = self.assoc.as_mut() else { |
| 671 | return 0; |
| 672 | }; |
| 673 | |
| 674 | let Ok(stream) = assoc.stream(id) else { |
| 675 | return 0; |
| 676 | }; |
| 677 | |
| 678 | stream.buffered_amount().unwrap_or(0) |
| 679 | } |
| 680 | |
| 681 | pub fn set_buffered_amount_low_threshold(&mut self, id: u16, threshold: usize) { |
| 682 | let entry = |
| 683 | entry_by_id_mut(&mut self.entries, id).expect("stream entry for valid channel id"); |
| 684 | |
| 685 | // This update will be propagated on next poll. |
| 686 | entry.buffered_threshold.set(threshold); |
| 687 | } |
| 688 | |
| 689 | pub fn handle_input(&mut self, now: Instant, data: &[u8]) { |
| 690 | trace!("Handle input: {}", data.len()); |
| 691 | |
| 692 | // TODO, remove Bytes in sctp and just use &[u8]. |
| 693 | let data = data.to_vec().into(); |
| 694 | let r = self.endpoint.handle(now, self.fake_addr, None, None, data); |
| 695 | |
| 696 | let Some((handle, event)) = r else { |
| 697 | return; |
| 698 | }; |
| 699 | |
| 700 | match event { |
| 701 | DatagramEvent::NewAssociation(a) => { |
| 702 | // In slow or unreliable networks from browsers (use 3g or slow 4g) settings. |
| 703 | // The browser resends a new associations and str0m would override the previously |
| 704 | // acked association. Webrtc should use only 1 association. |
| 705 | if self.assoc.is_some() { |
| 706 | return; |
| 707 | } |
| 708 | debug!("New remote association"); |
| 709 | // Remote side initiated the association |
| 710 | self.assoc = Some(a); |
| 711 | self.handle = handle; |
| 712 | set_state(&mut self.state, RtcSctpState::AwaitAssociationEstablished); |
| 713 | } |
| 714 | DatagramEvent::AssociationEvent(event) => { |
| 715 | self.assoc |
| 716 | .as_mut() |
| 717 | .expect("association for event") |
| 718 | .handle_event(event); |
| 719 | } |
| 720 | } |
| 721 | } |
| 722 | |
| 723 | pub fn handle_timeout(&mut self, now: Instant) { |
| 724 | if self.state == RtcSctpState::Uninited { |
| 725 | // Need to call `init()` before any timeouts are accepted. |
| 726 | return; |
| 727 | } |
| 728 | |
| 729 | self.last_now = now; |
| 730 | |
| 731 | // Remove closed entries. |
| 732 | self.entries.retain(|e| e.state != StreamEntryState::Closed); |
| 733 | |
| 734 | let Some(assoc) = &mut self.assoc else { |
| 735 | return; |
| 736 | }; |
| 737 | |
| 738 | assoc.handle_timeout(now); |
| 739 | |
| 740 | // propagate events between endpoint and association. |
| 741 | while let Some(e) = assoc.poll_endpoint_event() { |
| 742 | if let Some(ae) = self.endpoint.handle_event(self.handle, e) { |
| 743 | assoc.handle_event(ae); |
| 744 | } |
| 745 | } |
| 746 | } |
| 747 | |
| 748 | pub fn poll(&mut self) -> Option<SctpEvent> { |
| 749 | let r = self.do_poll(); |
| 750 | |
| 751 | if let Some(r) = &r { |
| 752 | trace!("Poll {:?}", r); |
| 753 | } |
| 754 | |
| 755 | r |
| 756 | } |
| 757 | |
| 758 | pub fn do_poll(&mut self) -> Option<SctpEvent> { |
| 759 | // A completed handshake or a written DCEP open can produce more to send, so |
| 760 | // those start over from the top instead of falling through. There is one |
| 761 | // restart per channel waiting to open, which is why this is a loop and not |
| 762 | // recursion. |
| 763 | 'restart: loop { |
| 764 | if self.state == RtcSctpState::Uninited { |
| 765 | // Need to call `init()` before any polling starts. |
| 766 | return None; |
| 767 | } |
| 768 | |
| 769 | // Remove closed entries. handle_timeout() also does this, but the |
| 770 | // remote can reuse a stream id (its reset handshake completed) before |
| 771 | // our next timeout. |
| 772 | self.entries.retain(|e| e.state != StreamEntryState::Closed); |
| 773 | |
| 774 | if let Some(t) = self.pushed_back_transmit.take() { |
| 775 | return Some(SctpEvent::Transmit { packets: t }); |
| 776 | } |
| 777 | |
| 778 | while let Some(t) = self.poll_transmit() { |
| 779 | let Some(buf) = transmit_to_vec(t) else { |
| 780 | continue; |
| 781 | }; |
| 782 | |
| 783 | return Some(SctpEvent::Transmit { packets: buf }); |
| 784 | } |
| 785 | |
| 786 | // Don't progress to move data between association and endpoint until we have an |
| 787 | // association we want to drive forward. |
| 788 | if !self.state.propagate_endpoint_to_assoc() { |
| 789 | return None; |
| 790 | } |
| 791 | |
| 792 | let assoc = self.assoc.as_mut()?; |
| 793 | |
| 794 | while let Some(e) = assoc.poll() { |
| 795 | if let Event::Connected = e { |
| 796 | assoc.set_max_send_message_size(self.remote_max_message_size); |
| 797 | set_state(&mut self.state, RtcSctpState::Established); |
| 798 | continue 'restart; |
| 799 | } |
| 800 | |
| 801 | if let Event::AssociationLost { ref reason } = e { |
| 802 | debug!("Association lost, reason: {}", reason); |
| 803 | // No reset can complete on a dead association. |
| 804 | self.reset_pending.clear(); |
| 805 | self.reset_complete.clear(); |
| 806 | return Some(SctpEvent::AssociationLost); |
| 807 | } |
| 808 | |
| 809 | if let Event::Stream(se) = e { |
| 810 | match se { |
| 811 | StreamEvent::Readable { id } | StreamEvent::Writable { id } => { |
| 812 | stream_entry( |
| 813 | &mut self.entries, |
| 814 | id, |
| 815 | StreamEntryState::AwaitConfig, |
| 816 | "readable/writable", |
| 817 | ); |
| 818 | } |
| 819 | StreamEvent::Finished { id } | StreamEvent::Stopped { id, .. } => { |
| 820 | // sctp-proto unregistered it when a reset arrived. |
| 821 | // Id reuse is signalled separately by StreamEvent::ResetComplete. |
| 822 | // |
| 823 | // sctp-proto arms that completion here, so from now on a reset |
| 824 | // is outstanding for the id even if we never closed it locally. |
| 825 | self.reset_pending.insert(id); |
| 826 | |
| 827 | // Only a live entry has anything to drop. Closed entries and |
| 828 | // missing ones already went through Close, and an AwaitOpen |
| 829 | // entry is a new incarnation waiting on the same id, it must |
| 830 | // not be killed by the old pending teardown. |
| 831 | if let Some(entry) = entry_by_id_mut(&mut self.entries, id) { |
| 832 | if entry.state != StreamEntryState::Closed |
| 833 | && entry.state != StreamEntryState::AwaitOpen |
| 834 | { |
| 835 | debug!("Stream {} finished", id); |
| 836 | entry.do_close = true; |
| 837 | } |
| 838 | } |
| 839 | } |
| 840 | StreamEvent::ResetComplete { id } => { |
| 841 | // The reset handshake for this id has fully completed. |
| 842 | debug!("Stream {} reset complete", id); |
| 843 | self.reset_pending.remove(&id); |
| 844 | self.reset_complete.push_back(id); |
| 845 | } |
| 846 | StreamEvent::BufferedAmountLow { id } => { |
| 847 | return Some(SctpEvent::BufferedAmountLow { id }); |
| 848 | } |
| 849 | _ => {} |
| 850 | } |
| 851 | } |
| 852 | } |
| 853 | |
| 854 | // Must wait for association state to be established before opening streams. |
| 855 | if self.state != RtcSctpState::Established { |
| 856 | return None; |
| 857 | } |
| 858 | |
| 859 | for entry in self.entries.iter_mut() { |
| 860 | let want_open = entry.state == StreamEntryState::AwaitOpen; |
| 861 | |
| 862 | if want_open { |
| 863 | debug!("Open stream {}", entry.id); |
| 864 | match assoc.open_stream(entry.id, PayloadProtocolIdentifier::Unknown) { |
| 865 | Ok(mut s) => { |
| 866 | entry.open_deadline = None; |
| 867 | |
| 868 | if !entry.configure_reliability(&mut s) { |
| 869 | entry.set_state(StreamEntryState::Closed); |
| 870 | let stream_id = entry.id; |
| 871 | let reset_pending = self.sctp_propagate_close(stream_id); |
| 872 | return Some(SctpEvent::Close { |
| 873 | id: stream_id, |
| 874 | reset_pending, |
| 875 | }); |
| 876 | } |
| 877 | |
| 878 | let config = entry.config.as_ref().expect("config if AwaitOpen"); |
| 879 | let in_band = config.negotiated.is_none(); |
| 880 | |
| 881 | if in_band { |
| 882 | let dcep: DcepOpen = config.into(); |
| 883 | let mut buf = vec![0; 1500]; |
| 884 | let n = dcep.marshal_to(&mut buf); |
| 885 | buf.truncate(n); |
| 886 | |
| 887 | match s.write_with_ppi(&buf, PayloadProtocolIdentifier::Dcep) { |
| 888 | Ok(l) => { |
| 889 | assert!(n == l); |
| 890 | entry.set_state(StreamEntryState::AwaitDcepAck); |
| 891 | |
| 892 | // Start over with polling, since we might have caused |
| 893 | // some network traffic by writing the DcepOpen. |
| 894 | continue 'restart; |
| 895 | } |
| 896 | Err(e) => { |
| 897 | warn!( |
| 898 | "Failed to write DCEP open on stream {}: {:?}", |
| 899 | entry.id, e |
| 900 | ); |
| 901 | entry.do_close = true; |
| 902 | entry.set_state(StreamEntryState::Closed); |
| 903 | let stream_id = entry.id; |
| 904 | let reset_pending = self.sctp_propagate_close(stream_id); |
| 905 | return Some(SctpEvent::Close { |
| 906 | id: stream_id, |
| 907 | reset_pending, |
| 908 | }); |
| 909 | } |
| 910 | } |
| 911 | } |
| 912 | |
| 913 | // Continuing means we are opening the stream out-of-band. |
| 914 | } |
| 915 | Err( |
| 916 | e @ (ProtoError::ErrStreamAlreadyExist |
| 917 | | ProtoError::ErrStreamResetPending), |
| 918 | ) => { |
| 919 | let config = entry.config.as_ref().expect("config if AwaitOpen"); |
| 920 | let in_band = config.negotiated.is_none(); |
| 921 | |
| 922 | // ErrStreamAlreadyExist has two causes: |
| 923 | // - the remote created it by sending on it first |
| 924 | // - a previous incarnation of the id is still registered, |
| 925 | // reset handshake hasn't finished |
| 926 | let stale_incarnation = matches!(e, ProtoError::ErrStreamAlreadyExist) |
| 927 | && assoc |
| 928 | .stream(entry.id) |
| 929 | .map(|s| !s.is_readable() && !s.is_writable()) |
| 930 | .unwrap_or(false); |
| 931 | |
| 932 | if in_band |
| 933 | || stale_incarnation |
| 934 | || matches!(e, ProtoError::ErrStreamResetPending) |
| 935 | { |
| 936 | // RFC 6525 reset handshake for a previous |
| 937 | // incarnation of this stream id hasn't finished yet |
| 938 | // |
| 939 | // - AlreadyExist clears when the remote's reciprocal reset arrives |
| 940 | // - ResetPending when the RECONFIG-RESPONSE arrives (silently) |
| 941 | // |
| 942 | // In both cases, stay in AwaitOpen and retry until past the deadline. |
| 943 | let deadline = *entry |
| 944 | .open_deadline |
| 945 | .get_or_insert(self.last_now + STREAM_OPEN_TIMEOUT); |
| 946 | |
| 947 | if self.last_now < deadline { |
| 948 | debug!( |
| 949 | "Stream {} open blocked ({:?}), will retry", |
| 950 | entry.id, e |
| 951 | ); |
| 952 | continue; |
| 953 | } |
| 954 | |
| 955 | debug!("Opening stream {} failed after retries: {:?}", entry.id, e); |
| 956 | entry.do_close = true; |
| 957 | entry.set_state(StreamEntryState::Closed); |
| 958 | let stream_id = entry.id; |
| 959 | let reset_pending = self.sctp_propagate_close(stream_id); |
| 960 | return Some(SctpEvent::Close { |
| 961 | id: stream_id, |
| 962 | reset_pending, |
| 963 | }); |
| 964 | } |
| 965 | |
| 966 | // Continuing means we are adopting the live out-of-band stream the |
| 967 | // remote created. It skipped the Ok branch above, so reliability |
| 968 | // params haven't been applied to it yet. |
| 969 | let mut stream = assoc.stream(entry.id).expect("stream that exists"); |
| 970 | if !entry.configure_reliability(&mut stream) { |
| 971 | entry.set_state(StreamEntryState::Closed); |
| 972 | let stream_id = entry.id; |
| 973 | let reset_pending = self.sctp_propagate_close(stream_id); |
| 974 | return Some(SctpEvent::Close { |
| 975 | id: stream_id, |
| 976 | reset_pending, |
| 977 | }); |
| 978 | } |
| 979 | } |
| 980 | Err(e) => { |
| 981 | warn!("Opening stream {} failed: {:?}", entry.id, e); |
| 982 | entry.do_close = true; |
| 983 | entry.set_state(StreamEntryState::Closed); |
| 984 | let stream_id = entry.id; |
| 985 | let reset_pending = self.sctp_propagate_close(stream_id); |
| 986 | return Some(SctpEvent::Close { |
| 987 | id: stream_id, |
| 988 | reset_pending, |
| 989 | }); |
| 990 | } |
| 991 | } |
| 992 | |
| 993 | // Consider out-of-band stream open. |
| 994 | let config = entry.config.as_ref().expect("config if AwaitOpen"); |
| 995 | let in_band = config.negotiated.is_none(); |
| 996 | assert!(!in_band); |
| 997 | |
| 998 | let label = config.label.clone(); |
| 999 | entry.set_state(StreamEntryState::Open); |
| 1000 | |
| 1001 | return Some(SctpEvent::Open { |
| 1002 | id: entry.id, |
| 1003 | label, |
| 1004 | }); |
| 1005 | } |
| 1006 | |
| 1007 | if entry.do_close && entry.state != StreamEntryState::Closed { |
| 1008 | entry.set_state(StreamEntryState::Closed); |
| 1009 | let stream_id = entry.id; |
| 1010 | let reset_pending = self.sctp_propagate_close(stream_id); |
| 1011 | return Some(SctpEvent::Close { |
| 1012 | id: stream_id, |
| 1013 | reset_pending, |
| 1014 | }); |
| 1015 | } |
| 1016 | |
| 1017 | let mut stream = match assoc.stream(entry.id) { |
| 1018 | Ok(v) => v, |
| 1019 | Err(e) => { |
| 1020 | // This is expected on browser refresh or similar abrupt shutdown. |
| 1021 | debug!("Getting stream {} failed: {:?}", entry.id, e); |
| 1022 | entry.do_close = true; |
| 1023 | continue; |
| 1024 | } |
| 1025 | }; |
| 1026 | |
| 1027 | // Propagate the desired buffered threshold. |
| 1028 | // The idea is to only do this if the user has changed the value for it without |
| 1029 | // incurring the cost of looking up the currently confifured value. |
| 1030 | if let BufferedThresholdConfig::Desired(x) = entry.buffered_threshold { |
| 1031 | if let Err(e) = stream.set_buffered_amount_low_threshold(x) { |
| 1032 | debug!("Setting buffered_amount_low_threshold failed: {:?}", e); |
| 1033 | entry.do_close = true; |
| 1034 | entry.buffered_threshold = BufferedThresholdConfig::Unconfigured; |
| 1035 | continue; |
| 1036 | } |
| 1037 | |
| 1038 | entry.buffered_threshold = BufferedThresholdConfig::Configured(x); |
| 1039 | } |
| 1040 | match stream_read_data(&mut stream) { |
| 1041 | Ok(Some((buf, ppi))) => { |
| 1042 | if ppi != PayloadProtocolIdentifier::Dcep { |
| 1043 | // This is the normal path for incoming data. |
| 1044 | let buf = ppi_adjust_buf(buf, ppi); |
| 1045 | let binary = matches!( |
| 1046 | ppi, |
| 1047 | PayloadProtocolIdentifier::Binary |
| 1048 | | PayloadProtocolIdentifier::BinaryEmpty |
| 1049 | ); |
| 1050 | return Some(SctpEvent::Data { |
| 1051 | id: entry.id, |
| 1052 | binary, |
| 1053 | data: buf, |
| 1054 | }); |
| 1055 | } |
| 1056 | |
| 1057 | // It's Dcep, either a DcepOpen or DcepAck. |
| 1058 | match entry.state { |
| 1059 | // We are in AwaitConfig state which means we are either going to get it via |
| 1060 | // the DcepOpen, or by an out-of-band configuration via open_stream. |
| 1061 | // This indicates we are doing in-band. |
| 1062 | StreamEntryState::AwaitConfig => { |
| 1063 | let dcep: DcepOpen = match buf.as_slice().try_into() { |
| 1064 | Ok(v) => v, |
| 1065 | Err(e) => { |
| 1066 | warn!("Failed to read incoming DCEP {}: {:?}", entry.id, e); |
| 1067 | entry.do_close = true; |
| 1068 | continue; |
| 1069 | } |
| 1070 | }; |
| 1071 | |
| 1072 | if entry.config.is_none() { |
| 1073 | entry.config = Some((&dcep).into()); |
| 1074 | } else { |
| 1075 | warn!("Received DcepOpen for configured stream: {}", entry.id); |
| 1076 | } |
| 1077 | |
| 1078 | // Apply DcepOpen's reliability parameters to the sctp-proto stream. |
| 1079 | // Without this, the DCEP-receiving side of an in-band channel sends |
| 1080 | // with stream defaults: ordered and fully reliable. |
| 1081 | if !entry.configure_reliability(&mut stream) { |
| 1082 | continue; |
| 1083 | } |
| 1084 | |
| 1085 | let mut obuf = [0]; |
| 1086 | DcepAck.marshal_to(&mut obuf); |
| 1087 | match stream.write_with_ppi(&obuf, PayloadProtocolIdentifier::Dcep) |
| 1088 | { |
| 1089 | Ok(l) => { |
| 1090 | assert!(obuf.len() == l); |
| 1091 | entry.set_state(StreamEntryState::Open); |
| 1092 | |
| 1093 | return Some(SctpEvent::Open { |
| 1094 | id: entry.id, |
| 1095 | label: dcep.label, |
| 1096 | }); |
| 1097 | } |
| 1098 | Err(e) => { |
| 1099 | warn!( |
| 1100 | "Failed to write DCEP ack on stream {}: {:?}", |
| 1101 | entry.id, e |
| 1102 | ); |
| 1103 | entry.do_close = true; |
| 1104 | entry.set_state(StreamEntryState::Closed); |
| 1105 | let stream_id = entry.id; |
| 1106 | let reset_pending = self.sctp_propagate_close(stream_id); |
| 1107 | return Some(SctpEvent::Close { |
| 1108 | id: stream_id, |
| 1109 | reset_pending, |
| 1110 | }); |
| 1111 | } |
| 1112 | } |
| 1113 | } |
| 1114 | StreamEntryState::AwaitDcepAck => { |
| 1115 | let res: Result<DcepAck, _> = buf.as_slice().try_into(); |
| 1116 | |
| 1117 | if let Err(e) = res { |
| 1118 | warn!("Failed to read incoming DCEP ACK {}: {:?}", entry.id, e); |
| 1119 | entry.do_close = true; |
| 1120 | continue; |
| 1121 | } |
| 1122 | |
| 1123 | entry.set_state(StreamEntryState::Open); |
| 1124 | let config = entry.config.as_ref().expect("config when DcepAck"); |
| 1125 | |
| 1126 | return Some(SctpEvent::Open { |
| 1127 | id: entry.id, |
| 1128 | label: config.label.clone(), |
| 1129 | }); |
| 1130 | } |
| 1131 | _ => { |
| 1132 | warn!( |
| 1133 | "Stream {} in wrong state when receiving DCEP: {:?}", |
| 1134 | entry.id, entry.state |
| 1135 | ); |
| 1136 | entry.do_close = true; |
| 1137 | continue; |
| 1138 | } |
| 1139 | } |
| 1140 | } |
| 1141 | Ok(None) => continue, |
| 1142 | Err(_) => entry.do_close = true, |
| 1143 | } |
| 1144 | } |
| 1145 | |
| 1146 | // Reset completions are reported last. Reaching here means the entry loop had |
| 1147 | // no `Close` left to emit, so the close for a released id has been delivered. |
| 1148 | if let Some(id) = self.reset_complete.pop_front() { |
| 1149 | return Some(SctpEvent::StreamResetComplete { id }); |
| 1150 | } |
| 1151 | |
| 1152 | return None; |
| 1153 | } |
| 1154 | } |
| 1155 | |
| 1156 | pub fn poll_timeout(&mut self) -> Option<Instant> { |
| 1157 | let assoc_timeout = self.assoc.as_mut().and_then(|a| a.poll_timeout()); |
| 1158 | |
| 1159 | // Wakeup backstop for entries whose open_stream() is being retried. |
| 1160 | // |
| 1161 | // Normally no wakeup is needed: a blocked open re-attempts on the |
| 1162 | // next do_poll(), and the packet that unblocks it (the peer's |
| 1163 | // RECONFIG-RESPONSE or reset) itself triggers handle_input() and that poll. |
| 1164 | // |
| 1165 | // Returning a wakeup while any entry retries guarantees the open |
| 1166 | // either resolves or fails within STREAM_OPEN_TIMEOUT. |
| 1167 | let retry_timeout = self |
| 1168 | .entries |
| 1169 | .iter() |
| 1170 | .any(|e| e.state == StreamEntryState::AwaitOpen && e.open_deadline.is_some()) |
| 1171 | .then(|| self.last_now + STREAM_OPEN_RETRY_INTERVAL); |
| 1172 | |
| 1173 | match (assoc_timeout, retry_timeout) { |
| 1174 | (Some(a), Some(r)) => Some(a.min(r)), |
| 1175 | (a, r) => a.or(r), |
| 1176 | } |
| 1177 | } |
| 1178 | |
| 1179 | pub fn push_back_transmit(&mut self, data: VecDeque<Bytes>) { |
| 1180 | trace!("Push back transmit: {}", data.len()); |
| 1181 | assert!(self.pushed_back_transmit.is_none()); |
| 1182 | self.pushed_back_transmit = Some(data); |
| 1183 | } |
| 1184 | |
| 1185 | fn poll_transmit(&mut self) -> Option<Transmit> { |
| 1186 | if let Some(t) = self.endpoint.poll_transmit() { |
| 1187 | return Some(t); |
| 1188 | } |
| 1189 | |
| 1190 | if let Some(t) = self.assoc.as_mut()?.poll_transmit(self.last_now) { |
| 1191 | return Some(t); |
| 1192 | } |
| 1193 | |
| 1194 | None |
| 1195 | } |
| 1196 | |
| 1197 | pub fn config(&self, sctp_stream_id: u16) -> Option<&ChannelConfig> { |
| 1198 | entry_by_id(&self.entries, sctp_stream_id).and_then(|s| s.config.as_ref()) |
| 1199 | } |
| 1200 | |
| 1201 | /// Close the sctp stream to allow re-use of the same id. |
| 1202 | /// |
| 1203 | /// Returns whether a reset handshake is outstanding for `stream_id`. |
| 1204 | fn sctp_propagate_close(&mut self, stream_id: u16) -> bool { |
| 1205 | let did_reset = match self.assoc.as_mut().map(|assoc| assoc.stream(stream_id)) { |
| 1206 | // `close()` only fails on the reset, which needs an established |
| 1207 | // association. Closing a channel while the association is shutting down |
| 1208 | // therefore queues nothing and no completion will ever arrive. |
| 1209 | Some(Ok(mut stream)) => stream.close().is_ok(), |
| 1210 | // No stream to reset. |
| 1211 | _ => false, |
| 1212 | }; |
| 1213 | |
| 1214 | if did_reset { |
| 1215 | self.reset_pending.insert(stream_id); |
| 1216 | } |
| 1217 | |
| 1218 | self.reset_pending.contains(&stream_id) |
| 1219 | } |
| 1220 | |
| 1221 | #[cfg(test)] |
| 1222 | pub(crate) fn remote_max_message_size(&self) -> u32 { |
| 1223 | self.remote_max_message_size |
| 1224 | } |
| 1225 | } |
| 1226 | |
| 1227 | fn transmit_to_vec(t: Transmit) -> Option<VecDeque<Bytes>> { |
| 1228 | let Payload::RawEncode(v) = t.payload else { |
| 1229 | return None; |
| 1230 | }; |
| 1231 | |
| 1232 | Some(v.into()) |
| 1233 | } |
| 1234 | |
| 1235 | fn set_state(current_state: &mut RtcSctpState, state: RtcSctpState) { |
| 1236 | if *current_state != state { |
| 1237 | debug!("{:?} => {:?}", current_state, state); |
| 1238 | *current_state = state; |
| 1239 | } |
| 1240 | } |
| 1241 | |
| 1242 | fn entry_index(entries: &[StreamEntry], id: u16) -> Result<usize, usize> { |
| 1243 | entries.binary_search_by_key(&id, |e| e.id) |
| 1244 | } |
| 1245 | |
| 1246 | fn entry_by_id(entries: &[StreamEntry], id: u16) -> Option<&StreamEntry> { |
| 1247 | entry_index(entries, id).ok().map(|i| &entries[i]) |
| 1248 | } |
| 1249 | |
| 1250 | fn entry_by_id_mut(entries: &mut [StreamEntry], id: u16) -> Option<&mut StreamEntry> { |
| 1251 | entry_index(entries, id).ok().map(|i| &mut entries[i]) |
| 1252 | } |
| 1253 | |
| 1254 | fn stream_entry<'a>( |
| 1255 | entries: &'a mut Vec<StreamEntry>, |
| 1256 | id: u16, |
| 1257 | initial_state: StreamEntryState, |
| 1258 | reason: &'static str, |
| 1259 | ) -> &'a mut StreamEntry { |
| 1260 | let idx = match entry_index(entries, id) { |
| 1261 | Ok(idx) => idx, |
| 1262 | Err(idx) => { |
| 1263 | debug!("New stream {} ({:?}): {}", id, initial_state, reason); |
| 1264 | entries.insert( |
| 1265 | idx, |
| 1266 | StreamEntry { |
| 1267 | config: None, |
| 1268 | state: initial_state, |
| 1269 | id, |
| 1270 | do_close: false, |
| 1271 | open_deadline: None, |
| 1272 | buffered_threshold: BufferedThresholdConfig::Unconfigured, |
| 1273 | }, |
| 1274 | ); |
| 1275 | idx |
| 1276 | } |
| 1277 | }; |
| 1278 | |
| 1279 | &mut entries[idx] |
| 1280 | } |
| 1281 | |
| 1282 | fn stream_read_data( |
| 1283 | stream: &mut Stream, |
| 1284 | ) -> Result<Option<(Vec<u8>, PayloadProtocolIdentifier)>, SctpError> { |
| 1285 | let Some(chunks) = stream.read()? else { |
| 1286 | return Ok(None); |
| 1287 | }; |
| 1288 | |
| 1289 | let n = chunks.len(); |
| 1290 | let mut buf = vec![0; n]; |
| 1291 | |
| 1292 | let l = chunks.read(&mut buf)?; |
| 1293 | assert!(l == n); |
| 1294 | |
| 1295 | use PayloadProtocolIdentifier::*; |
| 1296 | match chunks.ppi { |
| 1297 | Dcep | String | Binary => {} // keep as is |
| 1298 | StringEmpty | BinaryEmpty => buf.clear(), |
| 1299 | _ => { |
| 1300 | return Err(SctpError::Proto(ProtoError::Other( |
| 1301 | "Unknown PayloadProtocolIdentifier".into(), |
| 1302 | ))); |
| 1303 | } |
| 1304 | } |
| 1305 | |
| 1306 | Ok(Some((buf, chunks.ppi))) |
| 1307 | } |
| 1308 | |
| 1309 | fn ppi_adjust_buf(mut buf: Vec<u8>, ppi: PayloadProtocolIdentifier) -> Vec<u8> { |
| 1310 | match ppi { |
| 1311 | PayloadProtocolIdentifier::StringEmpty | PayloadProtocolIdentifier::BinaryEmpty => { |
| 1312 | buf.clear(); |
| 1313 | buf |
| 1314 | } |
| 1315 | _ => buf, |
| 1316 | } |
| 1317 | } |
| 1318 | |
| 1319 | impl fmt::Debug for SctpEvent { |
| 1320 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { |
| 1321 | match self { |
| 1322 | Self::Transmit { packets } => f |
| 1323 | .debug_struct("Transmit") |
| 1324 | .field("packets", &packets.len()) |
| 1325 | .finish(), |
| 1326 | Self::Open { id, label } => f |
| 1327 | .debug_struct("Open") |
| 1328 | .field("id", id) |
| 1329 | .field("label", label) |
| 1330 | .finish(), |
| 1331 | Self::Close { id, reset_pending } => f |
| 1332 | .debug_struct("Close") |
| 1333 | .field("id", id) |
| 1334 | .field("reset_pending", reset_pending) |
| 1335 | .finish(), |
| 1336 | Self::StreamResetComplete { id } => f |
| 1337 | .debug_struct("StreamResetComplete") |
| 1338 | .field("id", id) |
| 1339 | .finish(), |
| 1340 | Self::Data { id, binary, data } => f |
| 1341 | .debug_struct("Data") |
| 1342 | .field("id", id) |
| 1343 | .field("binary", binary) |
| 1344 | .field("data", &data.len()) |
| 1345 | .finish(), |
| 1346 | Self::BufferedAmountLow { id } => { |
| 1347 | f.debug_struct("BufferedAmountLow").field("id", id).finish() |
| 1348 | } |
| 1349 | Self::AssociationLost => f.debug_struct("AssociationLost").finish(), |
| 1350 | } |
| 1351 | } |
| 1352 | } |
| 1353 | |
| 1354 | impl From<&ChannelConfig> for DcepOpen { |
| 1355 | fn from(v: &ChannelConfig) -> Self { |
| 1356 | let (channel_type, reliability_parameter) = (&v.reliability).into(); |
| 1357 | DcepOpen { |
| 1358 | unordered: !v.ordered, |
| 1359 | channel_type, |
| 1360 | reliability_parameter, |
| 1361 | priority: 0, |
| 1362 | label: v.label.clone(), |
| 1363 | protocol: v.protocol.clone(), |
| 1364 | } |
| 1365 | } |
| 1366 | } |
| 1367 | |
| 1368 | impl From<&Reliability> for (ReliabilityType, u32) { |
| 1369 | fn from(v: &Reliability) -> Self { |
| 1370 | match v { |
| 1371 | Reliability::Reliable => (ReliabilityType::Reliable, 0), |
| 1372 | Reliability::MaxPacketLifetime { lifetime } => { |
| 1373 | (ReliabilityType::Timed, *lifetime as u32) |
| 1374 | } |
| 1375 | Reliability::MaxRetransmits { retransmits } => { |
| 1376 | (ReliabilityType::Rexmit, *retransmits as u32) |
| 1377 | } |
| 1378 | } |
| 1379 | } |
| 1380 | } |
| 1381 | |
| 1382 | impl From<&DcepOpen> for ChannelConfig { |
| 1383 | fn from(v: &DcepOpen) -> Self { |
| 1384 | ChannelConfig { |
| 1385 | label: v.label.clone(), |
| 1386 | ordered: !v.unordered, |
| 1387 | reliability: (v.channel_type, v.reliability_parameter).into(), |
| 1388 | negotiated: None, |
| 1389 | protocol: v.protocol.clone(), |
| 1390 | } |
| 1391 | } |
| 1392 | } |
| 1393 | |
| 1394 | impl From<(ReliabilityType, u32)> for Reliability { |
| 1395 | fn from((r, p): (ReliabilityType, u32)) -> Self { |
| 1396 | match r { |
| 1397 | ReliabilityType::Reliable => Reliability::Reliable, |
| 1398 | ReliabilityType::Rexmit => Reliability::MaxRetransmits { |
| 1399 | retransmits: p as u16, |
| 1400 | }, |
| 1401 | ReliabilityType::Timed => Reliability::MaxPacketLifetime { lifetime: p as u16 }, |
| 1402 | } |
| 1403 | } |
| 1404 | } |
| 1405 | |
| 1406 | #[cfg(test)] |
| 1407 | mod tests { |
| 1408 | use super::*; |
| 1409 | |
| 1410 | /// Insert an entry directly, keeping `entries` sorted by id. |
| 1411 | fn insert_entry(entries: &mut Vec<StreamEntry>, entry: StreamEntry) { |
| 1412 | let idx = entry_index(entries, entry.id).expect_err("entry should not already exist"); |
| 1413 | entries.insert(idx, entry); |
| 1414 | } |
| 1415 | use str0m_proto::DATAGRAM_MTU_TARGET; |
| 1416 | |
| 1417 | #[test] |
| 1418 | fn partial_snap_init_requires_both_chunks() { |
| 1419 | let now = Instant::now(); |
| 1420 | let mut sctp = RtcSctp::new(DATAGRAM_MTU_TARGET); |
| 1421 | let mut init_data = SctpInitData::new(); |
| 1422 | |
| 1423 | init_data.local_init_chunk().unwrap(); |
| 1424 | |
| 1425 | let err = sctp.init(true, now, Some(init_data), None).unwrap_err(); |
| 1426 | assert!( |
| 1427 | err.to_string() |
| 1428 | .contains("SNAP requires both local and remote SCTP INIT chunks") |
| 1429 | ); |
| 1430 | } |
| 1431 | |
| 1432 | #[test] |
| 1433 | fn malformed_remote_snap_does_not_disable_local_opt_in() { |
| 1434 | let mut sctp = RtcSctp::new(DATAGRAM_MTU_TARGET); |
| 1435 | sctp.enable_snap(); |
| 1436 | |
| 1437 | assert!(!sctp.set_remote_snap_init_string("!!!not-valid-base64!!!")); |
| 1438 | assert!(sctp.snap_enabled()); |
| 1439 | assert!(sctp.ensure_local_snap_init()); |
| 1440 | assert!(sctp.local_sctp_init_for_sdp().is_some()); |
| 1441 | } |
| 1442 | |
| 1443 | /// Helper to connect a client and server RtcSctp pair to Established state. |
| 1444 | fn connect_client_server() -> (RtcSctp, RtcSctp) { |
| 1445 | connect_client_server_with_limits(None, None) |
| 1446 | } |
| 1447 | |
| 1448 | fn connect_client_server_with_limits( |
| 1449 | client_limits: Option<SctpReceiveLimits>, |
| 1450 | server_limits: Option<SctpReceiveLimits>, |
| 1451 | ) -> (RtcSctp, RtcSctp) { |
| 1452 | let now = Instant::now(); |
| 1453 | let mut client = RtcSctp::with_receive_limits(DATAGRAM_MTU_TARGET, client_limits); |
| 1454 | let mut server = RtcSctp::with_receive_limits(DATAGRAM_MTU_TARGET, server_limits); |
| 1455 | |
| 1456 | client.init(true, now, None, None).unwrap(); |
| 1457 | server.init(false, now, None, None).unwrap(); |
| 1458 | |
| 1459 | // Exchange packets until both are Established. |
| 1460 | for _ in 0..20 { |
| 1461 | // Drain client transmits -> feed to server |
| 1462 | while let Some(t) = client.poll_transmit() { |
| 1463 | if let Some(bufs) = transmit_to_vec(t) { |
| 1464 | for buf in bufs { |
| 1465 | server.handle_input(now, &buf); |
| 1466 | } |
| 1467 | } |
| 1468 | } |
| 1469 | // Process server events |
| 1470 | while let Some(e) = server.do_poll() { |
| 1471 | if let SctpEvent::Transmit { packets } = e { |
| 1472 | for buf in packets { |
| 1473 | client.handle_input(now, &buf); |
| 1474 | } |
| 1475 | } |
| 1476 | } |
| 1477 | |
| 1478 | // Drain server transmits -> feed to client |
| 1479 | while let Some(t) = server.poll_transmit() { |
| 1480 | if let Some(bufs) = transmit_to_vec(t) { |
| 1481 | for buf in bufs { |
| 1482 | client.handle_input(now, &buf); |
| 1483 | } |
| 1484 | } |
| 1485 | } |
| 1486 | // Process client events |
| 1487 | while let Some(e) = client.do_poll() { |
| 1488 | if let SctpEvent::Transmit { packets } = e { |
| 1489 | for buf in packets { |
| 1490 | server.handle_input(now, &buf); |
| 1491 | } |
| 1492 | } |
| 1493 | } |
| 1494 | |
| 1495 | // Check if both established |
| 1496 | if client.state == RtcSctpState::Established |
| 1497 | && server.state == RtcSctpState::Established |
| 1498 | { |
| 1499 | break; |
| 1500 | } |
| 1501 | } |
| 1502 | |
| 1503 | assert_eq!(client.state, RtcSctpState::Established); |
| 1504 | assert_eq!(server.state, RtcSctpState::Established); |
| 1505 | |
| 1506 | (client, server) |
| 1507 | } |
| 1508 | |
| 1509 | #[test] |
| 1510 | fn receive_limits_apply_to_both_association_roles() { |
| 1511 | fn pump(from: &mut RtcSctp, to: &mut RtcSctp) -> Vec<SctpEvent> { |
| 1512 | let mut output = Vec::new(); |
| 1513 | while let Some(event) = from.do_poll() { |
| 1514 | if let SctpEvent::Transmit { packets } = event { |
| 1515 | for packet in packets { |
| 1516 | to.handle_input(from.last_now, &packet); |
| 1517 | } |
| 1518 | } else { |
| 1519 | output.push(event); |
| 1520 | } |
| 1521 | } |
| 1522 | output |
| 1523 | } |
| 1524 | let limits = SctpReceiveLimits::new(8192, 32768, 64, 8); |
| 1525 | for receiver_is_client in [false, true] { |
| 1526 | let (mut client, mut server) = connect_client_server_with_limits( |
| 1527 | receiver_is_client.then_some(limits), |
| 1528 | (!receiver_is_client).then_some(limits), |
| 1529 | ); |
| 1530 | let (sender, receiver) = if receiver_is_client { |
| 1531 | (&mut server, &mut client) |
| 1532 | } else { |
| 1533 | (&mut client, &mut server) |
| 1534 | }; |
| 1535 | for id in [0, 65000] { |
| 1536 | let config = ChannelConfig { |
| 1537 | negotiated: Some(id), |
| 1538 | ..Default::default() |
| 1539 | }; |
| 1540 | sender.open_stream(id, config.clone()); |
| 1541 | receiver.open_stream(id, config); |
| 1542 | } |
| 1543 | pump(sender, receiver); |
| 1544 | pump(receiver, sender); |
| 1545 | let mut now = Instant::now(); |
| 1546 | for (id, size) in [(0, 8192), (65000, 512), (65000, 8193)] { |
| 1547 | sender.write(id, true, &vec![42; size]).unwrap(); |
| 1548 | let mut received = None; |
| 1549 | let mut lost = false; |
| 1550 | for _ in 0..200 { |
| 1551 | now += Duration::from_millis(10); |
| 1552 | sender.handle_timeout(now); |
| 1553 | receiver.handle_timeout(now); |
| 1554 | pump(sender, receiver); |
| 1555 | for event in pump(receiver, sender) { |
| 1556 | match event { |
| 1557 | SctpEvent::Data { |
| 1558 | id: stream, data, .. |
| 1559 | } => received = Some((stream, data)), |
| 1560 | SctpEvent::AssociationLost => lost = true, |
| 1561 | _ => {} |
| 1562 | } |
| 1563 | } |
| 1564 | if received.is_some() || lost { |
| 1565 | break; |
| 1566 | } |
| 1567 | } |
| 1568 | if size <= 8192 { |
| 1569 | assert!(!lost); |
| 1570 | assert_eq!(received, Some((id, vec![42; size]))); |
| 1571 | } else { |
| 1572 | assert!(lost); |
| 1573 | assert!(received.is_none()); |
| 1574 | } |
| 1575 | } |
| 1576 | } |
| 1577 | } |
| 1578 | |
| 1579 | #[test] |
| 1580 | fn snap_advertises_the_configured_receive_window() { |
| 1581 | let limits = SctpReceiveLimits::new(8192, 32768, 64, 8); |
| 1582 | let mut direct = SctpInitData::with_receive_limits(limits); |
| 1583 | let init = direct.local_init_chunk().unwrap(); |
| 1584 | assert_eq!(u32::from_be_bytes(init[8..12].try_into().unwrap()), 32768); |
| 1585 | let mut sctp = RtcSctp::with_receive_limits(DATAGRAM_MTU_TARGET, Some(limits)); |
| 1586 | sctp.enable_snap(); |
| 1587 | assert!(sctp.ensure_local_snap_init()); |
| 1588 | let init = sctp |
| 1589 | .snap_init |
| 1590 | .as_ref() |
| 1591 | .unwrap() |
| 1592 | .local_init |
| 1593 | .as_ref() |
| 1594 | .unwrap(); |
| 1595 | assert_eq!(u32::from_be_bytes(init[8..12].try_into().unwrap()), 32768); |
| 1596 | } |
| 1597 | |
| 1598 | #[test] |
| 1599 | fn transmit_retains_packet_ownership_and_order() { |
| 1600 | let packets = vec![Bytes::from(vec![1; 48]), Bytes::from(vec![2; 512])]; |
| 1601 | let pointers = [packets[0].as_ptr(), packets[1].as_ptr()]; |
| 1602 | let transmit = Transmit { |
| 1603 | now: Instant::now(), |
| 1604 | remote: "127.0.0.1:5000".parse().unwrap(), |
| 1605 | ecn: None, |
| 1606 | local_ip: None, |
| 1607 | payload: Payload::RawEncode(packets), |
| 1608 | }; |
| 1609 | let output = transmit_to_vec(transmit).unwrap(); |
| 1610 | assert_eq!(output.len(), 2); |
| 1611 | for (n, packet) in output.iter().enumerate() { |
| 1612 | assert_eq!(packet.as_ptr(), pointers[n]); |
| 1613 | assert!(packet.iter().all(|byte| *byte == n as u8 + 1)); |
| 1614 | } |
| 1615 | } |
| 1616 | |
| 1617 | /// A stream the remote opened can be gone from the association by the time the |
| 1618 | /// application configures it. |
| 1619 | /// |
| 1620 | /// `StreamEntryState::AwaitConfig` is set when a `Readable`/`Writable` event |
| 1621 | /// arrives |
| 1622 | /// for an id we have no config for. If the peer then resets that stream, sctp-proto |
| 1623 | /// unregisters it during `handle_input()`, while our entry stays `AwaitConfig` until |
| 1624 | /// the reset event is drained in a later `do_poll()`. |
| 1625 | #[test] |
| 1626 | fn open_stream_out_of_band_after_remote_reset() { |
| 1627 | let (mut client, _server) = connect_client_server(); |
| 1628 | |
| 1629 | let stream_id: u16 = 3; |
| 1630 | assert!( |
| 1631 | client.assoc.as_mut().unwrap().stream(stream_id).is_err(), |
| 1632 | "the association must not have this stream for the test to mean anything" |
| 1633 | ); |
| 1634 | |
| 1635 | insert_entry( |
| 1636 | &mut client.entries, |
| 1637 | StreamEntry { |
| 1638 | config: None, |
| 1639 | state: StreamEntryState::AwaitConfig, |
| 1640 | id: stream_id, |
| 1641 | do_close: false, |
| 1642 | open_deadline: None, |
| 1643 | buffered_threshold: BufferedThresholdConfig::Unconfigured, |
| 1644 | }, |
| 1645 | ); |
| 1646 | |
| 1647 | client.open_stream( |
| 1648 | stream_id, |
| 1649 | ChannelConfig { |
| 1650 | label: "negotiated".to_string(), |
| 1651 | ordered: true, |
| 1652 | reliability: Reliability::Reliable, |
| 1653 | negotiated: Some(stream_id), |
| 1654 | protocol: String::new(), |
| 1655 | }, |
| 1656 | ); |
| 1657 | |
| 1658 | let entry = client |
| 1659 | .entries |
| 1660 | .iter() |
| 1661 | .find(|e| e.id == stream_id) |
| 1662 | .expect("entry to still exist"); |
| 1663 | |
| 1664 | assert!( |
| 1665 | entry.do_close, |
| 1666 | "a stream that vanished from the association should be marked for close" |
| 1667 | ); |
| 1668 | assert_ne!( |
| 1669 | entry.state, |
| 1670 | StreamEntryState::Open, |
| 1671 | "must not go Open when there is no underlying stream" |
| 1672 | ); |
| 1673 | } |
| 1674 | |
| 1675 | /// Regression test: when `assoc.open_stream()` returns `ErrStreamAlreadyExist` |
| 1676 | /// for an in-band (DCEP) data channel, the entry must eventually transition to |
| 1677 | /// Closed and emit `SctpEvent::Close`. The error is transient (a reset |
| 1678 | /// handshake could clear it), so the entry retries first — but bounded by |
| 1679 | /// `STREAM_OPEN_TIMEOUT`, not the infinite loop this once was. |
| 1680 | #[test] |
| 1681 | fn err_stream_already_exist_in_band_returns_close() { |
| 1682 | let (mut client, _server) = connect_client_server(); |
| 1683 | |
| 1684 | let stream_id: u16 = 0; |
| 1685 | |
| 1686 | // Pre-create the stream in the association so the next open_stream() with the |
| 1687 | // same ID will return ErrStreamAlreadyExist. |
| 1688 | let assoc = client.assoc.as_mut().unwrap(); |
| 1689 | assoc |
| 1690 | .open_stream(stream_id, PayloadProtocolIdentifier::Unknown) |
| 1691 | .expect("first open_stream should succeed"); |
| 1692 | |
| 1693 | // Manually add an entry in AwaitOpen state with in-band config (negotiated: None). |
| 1694 | // This simulates a locally-initiated in-band channel whose stream ID conflicts |
| 1695 | // with one already opened by the remote peer. |
| 1696 | insert_entry( |
| 1697 | &mut client.entries, |
| 1698 | StreamEntry { |
| 1699 | config: Some(ChannelConfig { |
| 1700 | label: "test".to_string(), |
| 1701 | ordered: true, |
| 1702 | reliability: Reliability::Reliable, |
| 1703 | negotiated: None, // in-band |
| 1704 | protocol: String::new(), |
| 1705 | }), |
| 1706 | state: StreamEntryState::AwaitOpen, |
| 1707 | id: stream_id, |
| 1708 | do_close: false, |
| 1709 | open_deadline: None, |
| 1710 | buffered_threshold: BufferedThresholdConfig::Unconfigured, |
| 1711 | }, |
| 1712 | ); |
| 1713 | |
| 1714 | // The first poll retries instead of failing (arms the deadline). |
| 1715 | let event = client.do_poll(); |
| 1716 | assert!( |
| 1717 | event.is_none(), |
| 1718 | "expected retry (no event) for stream {stream_id}, got {event:?}" |
| 1719 | ); |
| 1720 | |
| 1721 | // Past the deadline the open gives up and closes. |
| 1722 | let later = Instant::now() + STREAM_OPEN_TIMEOUT + Duration::from_secs(1); |
| 1723 | client.handle_timeout(later); |
| 1724 | let event = client.do_poll(); |
| 1725 | |
| 1726 | assert!( |
| 1727 | matches!(&event, Some(SctpEvent::Close { id, .. }) if *id == stream_id), |
| 1728 | "expected SctpEvent::Close for stream {stream_id}, got {event:?}" |
| 1729 | ); |
| 1730 | |
| 1731 | // Verify entry transitioned to Closed. |
| 1732 | let entry = entry_by_id(&client.entries, stream_id).unwrap(); |
| 1733 | assert_eq!(entry.state, StreamEntryState::Closed); |
| 1734 | } |
| 1735 | |
| 1736 | #[test] |
| 1737 | fn negotiated_reuse_does_not_report_open_for_old_closed_stream() { |
| 1738 | let (mut client, _server) = connect_client_server(); |
| 1739 | let stream_id = 0; |
| 1740 | |
| 1741 | let assoc = client.assoc.as_mut().unwrap(); |
| 1742 | let mut old = assoc |
| 1743 | .open_stream(stream_id, PayloadProtocolIdentifier::Unknown) |
| 1744 | .expect("old stream should open"); |
| 1745 | old.close().expect("old stream should start closing"); |
| 1746 | |
| 1747 | insert_entry( |
| 1748 | &mut client.entries, |
| 1749 | StreamEntry { |
| 1750 | config: Some(ChannelConfig { |
| 1751 | label: "replacement".to_string(), |
| 1752 | ordered: true, |
| 1753 | reliability: Reliability::Reliable, |
| 1754 | negotiated: Some(stream_id), |
| 1755 | protocol: String::new(), |
| 1756 | }), |
| 1757 | state: StreamEntryState::AwaitOpen, |
| 1758 | id: stream_id, |
| 1759 | do_close: false, |
| 1760 | open_deadline: None, |
| 1761 | buffered_threshold: BufferedThresholdConfig::Unconfigured, |
| 1762 | }, |
| 1763 | ); |
| 1764 | |
| 1765 | for _ in 0..10 { |
| 1766 | match client.do_poll() { |
| 1767 | Some(SctpEvent::Open { id, .. }) if id == stream_id => { |
| 1768 | panic!("replacement was reported open while the old stream still exists") |
| 1769 | } |
| 1770 | Some(_) => {} |
| 1771 | None => break, |
| 1772 | } |
| 1773 | } |
| 1774 | } |
| 1775 | |
| 1776 | #[test] |
| 1777 | fn close_is_reported_before_reset_complete_when_events_are_batched() { |
| 1778 | let now = Instant::now(); |
| 1779 | let (mut client, mut server) = connect_client_server(); |
| 1780 | let stream_id = 0; |
| 1781 | let config = ChannelConfig { |
| 1782 | label: "batched-close".to_string(), |
| 1783 | negotiated: Some(stream_id), |
| 1784 | ..Default::default() |
| 1785 | }; |
| 1786 | |
| 1787 | client.open_stream(stream_id, config.clone()); |
| 1788 | server.open_stream(stream_id, config); |
| 1789 | assert!(matches!( |
| 1790 | client.do_poll(), |
| 1791 | Some(SctpEvent::Open { id, .. }) if id == stream_id |
| 1792 | )); |
| 1793 | assert!(matches!( |
| 1794 | server.do_poll(), |
| 1795 | Some(SctpEvent::Open { id, .. }) if id == stream_id |
| 1796 | )); |
| 1797 | |
| 1798 | client.close_stream(stream_id); |
| 1799 | |
| 1800 | // Move all reset packets in both directions without polling client |
| 1801 | // application events. This batches Finished and ResetComplete in the |
| 1802 | // association event queue, which is valid for a sans-I/O caller. |
| 1803 | for _ in 0..4 { |
| 1804 | while let Some(transmit) = client.poll_transmit() { |
| 1805 | for packet in transmit_to_vec(transmit).unwrap() { |
| 1806 | server.handle_input(now, &packet); |
| 1807 | } |
| 1808 | } |
| 1809 | while let Some(transmit) = server.poll_transmit() { |
| 1810 | for packet in transmit_to_vec(transmit).unwrap() { |
| 1811 | client.handle_input(now, &packet); |
| 1812 | } |
| 1813 | } |
| 1814 | } |
| 1815 | |
| 1816 | for _ in 0..10 { |
| 1817 | match client.do_poll() { |
| 1818 | Some(SctpEvent::Close { id, .. }) if id == stream_id => return, |
| 1819 | Some(SctpEvent::StreamResetComplete { id }) if id == stream_id => { |
| 1820 | panic!("ResetComplete was reported before Close") |
| 1821 | } |
| 1822 | Some(_) => {} |
| 1823 | None => break, |
| 1824 | } |
| 1825 | } |
| 1826 | |
| 1827 | panic!("stream close was not reported"); |
| 1828 | } |
| 1829 | |
| 1830 | /// Regression test: the DCEP-receiving side of an in-band channel must apply |
| 1831 | /// the DcepOpen reliability parameters (here: unordered) to its sctp-proto |
| 1832 | /// stream, instead of sending with stream defaults (ordered, fully reliable). |
| 1833 | #[test] |
| 1834 | fn dcep_receiver_applies_reliability_params() { |
| 1835 | let now = Instant::now(); |
| 1836 | let (mut client, mut server) = connect_client_server(); |
| 1837 | |
| 1838 | // Drain `from`, feeding packets to `to` and returning the raw bytes. |
| 1839 | let pump = |from: &mut RtcSctp, to: &mut RtcSctp| { |
| 1840 | let mut wire = vec![]; |
| 1841 | while let Some(e) = from.do_poll() { |
| 1842 | if let SctpEvent::Transmit { packets } = e { |
| 1843 | for p in packets { |
| 1844 | to.handle_input(now, &p); |
| 1845 | wire.extend(p); |
| 1846 | } |
| 1847 | } |
| 1848 | } |
| 1849 | wire |
| 1850 | }; |
| 1851 | |
| 1852 | // Client opens an unordered in-band (DCEP) channel; once the handshake is |
| 1853 | // pumped through, the server (DCEP receiver) sends data back. |
| 1854 | client.open_stream( |
| 1855 | 0, |
| 1856 | ChannelConfig { |
| 1857 | ordered: false, |
| 1858 | ..Default::default() |
| 1859 | }, |
| 1860 | ); |
| 1861 | pump(&mut client, &mut server); // DcepOpen |
| 1862 | pump(&mut server, &mut client); // DcepAck, server side now open |
| 1863 | let payload = b"from dcep receiver"; |
| 1864 | server.write(0, true, payload).unwrap(); |
| 1865 | |
| 1866 | // The 16-byte DATA chunk header puts the flags byte 15 bytes before the |
| 1867 | // payload it carries. 0x04 is the U (unordered) flag. |
| 1868 | let wire = pump(&mut server, &mut client); |
| 1869 | let pos = wire |
| 1870 | .windows(payload.len()) |
| 1871 | .position(|w| w == payload) |
| 1872 | .unwrap(); |
| 1873 | assert!( |
| 1874 | wire[pos - 15] & 0x04 != 0, |
| 1875 | "DCEP receiver should send unordered" |
| 1876 | ); |
| 1877 | } |
| 1878 | |
| 1879 | #[test] |
| 1880 | fn max_payload_size_matches_mtu_minus_overhead() { |
| 1881 | let overhead = crate::io::MAX_DTLS_OVERHEAD + SCTP_OVERHEAD; |
| 1882 | |
| 1883 | let default_sctp = RtcSctp::new(DATAGRAM_MTU_TARGET); |
| 1884 | assert_eq!( |
| 1885 | default_sctp.max_payload_size(), |
| 1886 | DATAGRAM_MTU_TARGET - overhead |
| 1887 | ); |
| 1888 | |
| 1889 | let small_sctp = RtcSctp::new(900); |
| 1890 | assert_eq!(small_sctp.max_payload_size(), 900 - overhead); |
| 1891 | } |
| 1892 | } |