22
33use std:: fmt;
44use std:: ops:: { Deref , DerefMut } ;
5+ use std:: slice:: Iter ;
56
67use crate :: Rtc ;
78use crate :: RtcError ;
@@ -100,6 +101,8 @@ impl<'a> SdpApi<'a> {
100101 // Ensure setup=active/passive is corresponding remote and init dtls.
101102 init_dtls ( self . rtc , & offer) ?;
102103
104+ let remote_max_message_size = extract_max_message_size ( offer. media_lines . iter ( ) ) ;
105+
103106 // Extract a=sctp-init from remote offer before apply_offer consumes it.
104107 let remote_sctp_init = offer. sctp_init ( ) . map ( |v| v. to_owned ( ) ) ;
105108
@@ -119,7 +122,8 @@ impl<'a> SdpApi<'a> {
119122
120123 if self . rtc . session . app ( ) . is_some ( ) {
121124 let init_data = self . rtc . sctp . build_snap_init_data ( ) ;
122- self . rtc . try_init_sctp ( client, init_data) ?;
125+ self . rtc
126+ . try_init_sctp ( client, init_data, remote_max_message_size) ?;
123127 }
124128
125129 let params = AsSdpParams :: new ( self . rtc , None ) ;
@@ -198,6 +202,8 @@ impl<'a> SdpApi<'a> {
198202 // Split out new channels, since that is not handled by the Session.
199203 let new_channels = pending. changes . take_new_channels ( ) ;
200204
205+ let remote_max_message_size = extract_max_message_size ( answer. media_lines . iter ( ) ) ;
206+
201207 // Modify session with answer
202208 apply_answer ( & mut self . rtc . session , pending. changes , answer) ?;
203209
@@ -211,7 +217,8 @@ impl<'a> SdpApi<'a> {
211217
212218 if self . rtc . session . app ( ) . is_some ( ) {
213219 let init_data = self . rtc . sctp . build_snap_init_data ( ) ;
214- self . rtc . try_init_sctp ( client, init_data) ?;
220+ self . rtc
221+ . try_init_sctp ( client, init_data, remote_max_message_size) ?;
215222 }
216223
217224 for ( id, config) in new_channels {
@@ -1392,6 +1399,23 @@ fn update_media(
13921399 }
13931400}
13941401
1402+ fn extract_max_message_size ( mut media_lines : Iter < MediaLine > ) -> Option < u32 > {
1403+ if let Some ( app_line) = media_lines. find ( |m| m. typ . is_channel ( ) ) {
1404+ if let Some ( max_size) = app_line. max_message_size ( ) {
1405+ // RFC 8841 §6.1: a value of 0 means the peer can receive a message of
1406+ // any size, subject to local capacity. Represent that as the maximum.
1407+ let max_size = if max_size == 0 {
1408+ u32:: MAX
1409+ } else {
1410+ u32:: try_from ( max_size) . unwrap_or ( u32:: MAX )
1411+ } ;
1412+ return Some ( max_size) ;
1413+ }
1414+ }
1415+
1416+ None
1417+ }
1418+
13951419trait AsSdpMediaLine {
13961420 fn mid ( & self ) -> Mid ;
13971421 fn msid ( & self ) -> Option < & Msid > ;
@@ -1428,7 +1452,9 @@ impl AsSdpMediaLine for (Mid, usize) {
14281452 ) -> MediaLine {
14291453 attrs. push ( MediaAttribute :: Mid ( self . 0 ) ) ;
14301454 attrs. push ( MediaAttribute :: SctpPort ( 5000 ) ) ;
1431- attrs. push ( MediaAttribute :: MaxMessageSize ( 262144 ) ) ;
1455+ attrs. push ( MediaAttribute :: MaxMessageSize (
1456+ crate :: sctp:: LOCAL_MAX_MESSAGE_SIZE as usize ,
1457+ ) ) ;
14321458
14331459 MediaLine {
14341460 typ : sdp:: MediaType :: Application ,
@@ -2477,4 +2503,197 @@ mod test {
24772503 // No space at the end
24782504 assert_eq ! ( count_lines( & line_string, "a=rid:no_attrs send" ) , 1 ) ;
24792505 }
2506+
2507+ #[ test]
2508+ fn test_local_max_message_size_advertised ( ) {
2509+ crate :: init_crypto_default ( ) ;
2510+
2511+ let now = Instant :: now ( ) ;
2512+ let mut rtc = Rtc :: new ( now) ;
2513+
2514+ // Create an offer with a data channel
2515+ let mut change = rtc. sdp_api ( ) ;
2516+ change. add_channel ( "test-channel" . into ( ) ) ;
2517+ let ( offer, _) = change. apply ( ) . unwrap ( ) ;
2518+
2519+ // Find the application m-line in the offer
2520+ let app_line = offer
2521+ . media_lines
2522+ . iter ( )
2523+ . find ( |m| m. typ . is_channel ( ) )
2524+ . expect ( "should have application m-line" ) ;
2525+
2526+ // Verify that max-message-size attribute is present and matches LOCAL_MAX_MESSAGE_SIZE
2527+ let max_size = app_line
2528+ . max_message_size ( )
2529+ . expect ( "max-message-size attribute should be present" ) ;
2530+
2531+ assert_eq ! (
2532+ max_size,
2533+ crate :: sctp:: LOCAL_MAX_MESSAGE_SIZE as usize ,
2534+ "max-message-size should match LOCAL_MAX_MESSAGE_SIZE constant"
2535+ ) ;
2536+
2537+ // Also verify it's in the SDP string output
2538+ let sdp_string = offer. to_sdp_string ( ) ;
2539+ let expected_line = format ! ( "a=max-message-size:{}" , crate :: sctp:: LOCAL_MAX_MESSAGE_SIZE ) ;
2540+ assert ! (
2541+ sdp_string. contains( & expected_line) ,
2542+ "SDP should contain max-message-size attribute with LOCAL_MAX_MESSAGE_SIZE value"
2543+ ) ;
2544+ }
2545+
2546+ #[ test]
2547+ fn test_remote_max_message_size_parsing ( ) {
2548+ // Parse SDP with max-message-size attribute and verify value is extracted correctly
2549+ let sdp = "v=0\r \n \
2550+ o=- 0 0 IN IP4 172.17.0.1\r \n \
2551+ s=-\r \n \
2552+ c=IN IP4 172.17.0.1\r \n \
2553+ t=0 0\r \n \
2554+ a=group:BUNDLE 0\r \n \
2555+ 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\
2556+ :D3:9D:1F:94:6E:A5:23:46:94:23\r \n \
2557+ m=application 9999 UDP/DTLS/SCTP webrtc-datachannel\r \n \
2558+ a=mid:0\r \n \
2559+ a=ice-ufrag:test\r \n \
2560+ a=ice-pwd:testpassword1234\r \n \
2561+ a=setup:actpass\r \n \
2562+ a=sctp-port:5000\r \n \
2563+ a=max-message-size:131072\r \n \
2564+ ";
2565+
2566+ let offer = SdpOffer :: from_sdp_string ( sdp) . expect ( "should parse" ) ;
2567+
2568+ // Find the application m-line
2569+ let app_line = offer
2570+ . media_lines
2571+ . iter ( )
2572+ . find ( |m| m. typ . is_channel ( ) )
2573+ . expect ( "should have application m-line" ) ;
2574+
2575+ assert_eq ! (
2576+ app_line. max_message_size( ) ,
2577+ Some ( 131072 ) ,
2578+ "max-message-size should be parsed as 131072"
2579+ ) ;
2580+ }
2581+
2582+ #[ test]
2583+ fn test_remote_max_message_size_applied ( ) {
2584+ crate :: init_crypto_default ( ) ;
2585+
2586+ let now = Instant :: now ( ) ;
2587+ let mut rtc1 = Rtc :: new ( now) ;
2588+ let mut rtc2 = Rtc :: new ( now) ;
2589+
2590+ // Create an offer from rtc1 with a channel
2591+ let mut change1 = rtc1. sdp_api ( ) ;
2592+ change1. add_channel ( "test-channel" . into ( ) ) ;
2593+ let ( offer1, pending1) = change1. apply ( ) . unwrap ( ) ;
2594+
2595+ // Get the offer SDP string and modify it to have a custom max-message-size
2596+ let custom_max_size1 = 98304u32 ;
2597+ let sdp_string = offer1. to_sdp_string ( ) ;
2598+ let modified_sdp = sdp_string. replace (
2599+ & format ! ( "a=max-message-size:{}" , crate :: sctp:: LOCAL_MAX_MESSAGE_SIZE ) ,
2600+ & format ! ( "a=max-message-size:{}" , custom_max_size1) ,
2601+ ) ;
2602+
2603+ let modified_offer =
2604+ SdpOffer :: from_sdp_string ( & modified_sdp) . expect ( "modified SDP should parse" ) ;
2605+
2606+ let answer = rtc2. sdp_api ( ) . accept_offer ( modified_offer) . unwrap ( ) ;
2607+
2608+ // Verify that rtc2's SCTP send limit is set to the custom value from rtc1's offer
2609+ assert_eq ! (
2610+ rtc2. sctp. remote_max_message_size( ) ,
2611+ custom_max_size1,
2612+ "rtc2 should have remote max message size set to rtc1's advertised value"
2613+ ) ;
2614+
2615+ // Now verify the reverse: rtc1 accepts rtc2's answer and applies its max-message-size
2616+ let custom_max_size2 = 131072u32 ;
2617+ let sdp_string = answer. to_sdp_string ( ) ;
2618+ let modified_sdp = sdp_string. replace (
2619+ & format ! ( "a=max-message-size:{}" , crate :: sctp:: LOCAL_MAX_MESSAGE_SIZE ) ,
2620+ & format ! ( "a=max-message-size:{}" , custom_max_size2) ,
2621+ ) ;
2622+
2623+ let modified_answer =
2624+ SdpAnswer :: from_sdp_string ( & modified_sdp) . expect ( "modified SDP should parse" ) ;
2625+
2626+ rtc1. sdp_api ( )
2627+ . accept_answer ( pending1, modified_answer)
2628+ . unwrap ( ) ;
2629+
2630+ assert_eq ! (
2631+ rtc1. sctp. remote_max_message_size( ) ,
2632+ custom_max_size2,
2633+ "rtc1 should have remote max message size set to rtc2's advertised value"
2634+ ) ;
2635+ }
2636+
2637+ #[ test]
2638+ fn test_remote_max_message_size_zero_means_unbounded ( ) {
2639+ // RFC 8841 §6.1: a=max-message-size:0 means the peer can receive a message
2640+ // of any size, so we represent it as u32::MAX rather than passing 0 through
2641+ // (which would reject every non-empty outbound message in sctp-proto).
2642+ crate :: init_crypto_default ( ) ;
2643+
2644+ let now = Instant :: now ( ) ;
2645+ let mut rtc1 = Rtc :: new ( now) ;
2646+ let mut rtc2 = Rtc :: new ( now) ;
2647+
2648+ let mut change1 = rtc1. sdp_api ( ) ;
2649+ change1. add_channel ( "test-channel" . into ( ) ) ;
2650+ let ( offer1, _pending1) = change1. apply ( ) . unwrap ( ) ;
2651+
2652+ let sdp_string = offer1. to_sdp_string ( ) ;
2653+ let modified_sdp = sdp_string. replace (
2654+ & format ! ( "a=max-message-size:{}" , crate :: sctp:: LOCAL_MAX_MESSAGE_SIZE ) ,
2655+ "a=max-message-size:0" ,
2656+ ) ;
2657+
2658+ let modified_offer =
2659+ SdpOffer :: from_sdp_string ( & modified_sdp) . expect ( "modified SDP should parse" ) ;
2660+
2661+ rtc2. sdp_api ( ) . accept_offer ( modified_offer) . unwrap ( ) ;
2662+
2663+ assert_eq ! (
2664+ rtc2. sctp. remote_max_message_size( ) ,
2665+ u32 :: MAX ,
2666+ "a=max-message-size:0 should be treated as unbounded (u32::MAX)"
2667+ ) ;
2668+ }
2669+
2670+ #[ test]
2671+ fn test_remote_max_message_size_too_large_clamps_to_unbounded ( ) {
2672+ crate :: init_crypto_default ( ) ;
2673+
2674+ let now = Instant :: now ( ) ;
2675+ let mut rtc1 = Rtc :: new ( now) ;
2676+ let mut rtc2 = Rtc :: new ( now) ;
2677+
2678+ let mut change1 = rtc1. sdp_api ( ) ;
2679+ change1. add_channel ( "test-channel" . into ( ) ) ;
2680+ let ( offer1, _pending1) = change1. apply ( ) . unwrap ( ) ;
2681+
2682+ let sdp_string = offer1. to_sdp_string ( ) ;
2683+ let modified_sdp = sdp_string. replace (
2684+ & format ! ( "a=max-message-size:{}" , crate :: sctp:: LOCAL_MAX_MESSAGE_SIZE ) ,
2685+ "a=max-message-size:4294967296" ,
2686+ ) ;
2687+
2688+ let modified_offer =
2689+ SdpOffer :: from_sdp_string ( & modified_sdp) . expect ( "modified SDP should parse" ) ;
2690+
2691+ rtc2. sdp_api ( ) . accept_offer ( modified_offer) . unwrap ( ) ;
2692+
2693+ assert_eq ! (
2694+ rtc2. sctp. remote_max_message_size( ) ,
2695+ u32 :: MAX ,
2696+ "a=max-message-size larger than u32::MAX should be treated as unbounded"
2697+ ) ;
2698+ }
24802699}
0 commit comments