//! Temporary probe: replicate handle_whip_injest's Rtc setup, feed it a //! realistic OBS/libdatachannel WHIP offer, print the answer SDP and check //! whether the string-fixups in webrtc_ingest.rs actually match. use std::{net::SocketAddr, time::Instant}; use str0m::{change::SdpOffer, net::Protocol, Candidate, Rtc}; fn obs_like_offer() -> String { let mut s = String::new(); s.push_str("v=0\r\n"); s.push_str("o=- 4527835755568137757 2 IN IP4 127.0.0.1\r\n"); s.push_str("s=-\r\n"); s.push_str("t=0 0\r\n"); s.push_str("a=group:BUNDLE 0 1\r\n"); s.push_str("a=msid-semantic: WMS *\r\n"); s.push_str("m=audio 9 UDP/TLS/RTP/SAVPF 111\r\n"); s.push_str("c=IN IP4 0.0.0.0\r\n"); s.push_str("a=rtcp:9 IN IP4 0.0.0.0\r\n"); s.push_str("a=ice-ufrag:obs_ufrag_audio\r\n"); s.push_str("a=ice-pwd:obs_pwd_audio\r\n"); s.push_str("a=ice-options:trickle\r\n"); s.push_str("a=fingerprint:sha-256 5A:6B:7C:8D:9E:AF:B0:C1:D2:E3:F4:05:16:27:38:49:5A:6B:7C:8D:9E:AF:B0:C1:D2:E3:F4:05:16:27:38:49:5A\r\n"); s.push_str("a=setup:actpass\r\n"); s.push_str("a=mid:0\r\n"); s.push_str("a=sendrecv\r\n"); s.push_str("a=rtcp-mux\r\n"); s.push_str("a=rtpmap:111 opus/48000/2\r\n"); s.push_str("a=rtcp-fb:111 transport-cc\r\n"); s.push_str("a=fmtp:111 minptime=10;useinbandfec=1\r\n"); s.push_str("m=video 9 UDP/TLS/RTP/SAVPF 96\r\n"); s.push_str("c=IN IP4 0.0.0.0\r\n"); s.push_str("a=rtcp:9 IN IP4 0.0.0.0\r\n"); s.push_str("a=ice-ufrag:obs_ufrag_video\r\n"); s.push_str("a=ice-pwd:obs_pwd_video\r\n"); s.push_str("a=ice-options:trickle\r\n"); s.push_str("a=fingerprint:sha-256 5A:6B:7C:8D:9E:AF:B0:C1:D2:E3:F4:05:16:27:38:49:5A:6B:7C:8D:9E:AF:B0:C1:D2:E3:F4:05:16:27:38:49:5A\r\n"); s.push_str("a=setup:actpass\r\n"); s.push_str("a=mid:1\r\n"); s.push_str("a=sendrecv\r\n"); s.push_str("a=rtcp-mux\r\n"); s.push_str("a=rtpmap:96 H264/90000\r\n"); s.push_str("a=rtcp-fb:96 nack\r\n"); s.push_str("a=rtcp-fb:96 nack pli\r\n"); s.push_str("a=rtcp-fb:96 transport-cc\r\n"); s.push_str( "a=fmtp:96 level-asymmetry-allowed=1;packetization-mode=1;profile-level-id=42e01f\r\n", ); s } #[test] fn probe_whip_answer() { let offer_sdp = obs_like_offer(); let sdp_offer = SdpOffer::from_sdp_string(&offer_sdp).expect("parse offer"); // Mirror handle_whip_injest's builder setup. let mut builder = Rtc::builder(); { let cc = builder.codec_config(); cc.clear(); cc.enable_opus(true); cc.enable_h264(true); } let mut rtc = builder.build(Instant::now()); let public_addr: SocketAddr = "203.0.113.7:6969".parse().unwrap(); let candidate = Candidate::host(public_addr, Protocol::Udp).unwrap(); rtc.add_local_candidate(candidate); let answer = rtc.sdp_api().accept_offer(sdp_offer).expect("accept offer"); let answer_sdp = answer.to_sdp_string(); println!("=== RAW ANSWER ==="); println!("{answer_sdp}"); println!("=== END RAW ANSWER ==="); // Now replicate the fixups from webrtc_ingest.rs verbatim. let answer_sdp = answer_sdp .replace("a=ice-options:trickle\r\n", "") .replace("a=ice-options:trickle\n", ""); let answer_sdp = answer_sdp .replace("a=group:BUNDLE 0 1\r\n", "") .replace("a=group:BUNDLE 0 1\n", ""); let answer_sdp = if let Some(cand_line) = answer_sdp.lines().find(|l| l.starts_with("a=candidate:")) { let cand_replacement = format!( "m=video 9 UDP/TLS/RTP/SAVPF 96\r\nc=IN IP4 127.0.0.1\r\n{}\r\n", cand_line ); answer_sdp.replace( "m=video 9 UDP/TLS/RTP/SAVPF 96\r\nc=IN IP4 0.0.0.0\r\n", &cand_replacement, ) } else { answer_sdp }; println!("=== FIXED ANSWER ==="); println!("{answer_sdp}"); println!("=== END FIXED ANSWER ==="); // Diagnostics let video_mline = answer_sdp .lines() .find(|l| l.starts_with("m=video")) .unwrap(); println!("video m-line after fixup: {video_mline}"); println!( "has a=candidate after fixup: {}", answer_sdp.lines().any(|l| l.starts_with("a=candidate:")) ); println!( "a=candidate lines: {:?}", answer_sdp .lines() .filter(|l| l.starts_with("a=candidate:")) .collect::>() ); }