From 98a5c4724c751e3fc9c7e94c02ea8616287675ed Mon Sep 17 00:00:00 2001 From: Haiko Schol Date: Thu, 29 Jan 2026 17:57:19 +0700 Subject: [PATCH 1/6] Init SCTP with max msg size constants --- Cargo.lock | 3 +-- Cargo.toml | 3 ++- src/sctp/mod.rs | 16 ++++++++++++++-- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 344a58fb0..8649d85d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1589,8 +1589,7 @@ dependencies = [ [[package]] name = "sctp-proto" version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57239be45f014795255107f85dfe3503791711e77c4995f936caf89a2afe0c16" +source = "git+https://github.com/haikoschol/sctp-proto?branch=max-receive-message-size#4d24dc5480b6fa6e91d50cdac22954ca2f6e5444" dependencies = [ "bytes", "crc", diff --git a/Cargo.toml b/Cargo.toml index d4d2042dd..34e47d99a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,7 +46,8 @@ _internal_test_exports = ["str0m-proto/_internal_test_exports"] [dependencies] tracing = "0.1.37" fastrand = "2.0.1" -sctp-proto = "0.7.0" +#sctp-proto = "0.7.0" +sctp-proto = { git = "https://github.com/haikoschol/sctp-proto", branch="max-receive-message-size" } combine = "4.6.6" subtle = "2.0.0" arrayvec = "0.7.6" diff --git a/src/sctp/mod.rs b/src/sctp/mod.rs index d41003f17..5cca9a7b7 100644 --- a/src/sctp/mod.rs +++ b/src/sctp/mod.rs @@ -24,6 +24,12 @@ pub use error::SctpError; /// Bytes that can be buffered inside str0m across all streams. const MAX_BUFFERED_ACROSS_STREAMS: usize = 128 * 1024; +/// Maximum message size we advertise in SDP (what we can receive) +pub const LOCAL_MAX_MESSAGE_SIZE: u32 = 256 * 1024; + +/// Default max message size if remote doesn't advertise +pub const DEFAULT_REMOTE_MAX_MESSAGE_SIZE: u32 = 64 * 1024; + pub(crate) struct RtcSctp { state: RtcSctpState, endpoint: Endpoint, @@ -233,7 +239,12 @@ impl RtcSctp { // DTLS above MTU 1200: 1277 // Let's try 1120, see if we can avoid warnings. config.max_payload_size(1120); - let server_config = ServerConfig::default(); + let mut server_config = ServerConfig::default(); + + server_config.transport = Arc::new( + TransportConfig::default().with_max_receive_message_size(LOCAL_MAX_MESSAGE_SIZE) + ); + let endpoint = Endpoint::new(Arc::new(config), Some(Arc::new(server_config))); let fake_addr = "1.1.1.1:5000".parse().unwrap(); @@ -266,7 +277,8 @@ impl RtcSctp { // and SCTP should not give up until ICE gives up. let transport = TransportConfig::default() .with_max_init_retransmits(None) - .with_max_data_retransmits(None); + .with_max_data_retransmits(None) + .with_max_receive_message_size(DEFAULT_REMOTE_MAX_MESSAGE_SIZE); let config = ClientConfig { transport: Arc::new(transport), From 82c4cad0df2f420c3cbb377621112aff7320da33 Mon Sep 17 00:00:00 2001 From: Haiko Schol Date: Mon, 2 Feb 2026 14:01:06 +0700 Subject: [PATCH 2/6] Extract and apply max-message-size from SDP --- Cargo.lock | 2 +- src/change/sdp.rs | 165 ++++++++++++++++++++++++++++++++++++++++++---- src/sctp/mod.rs | 21 +++++- src/sdp/data.rs | 11 ++++ 4 files changed, 186 insertions(+), 13 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 8649d85d4..63b481ab5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1589,7 +1589,7 @@ dependencies = [ [[package]] name = "sctp-proto" version = "0.7.0" -source = "git+https://github.com/haikoschol/sctp-proto?branch=max-receive-message-size#4d24dc5480b6fa6e91d50cdac22954ca2f6e5444" +source = "git+https://github.com/haikoschol/sctp-proto?branch=max-receive-message-size#d2fd466de46696045b11c195bc574c9f5bc6d1d3" dependencies = [ "bytes", "crc", diff --git a/src/change/sdp.rs b/src/change/sdp.rs index 5949161d4..1047c8455 100644 --- a/src/change/sdp.rs +++ b/src/change/sdp.rs @@ -63,7 +63,7 @@ impl<'a> SdpApi<'a> { /// // send json_answer to remote peer. /// let json_answer = serde_json::to_vec(&answer).unwrap(); /// ``` - pub fn accept_offer(self, offer: SdpOffer) -> Result { + pub fn accept_offer(&mut self, offer: SdpOffer) -> Result { debug!("Accept offer"); // Invalidate any outstanding PendingOffer. @@ -100,7 +100,7 @@ impl<'a> SdpApi<'a> { init_dtls(self.rtc, &offer)?; // Modify session with offer - apply_offer(&mut self.rtc.session, offer)?; + apply_offer(&mut self.rtc, offer)?; // Handle potentially new m=application line. let client = self.rtc.dtls.is_active().expect("DTLS active to be set"); @@ -138,7 +138,7 @@ impl<'a> SdpApi<'a> { /// rtc.sdp_api().accept_answer(pending, answer).unwrap(); /// ``` pub fn accept_answer( - self, + mut self, mut pending: SdpPendingOffer, answer: SdpAnswer, ) -> Result<(), RtcError> { @@ -175,7 +175,7 @@ impl<'a> SdpApi<'a> { let new_channels = pending.changes.take_new_channels(); // Modify session with answer - apply_answer(&mut self.rtc.session, pending.changes, answer)?; + apply_answer(&mut self.rtc, pending.changes, answer)?; // Handle potentially new m=application line. let client = self.rtc.dtls.is_active().expect("DTLS to be inited"); @@ -800,9 +800,10 @@ fn as_sdp(session: &Session, params: AsSdpParams) -> Sdp { } } -fn apply_offer(session: &mut Session, offer: SdpOffer) -> Result<(), RtcError> { +fn apply_offer(rtc: &mut Rtc, offer: SdpOffer) -> Result<(), RtcError> { offer.assert_consistency()?; + let session = &mut rtc.session; update_session(session, &offer); let bundle_mids = offer.bundle_mids(); @@ -812,16 +813,20 @@ fn apply_offer(session: &mut Session, offer: SdpOffer) -> Result<(), RtcError> { ensure_stream_tx(session); + // Extract remote's max-message-size from application m-line + if let Some(app_line) = offer.media_lines.iter().find(|m| m.typ.is_channel()) { + if let Some(max_size) = app_line.max_message_size() { + rtc.sctp.set_remote_max_message_size(max_size as u32); + } + } + Ok(()) } -fn apply_answer( - session: &mut Session, - pending: Changes, - answer: SdpAnswer, -) -> Result<(), RtcError> { +fn apply_answer(rtc: &mut Rtc, pending: Changes, answer: SdpAnswer) -> Result<(), RtcError> { answer.assert_consistency()?; + let session = &mut rtc.session; update_session(session, &answer); let bundle_mids = answer.bundle_mids(); @@ -839,6 +844,13 @@ fn apply_answer( ensure_stream_tx(session); + // Extract remote's max-message-size from application m-line + if let Some(app_line) = answer.media_lines.iter().find(|m| m.typ.is_channel()) { + if let Some(max_size) = app_line.max_message_size() { + rtc.sctp.set_remote_max_message_size(max_size as u32); + } + } + Ok(()) } @@ -1263,7 +1275,9 @@ impl AsSdpMediaLine for (Mid, usize) { ) -> MediaLine { attrs.push(MediaAttribute::Mid(self.0)); attrs.push(MediaAttribute::SctpPort(5000)); - attrs.push(MediaAttribute::MaxMessageSize(262144)); + attrs.push(MediaAttribute::MaxMessageSize( + crate::sctp::LOCAL_MAX_MESSAGE_SIZE as usize, + )); MediaLine { typ: sdp::MediaType::Application, @@ -2010,4 +2024,133 @@ mod test { // No space at the end assert_eq!(count_lines(&line_string, "a=rid:no_attrs send"), 1); } + + #[test] + fn test_local_max_message_size_advertised() { + crate::init_crypto_default(); + + let now = Instant::now(); + let mut rtc = Rtc::new(now); + + // Create an offer with a data channel + let mut change = rtc.sdp_api(); + change.add_channel("test-channel".into()); + let (offer, _) = change.apply().unwrap(); + + // Find the application m-line in the offer + let app_line = offer + .media_lines + .iter() + .find(|m| m.typ.is_channel()) + .expect("should have application m-line"); + + // Verify that max-message-size attribute is present and matches LOCAL_MAX_MESSAGE_SIZE + let max_size = app_line + .max_message_size() + .expect("max-message-size attribute should be present"); + + assert_eq!( + max_size, + crate::sctp::LOCAL_MAX_MESSAGE_SIZE as usize, + "max-message-size should match LOCAL_MAX_MESSAGE_SIZE constant" + ); + + // Also verify it's in the SDP string output + let sdp_string = offer.to_sdp_string(); + let expected_line = format!("a=max-message-size:{}", crate::sctp::LOCAL_MAX_MESSAGE_SIZE); + assert!( + sdp_string.contains(&expected_line), + "SDP should contain max-message-size attribute with LOCAL_MAX_MESSAGE_SIZE value" + ); + } + + #[test] + fn test_remote_max_message_size_parsing() { + // Parse SDP with max-message-size attribute and verify value is extracted correctly + let sdp = "v=0\r\n\ + o=- 0 0 IN IP4 172.17.0.1\r\n\ + s=-\r\n\ + c=IN IP4 172.17.0.1\r\n\ + t=0 0\r\n\ + a=group:BUNDLE 0\r\n\ + a=fingerprint:sha-256 B4:12:1C:7C:7D:ED:F1:FA:61:07:57:9C:29:BE:58:E3:BC:41:E7:13:8E:7D:D3:9D:1F:94:6E:A5:23:46:94:23\r\n\ + m=application 9999 UDP/DTLS/SCTP webrtc-datachannel\r\n\ + a=mid:0\r\n\ + a=ice-ufrag:test\r\n\ + a=ice-pwd:testpassword1234\r\n\ + a=setup:actpass\r\n\ + a=sctp-port:5000\r\n\ + a=max-message-size:131072\r\n\ + "; + + let offer = SdpOffer::from_sdp_string(sdp).expect("should parse"); + + // Find the application m-line + let app_line = offer + .media_lines + .iter() + .find(|m| m.typ.is_channel()) + .expect("should have application m-line"); + + assert_eq!( + app_line.max_message_size(), + Some(131072), + "max-message-size should be parsed as 131072" + ); + } + + #[test] + fn test_remote_max_message_size_applied() { + crate::init_crypto_default(); + + let now = Instant::now(); + let mut rtc1 = Rtc::new(now); + let mut rtc2 = Rtc::new(now); + + // Create an offer from rtc1 with a channel + let mut change1 = rtc1.sdp_api(); + change1.add_channel("test-channel".into()); + let (offer1, pending1) = change1.apply().unwrap(); + + // Get the offer SDP string and modify it to have a custom max-message-size + let custom_max_size1 = 98304u32; + let sdp_string = offer1.to_sdp_string(); + let modified_sdp = sdp_string.replace( + &format!("a=max-message-size:{}", crate::sctp::LOCAL_MAX_MESSAGE_SIZE), + &format!("a=max-message-size:{}", custom_max_size1), + ); + + let modified_offer = + SdpOffer::from_sdp_string(&modified_sdp).expect("modified SDP should parse"); + + let answer = rtc2.sdp_api().accept_offer(modified_offer).unwrap(); + + // Verify that rtc2's SCTP send limit is set to the custom value from rtc1's offer + assert_eq!( + rtc2.sctp.remote_max_message_size(), + Some(custom_max_size1), + "rtc2 should have remote max message size set to rtc1's advertised value" + ); + + // Now verify the reverse: rtc1 accepts rtc2's answer and applies its max-message-size + let custom_max_size2 = 131072u32; + let sdp_string = answer.to_sdp_string(); + let modified_sdp = sdp_string.replace( + &format!("a=max-message-size:{}", crate::sctp::LOCAL_MAX_MESSAGE_SIZE), + &format!("a=max-message-size:{}", custom_max_size2), + ); + + let modified_answer = + SdpAnswer::from_sdp_string(&modified_sdp).expect("modified SDP should parse"); + + rtc1.sdp_api() + .accept_answer(pending1, modified_answer) + .unwrap(); + + assert_eq!( + rtc1.sctp.remote_max_message_size(), + Some(custom_max_size2), + "rtc1 should have remote max message size set to rtc2's advertised value" + ); + } } diff --git a/src/sctp/mod.rs b/src/sctp/mod.rs index 5cca9a7b7..3436f14a9 100644 --- a/src/sctp/mod.rs +++ b/src/sctp/mod.rs @@ -40,6 +40,7 @@ pub(crate) struct RtcSctp { pushed_back_transmit: Option>>, last_now: Instant, client: bool, + remote_max_message_size: Option, } /// This is okay because there is no way for a user of Rtc to interact with the Sctp subsystem @@ -242,7 +243,7 @@ impl RtcSctp { let mut server_config = ServerConfig::default(); server_config.transport = Arc::new( - TransportConfig::default().with_max_receive_message_size(LOCAL_MAX_MESSAGE_SIZE) + TransportConfig::default().with_max_receive_message_size(LOCAL_MAX_MESSAGE_SIZE), ); let endpoint = Endpoint::new(Arc::new(config), Some(Arc::new(server_config))); @@ -258,6 +259,7 @@ impl RtcSctp { pushed_back_transmit: None, last_now: Instant::now(), // placeholder until init() client: false, + remote_max_message_size: None, } } @@ -279,6 +281,7 @@ impl RtcSctp { .with_max_init_retransmits(None) .with_max_data_retransmits(None) .with_max_receive_message_size(DEFAULT_REMOTE_MAX_MESSAGE_SIZE); + // Note: send limit set later after SDP negotiation let config = ClientConfig { transport: Arc::new(transport), @@ -803,6 +806,22 @@ impl RtcSctp { .find(|s| s.id == sctp_stream_id) .and_then(|s| s.config.as_ref()) } + + /// Set the remote peer's max-message-size (from SDP) + /// This limits how large messages we can send + pub fn set_remote_max_message_size(&mut self, size: u32) { + self.remote_max_message_size = Some(size); + if let Some(assoc) = &mut self.assoc { + assoc.set_max_send_message_size(size); + } + } + + /// Get the remote peer's max-message-size (from SDP negotiation) + /// Returns None if not yet negotiated + #[cfg(test)] + pub(crate) fn remote_max_message_size(&self) -> Option { + self.remote_max_message_size + } } fn transmit_to_vec(t: Transmit) -> Option>> { diff --git a/src/sdp/data.rs b/src/sdp/data.rs index dfd769645..9a42b63bb 100644 --- a/src/sdp/data.rs +++ b/src/sdp/data.rs @@ -642,6 +642,17 @@ impl MediaLine { v } + + /// Get the max-message-size attribute value, if present + pub fn max_message_size(&self) -> Option { + self.attrs.iter().find_map(|a| { + if let MediaAttribute::MaxMessageSize(size) = a { + Some(*size) + } else { + None + } + }) + } } #[derive(Debug, Clone, PartialEq, Eq)] From 8ac65e4dfac7f714e1bb56cf3c2018b765ac6f43 Mon Sep 17 00:00:00 2001 From: Haiko Schol Date: Mon, 2 Feb 2026 18:02:52 +0700 Subject: [PATCH 3/6] Consume self in SdpApi::accept_offer --- src/change/sdp.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/change/sdp.rs b/src/change/sdp.rs index 1047c8455..2f50cf25f 100644 --- a/src/change/sdp.rs +++ b/src/change/sdp.rs @@ -63,7 +63,7 @@ impl<'a> SdpApi<'a> { /// // send json_answer to remote peer. /// let json_answer = serde_json::to_vec(&answer).unwrap(); /// ``` - pub fn accept_offer(&mut self, offer: SdpOffer) -> Result { + pub fn accept_offer(mut self, offer: SdpOffer) -> Result { debug!("Accept offer"); // Invalidate any outstanding PendingOffer. From 892488fcab55ccc14050b09c4031968c77c86f7a Mon Sep 17 00:00:00 2001 From: Haiko Schol Date: Tue, 3 Feb 2026 13:44:24 +0700 Subject: [PATCH 4/6] Use correct const for max_receive_message_size LOCAL_MAX_MESSAGE_SIZE is what we are willing to receive. DEFAULT_REMOTE_MAX_MESSAGE_SIZE is what the remote is willing to receive, hence we are able to send. --- src/sctp/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sctp/mod.rs b/src/sctp/mod.rs index 3436f14a9..c45fd3771 100644 --- a/src/sctp/mod.rs +++ b/src/sctp/mod.rs @@ -280,7 +280,7 @@ impl RtcSctp { let transport = TransportConfig::default() .with_max_init_retransmits(None) .with_max_data_retransmits(None) - .with_max_receive_message_size(DEFAULT_REMOTE_MAX_MESSAGE_SIZE); + .with_max_receive_message_size(LOCAL_MAX_MESSAGE_SIZE); // Note: send limit set later after SDP negotiation let config = ClientConfig { From 8eed417c86081a64dd3471af6eccadd7df490187 Mon Sep 17 00:00:00 2001 From: Haiko Schol Date: Tue, 3 Feb 2026 14:45:45 +0700 Subject: [PATCH 5/6] Set remote_max_message_size in SCTP initialization --- src/change/direct.rs | 2 +- src/change/sdp.rs | 42 +++++++++++++++++++++--------------------- src/lib.rs | 5 +++-- src/sctp/mod.rs | 27 ++++++++++----------------- 4 files changed, 35 insertions(+), 41 deletions(-) diff --git a/src/change/direct.rs b/src/change/direct.rs index 6dd6ed8d1..d8117dbbf 100644 --- a/src/change/direct.rs +++ b/src/change/direct.rs @@ -98,7 +98,7 @@ impl<'a> DirectApi<'a> { /// Start the SCTP over DTLS. pub fn start_sctp(&mut self, client: bool) { - self.rtc.init_sctp(client) + self.rtc.init_sctp(client, None) } /// Create a new data channel. diff --git a/src/change/sdp.rs b/src/change/sdp.rs index 2f50cf25f..c26ef45eb 100644 --- a/src/change/sdp.rs +++ b/src/change/sdp.rs @@ -1,8 +1,5 @@ //! Strategy that amends the [`Rtc`] via SDP OFFER/ANSWER negotiation. -use std::fmt; -use std::ops::{Deref, DerefMut}; - use crate::channel::ChannelId; use crate::crypto::Fingerprint; use crate::format::CodecConfig; @@ -20,6 +17,9 @@ use crate::session::Session; use crate::Rtc; use crate::RtcError; use crate::{Candidate, IceCreds}; +use std::fmt; +use std::ops::{Deref, DerefMut}; +use std::slice::Iter; pub use crate::sdp::{SdpAnswer, SdpOffer}; use crate::streams::{Streams, DEFAULT_RTX_CACHE_DURATION, DEFAULT_RTX_RATIO_CAP}; @@ -99,13 +99,15 @@ impl<'a> SdpApi<'a> { // Ensure setup=active/passive is corresponding remote and init dtls. init_dtls(self.rtc, &offer)?; + let remote_max_message_size = extract_max_message_size(offer.media_lines.iter()); + // Modify session with offer apply_offer(&mut self.rtc, offer)?; // Handle potentially new m=application line. let client = self.rtc.dtls.is_active().expect("DTLS active to be set"); if self.rtc.session.app().is_some() { - self.rtc.init_sctp(client); + self.rtc.init_sctp(client, remote_max_message_size); } let params = AsSdpParams::new(self.rtc, None); @@ -174,13 +176,15 @@ impl<'a> SdpApi<'a> { // Split out new channels, since that is not handled by the Session. let new_channels = pending.changes.take_new_channels(); + let remote_max_message_size = extract_max_message_size(answer.media_lines.iter()); + // Modify session with answer apply_answer(&mut self.rtc, pending.changes, answer)?; // Handle potentially new m=application line. let client = self.rtc.dtls.is_active().expect("DTLS to be inited"); if self.rtc.session.app().is_some() { - self.rtc.init_sctp(client); + self.rtc.init_sctp(client, remote_max_message_size); } for (id, config) in new_channels { @@ -813,13 +817,6 @@ fn apply_offer(rtc: &mut Rtc, offer: SdpOffer) -> Result<(), RtcError> { ensure_stream_tx(session); - // Extract remote's max-message-size from application m-line - if let Some(app_line) = offer.media_lines.iter().find(|m| m.typ.is_channel()) { - if let Some(max_size) = app_line.max_message_size() { - rtc.sctp.set_remote_max_message_size(max_size as u32); - } - } - Ok(()) } @@ -844,13 +841,6 @@ fn apply_answer(rtc: &mut Rtc, pending: Changes, answer: SdpAnswer) -> Result<() ensure_stream_tx(session); - // Extract remote's max-message-size from application m-line - if let Some(app_line) = answer.media_lines.iter().find(|m| m.typ.is_channel()) { - if let Some(max_size) = app_line.max_message_size() { - rtc.sctp.set_remote_max_message_size(max_size as u32); - } - } - Ok(()) } @@ -1239,6 +1229,16 @@ fn update_media( } } +fn extract_max_message_size(mut media_lines: Iter) -> Option { + if let Some(app_line) = media_lines.find(|m| m.typ.is_channel()) { + if let Some(max_size) = app_line.max_message_size() { + return Some(max_size as u32); + } + } + + None +} + trait AsSdpMediaLine { fn mid(&self) -> Mid; fn msid(&self) -> Option<&Msid>; @@ -2128,7 +2128,7 @@ mod test { // Verify that rtc2's SCTP send limit is set to the custom value from rtc1's offer assert_eq!( rtc2.sctp.remote_max_message_size(), - Some(custom_max_size1), + custom_max_size1, "rtc2 should have remote max message size set to rtc1's advertised value" ); @@ -2149,7 +2149,7 @@ mod test { assert_eq!( rtc1.sctp.remote_max_message_size(), - Some(custom_max_size2), + custom_max_size2, "rtc1 should have remote max message size set to rtc2's advertised value" ); } diff --git a/src/lib.rs b/src/lib.rs index ef7140120..64ac35301 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1391,14 +1391,15 @@ impl Rtc { Ok(()) } - fn init_sctp(&mut self, client: bool) { + fn init_sctp(&mut self, client: bool, remote_max_message_size: Option) { // If we got an m=application line, ensure we have negotiated the // SCTP association with the other side. if self.sctp.is_inited() { return; } - self.sctp.init(client, self.last_now); + self.sctp + .init(client, self.last_now, remote_max_message_size); } /// Creates a new Mid that is not in the session already. diff --git a/src/sctp/mod.rs b/src/sctp/mod.rs index c45fd3771..1ca27c0b2 100644 --- a/src/sctp/mod.rs +++ b/src/sctp/mod.rs @@ -40,7 +40,7 @@ pub(crate) struct RtcSctp { pushed_back_transmit: Option>>, last_now: Instant, client: bool, - remote_max_message_size: Option, + remote_max_message_size: u32, } /// This is okay because there is no way for a user of Rtc to interact with the Sctp subsystem @@ -259,7 +259,7 @@ impl RtcSctp { pushed_back_transmit: None, last_now: Instant::now(), // placeholder until init() client: false, - remote_max_message_size: None, + remote_max_message_size: DEFAULT_REMOTE_MAX_MESSAGE_SIZE, } } @@ -267,12 +267,16 @@ impl RtcSctp { self.state != RtcSctpState::Uninited } - pub fn init(&mut self, client: bool, now: Instant) { + pub fn init(&mut self, client: bool, now: Instant, remote_max_message_size: Option) { assert!(self.state == RtcSctpState::Uninited); self.client = client; self.last_now = now; + if let Some(max_msg_size) = remote_max_message_size { + self.remote_max_message_size = max_msg_size; + } + if client { // For WebRTC, we never want to give up retransmitting // init and data packets. The connectivity is in ICE, @@ -280,8 +284,8 @@ impl RtcSctp { let transport = TransportConfig::default() .with_max_init_retransmits(None) .with_max_data_retransmits(None) - .with_max_receive_message_size(LOCAL_MAX_MESSAGE_SIZE); - // Note: send limit set later after SDP negotiation + .with_max_receive_message_size(LOCAL_MAX_MESSAGE_SIZE) + .with_max_send_message_size(self.remote_max_message_size); let config = ClientConfig { transport: Arc::new(transport), @@ -807,19 +811,8 @@ impl RtcSctp { .and_then(|s| s.config.as_ref()) } - /// Set the remote peer's max-message-size (from SDP) - /// This limits how large messages we can send - pub fn set_remote_max_message_size(&mut self, size: u32) { - self.remote_max_message_size = Some(size); - if let Some(assoc) = &mut self.assoc { - assoc.set_max_send_message_size(size); - } - } - - /// Get the remote peer's max-message-size (from SDP negotiation) - /// Returns None if not yet negotiated #[cfg(test)] - pub(crate) fn remote_max_message_size(&self) -> Option { + pub(crate) fn remote_max_message_size(&self) -> u32 { self.remote_max_message_size } } From 6eceb4b6a7e47da66b265a1aa804b6044186f366 Mon Sep 17 00:00:00 2001 From: Haiko Schol Date: Tue, 3 Feb 2026 14:46:22 +0700 Subject: [PATCH 6/6] Set max_send_message_size on newly connected Association --- src/sctp/mod.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/sctp/mod.rs b/src/sctp/mod.rs index 1ca27c0b2..95a5be169 100644 --- a/src/sctp/mod.rs +++ b/src/sctp/mod.rs @@ -557,6 +557,7 @@ impl RtcSctp { while let Some(e) = assoc.poll() { if let Event::Connected = e { + assoc.set_max_send_message_size(self.remote_max_message_size); set_state(&mut self.state, RtcSctpState::Established); return self.poll(); }