File
Blob: firmware/vendor/str0m/tests/remove-media-while-pacer-queued.rs
| 1 | use std::time::Duration; |
| 2 | |
| 3 | use str0m::format::Codec; |
| 4 | use str0m::media::MediaKind; |
| 5 | use str0m::rtp::{ExtensionValues, RtpWrite, Ssrc}; |
| 6 | use str0m::{Input, RtcError}; |
| 7 | |
| 8 | mod common; |
| 9 | use common::{connect_l_r, init_crypto_default, init_log}; |
| 10 | |
| 11 | /// This test triggers the panic in `Session::poll_packet` where the pacer |
| 12 | /// holds a reference to a MidRid whose media has been removed. |
| 13 | /// |
| 14 | /// Sequence: |
| 15 | /// 1. Set up a connected pair with a media + stream via direct API |
| 16 | /// 2. Write an RTP packet so the stream's send queue is non-empty |
| 17 | /// 3. Call handle_input(Timeout) so session.handle_timeout populates the pacer |
| 18 | /// 4. Remove the media via direct_api().remove_media() — pacer still references the mid |
| 19 | /// 5. Call poll_output() — poll_packet asks pacer for next mid, finds it missing → panic |
| 20 | #[test] |
| 21 | pub fn remove_media_while_pacer_queued() -> Result<(), RtcError> { |
| 22 | init_log(); |
| 23 | init_crypto_default(); |
| 24 | |
| 25 | let (mut l, mut r) = connect_l_r(); |
| 26 | |
| 27 | let mid = "aud".into(); |
| 28 | let ssrc_tx: Ssrc = 42.into(); |
| 29 | |
| 30 | // Declare media and stream on sender |
| 31 | l.direct_api().declare_media(mid, MediaKind::Audio); |
| 32 | l.direct_api().declare_stream_tx(ssrc_tx, None, mid, None); |
| 33 | |
| 34 | // Declare matching media on receiver |
| 35 | r.direct_api().declare_media(mid, MediaKind::Audio); |
| 36 | |
| 37 | // Sync time |
| 38 | let max = l.last.max(r.last); |
| 39 | l.last = max; |
| 40 | r.last = max; |
| 41 | |
| 42 | let params = l.params_opus(); |
| 43 | assert_eq!(params.spec().codec, Codec::Opus); |
| 44 | let pt = params.pt(); |
| 45 | |
| 46 | // Write an RTP packet so the stream's send queue has data |
| 47 | let wallclock = l.start + l.duration(); |
| 48 | { |
| 49 | let mut direct = l.direct_api(); |
| 50 | let stream = direct.stream_tx(&ssrc_tx).unwrap(); |
| 51 | |
| 52 | let exts = ExtensionValues { |
| 53 | audio_level: Some(-42), |
| 54 | voice_activity: Some(false), |
| 55 | ..Default::default() |
| 56 | }; |
| 57 | |
| 58 | stream.write_rtp( |
| 59 | RtpWrite::new( |
| 60 | pt, |
| 61 | 47_000.into(), |
| 62 | 47_000_000, |
| 63 | wallclock, |
| 64 | [0x1, 0x2, 0x3, 0x4], |
| 65 | ) |
| 66 | .ext_vals(exts) |
| 67 | .nackable(true), |
| 68 | ); |
| 69 | } |
| 70 | |
| 71 | // Advance time slightly so the timeout fires |
| 72 | let now = l.last + Duration::from_millis(10); |
| 73 | l.last = now; |
| 74 | |
| 75 | // Trigger handle_timeout which calls update_queue_state → pacer captures the MidRid |
| 76 | l.handle_input(Input::Timeout(now))?; |
| 77 | |
| 78 | // Now remove the media — the pacer still holds a reference to mid "aud" |
| 79 | l.direct_api().remove_media(mid); |
| 80 | |
| 81 | // poll_output will call poll_packet → pacer.poll_queue() returns the stale MidRid → panic |
| 82 | // (The panic: "Pacer pointed to mid {} which doesn't exist") |
| 83 | let _output = l.poll_output()?; |
| 84 | |
| 85 | Ok(()) |
| 86 | } |