@@ -4041,22 +4041,6 @@ impl SipSession {
40414041 bridge_builder = bridge_builder. with_rtp_port_range ( start, end) ;
40424042 }
40434043
4044- if !caller_is_webrtc {
4045- let app_caller_mode = Self :: sdp_transport_mode ( caller_offer) ;
4046- let caller_config = rustrtc:: RtcConfiguration {
4047- transport_mode : app_caller_mode,
4048- rtp_start_port : self . context . dialplan . media . rtp_start_port ,
4049- rtp_end_port : self . context . dialplan . media . rtp_end_port ,
4050- enable_latching : self . context . dialplan . media . enable_latching ,
4051- probation_max_packets : self . context . dialplan . media . probation_max_packets ,
4052- external_ip : self . context . dialplan . media . external_ip . clone ( ) ,
4053- bind_ip : self . context . dialplan . media . bind_ip . clone ( ) ,
4054- cname : Some ( self . server . rtc_cname . clone ( ) ) ,
4055- ..Default :: default ( )
4056- } ;
4057- bridge_builder = bridge_builder. with_caller_config ( caller_config) ;
4058- }
4059-
40604044 if !caller_is_webrtc && caller_offer. contains ( "a=group:BUNDLE" ) {
40614045 bridge_builder = bridge_builder
40624046 . with_rtp_sdp_compatibility ( rustrtc:: config:: SdpCompatibilityMode :: Standard )
@@ -11516,6 +11500,114 @@ mod tests {
1151611500 }
1151711501 }
1151811502
11503+ #[ tokio:: test]
11504+ async fn test_ivr_to_webrtc_transfer_callee_sdp_has_dtls_fingerprint ( ) {
11505+ use crate :: call:: { DialDirection , Dialplan , TransactionCookie } ;
11506+ use crate :: proxy:: proxy_call:: test_util:: tests:: MockMediaPeer ;
11507+ use crate :: proxy:: tests:: common:: {
11508+ create_test_request, create_test_server, create_transaction,
11509+ } ;
11510+
11511+ let ( server, _) = create_test_server ( ) . await ;
11512+ let request = create_test_request (
11513+ rsipstack:: sip:: Method :: Invite ,
11514+ "alice" ,
11515+ None ,
11516+ "rustpbx.com" ,
11517+ None ,
11518+ ) ;
11519+ let original_request = request. clone ( ) ;
11520+ let ( tx, _) = create_transaction ( request) . await ;
11521+ let ( state_tx, _state_rx) = mpsc:: unbounded_channel ( ) ;
11522+ let server_dialog = server
11523+ . dialog_layer
11524+ . get_or_create_server_invite ( & tx, state_tx, None , None )
11525+ . expect ( "failed to create server dialog" ) ;
11526+
11527+ let context = CallContext {
11528+ session_id : "test-ivr-to-webrtc" . to_string ( ) ,
11529+ dialplan : Arc :: new ( Dialplan :: new (
11530+ "test-ivr-to-webrtc" . to_string ( ) ,
11531+ original_request,
11532+ DialDirection :: Inbound ,
11533+ ) ) ,
11534+ cookie : TransactionCookie :: default ( ) ,
11535+ start_time : Instant :: now ( ) ,
11536+ original_caller : "sip:alice@rustpbx.com" . to_string ( ) ,
11537+ original_callee : "sip:ivr@rustpbx.com" . to_string ( ) ,
11538+ max_forwards : 70 ,
11539+ created_at : chrono:: Utc :: now ( ) . to_rfc3339 ( ) ,
11540+ metadata : None ,
11541+ } ;
11542+
11543+ let caller_peer = Arc :: new ( MockMediaPeer :: new ( ) ) ;
11544+ let callee_peer = Arc :: new ( MockMediaPeer :: new ( ) ) ;
11545+ let ( mut session, _handle, _cmd_rx) = SipSession :: new (
11546+ server. clone ( ) ,
11547+ CancellationToken :: new ( ) ,
11548+ None ,
11549+ context,
11550+ server_dialog,
11551+ true , // use_media_proxy = true → Anchored
11552+ caller_peer,
11553+ callee_peer,
11554+ ) ;
11555+
11556+ // RTP caller SDP (no ICE/DTLS)
11557+ session. media . caller_offer = Some (
11558+ concat ! (
11559+ "v=0\r \n " ,
11560+ "o=alice 1 1 IN IP4 192.0.2.10\r \n " ,
11561+ "s=Talk\r \n " ,
11562+ "c=IN IP4 192.0.2.10\r \n " ,
11563+ "t=0 0\r \n " ,
11564+ "m=audio 40000 RTP/AVP 0 8 101\r \n " ,
11565+ "a=rtpmap:0 PCMU/8000\r \n " ,
11566+ "a=rtpmap:8 PCMA/8000\r \n " ,
11567+ "a=rtpmap:101 telephone-event/8000\r \n " ,
11568+ "a=sendrecv\r \n " ,
11569+ )
11570+ . to_string ( ) ,
11571+ ) ;
11572+
11573+ // Step 1: IVR - create app caller media bridge (simulates early media / queue)
11574+ let answer = session
11575+ . prepare_app_caller_media_bridge ( )
11576+ . await
11577+ . expect ( "app caller bridge answer should be prepared" ) ;
11578+ assert ! ( answer. contains( "RTP/AVP" ) ) ;
11579+ assert ! ( session. media. media_bridge. is_some( ) ) ;
11580+ assert ! ( session. media. caller_answer_uses_media_bridge) ;
11581+ assert ! ( !session. media. callee_offer_uses_media_bridge) ;
11582+
11583+ // Step 2: Transfer to WebRTC agent - create callee track reusing the bridge
11584+ let sdp = session
11585+ . create_callee_track ( true )
11586+ . await
11587+ . expect ( "create_callee_track should succeed for IVR->WebRTC transfer" ) ;
11588+
11589+ // The SDP sent to the WebRTC agent MUST contain DTLS fingerprint and ICE
11590+ assert ! (
11591+ sdp. contains( "a=fingerprint" ) ,
11592+ "IVR->WebRTC callee SDP must contain DTLS fingerprint, got: {}" ,
11593+ sdp
11594+ ) ;
11595+ assert ! (
11596+ sdp. contains( "a=ice-ufrag" ) ,
11597+ "IVR->WebRTC callee SDP must contain ICE ufrag, got: {}" ,
11598+ sdp
11599+ ) ;
11600+ assert ! (
11601+ sdp. contains( "UDP/TLS/RTP/SAVPF" ) ,
11602+ "IVR->WebRTC callee SDP must use UDP/TLS/RTP/SAVPF profile, got: {}" ,
11603+ sdp
11604+ ) ;
11605+
11606+ if let Some ( bridge) = session. media . media_bridge . take ( ) {
11607+ bridge. stop ( ) . await ;
11608+ }
11609+ }
11610+
1151911611 #[ tokio:: test]
1152011612 async fn test_sip_session_handle ( ) {
1152111613 use crate :: call:: runtime:: SessionId ;
0 commit comments