use core::time::Duration;
use srt_runtime::tsbpd::{TickOutcome, TsbpdScheduler};
const TIME_BASE_US: u64 = 42_000_000;
const DELAY_MS: u64 = 120;
const DELAY_US: u64 = DELAY_MS * 1000;
fn drop_threshold_us() -> u64 {
(DELAY_MS * 5).div_ceil(4) * 1000
}
const ISN: u32 = 0;
struct PacketInput {
seq: u32,
timestamp: u32,
}
fn tick_to(sched: &mut TsbpdScheduler, now_us: u64) -> TickOutcome {
sched.tick(Duration::from_micros(now_us))
}
fn feed(sched: &mut TsbpdScheduler, seq: u32, ts: u32, now_us: u64) -> TickOutcome {
sched.feed_data(seq, ts, Duration::from_micros(now_us))
}
#[test]
fn ordered_arrivals_withhold_then_release() {
let mut sched = TsbpdScheduler::new(ISN, TIME_BASE_US, DELAY_MS, 0, false, None);
let pkts = [
PacketInput {
seq: 0,
timestamp: 0,
},
PacketInput {
seq: 1,
timestamp: 10_000,
},
PacketInput {
seq: 2,
timestamp: 20_000,
},
];
for p in &pkts {
let out = feed(&mut sched, p.seq, p.timestamp, 0);
assert!(out.delivered.is_empty(), "no delivery before PktTsbpdTime");
assert!(out.dropped.is_empty());
}
assert_eq!(sched.buffered_count(), 3);
let tsbpd0 = TIME_BASE_US + DELAY_US;
let out = tick_to(&mut sched, tsbpd0);
assert_eq!(out.delivered, vec![0], "release seq 0 at its play time");
assert!(out.dropped.is_empty());
assert_eq!(sched.buffered_count(), 2);
let tsbpd1 = TIME_BASE_US + 10_000 + DELAY_US;
let out = tick_to(&mut sched, tsbpd1);
assert_eq!(out.delivered, vec![1], "release seq 1 at its play time");
assert_eq!(sched.buffered_count(), 1);
let tsbpd2 = TIME_BASE_US + 20_000 + DELAY_US;
let out = tick_to(&mut sched, tsbpd2);
assert_eq!(out.delivered, vec![2], "release seq 2 at its play time");
assert_eq!(sched.buffered_count(), 0);
}
#[test]
fn out_of_order_arrivals_released_in_sequence() {
let mut sched = TsbpdScheduler::new(ISN, TIME_BASE_US, DELAY_MS, 0, false, None);
feed(&mut sched, 1, 10_000, 0);
assert_eq!(sched.buffered_count(), 1);
assert!(sched.has_gap());
feed(&mut sched, 2, 20_000, 0);
assert_eq!(sched.buffered_count(), 2);
let tsbpd0 = TIME_BASE_US + DELAY_US;
let tsbpd2 = TIME_BASE_US + 20_000 + DELAY_US;
let now = tsbpd2.max(tsbpd0); let out = feed(&mut sched, 0, 0, now);
assert_eq!(
out.delivered,
vec![0, 1, 2],
"out-of-order arrivals must release in sequence 0, 1, 2"
);
assert!(out.dropped.is_empty());
assert_eq!(sched.buffered_count(), 0);
}
#[test]
fn packet_withheld_until_play_time() {
let mut sched = TsbpdScheduler::new(ISN, TIME_BASE_US, DELAY_MS, 0, false, None);
feed(&mut sched, 0, 0, 0);
let just_before = TIME_BASE_US + DELAY_US - 1;
let out = tick_to(&mut sched, just_before);
assert!(
out.delivered.is_empty(),
"packet must not be released before PktTsbpdTime"
);
let at_time = TIME_BASE_US + DELAY_US;
let out = tick_to(&mut sched, at_time);
assert_eq!(
out.delivered,
vec![0],
"packet released exactly at play time"
);
}
#[test]
fn too_late_packet_dropped_on_arrival() {
let mut sched = TsbpdScheduler::new(ISN, TIME_BASE_US, DELAY_MS, 0, true, None);
let pkt_tsbpd = TIME_BASE_US + DELAY_US;
let now = pkt_tsbpd + drop_threshold_us() + 1; let out = feed(&mut sched, 0, 0, now);
assert!(
out.delivered.is_empty(),
"too-late packet must not be delivered"
);
assert_eq!(
out.dropped,
vec![0],
"too-late packet must be listed as dropped"
);
assert_eq!(
sched.buffered_count(),
0,
"dropped packet must not be buffered"
);
assert_eq!(sched.next_release(), 1);
}
#[test]
fn drop_chain_after_gap_fill() {
let mut sched = TsbpdScheduler::new(ISN, TIME_BASE_US, DELAY_MS, 0, true, None);
feed(&mut sched, 1, 10_000, 0);
feed(&mut sched, 2, 20_000, 0);
let pkt0_tsbpd = TIME_BASE_US + DELAY_US;
let now = pkt0_tsbpd + drop_threshold_us() + 1;
let _out = feed(&mut sched, 0, 0, now);
assert_eq!(sched.buffered_count(), 0);
assert_eq!(sched.next_release(), 3);
assert_eq!(sched.buffered_count(), 0);
}
#[test]
fn timestamp_wrap_handling() {
let mut sched = TsbpdScheduler::new(ISN, TIME_BASE_US, DELAY_MS, 0, false, None);
let near_wrap: u32 = 0xFFFF_FF00u32;
let after_wrap: u32 = 500;
feed(&mut sched, 0, near_wrap, 0);
feed(&mut sched, 1, after_wrap, 0);
assert_eq!(sched.buffered_count(), 2);
let tsbpd0 = TIME_BASE_US + near_wrap as u64 + DELAY_US;
let tsbpd1 = TIME_BASE_US + after_wrap as u64 + DELAY_US;
assert!(
tsbpd0 > tsbpd1,
"pre-wrap packet has later play time in u64"
);
let out = tick_to(&mut sched, tsbpd0);
assert_eq!(
out.delivered,
vec![0, 1],
"wrapped timestamps must deliver in sequence order"
);
}
#[test]
fn disabled_drop_delivers_late_packets() {
let mut sched = TsbpdScheduler::new(ISN, TIME_BASE_US, DELAY_MS, 0, false, None);
let very_late = TIME_BASE_US + DELAY_US + drop_threshold_us() * 10;
let out = feed(&mut sched, 0, 0, very_late);
assert_eq!(
out.delivered,
vec![0],
"late packet must be delivered when drop is disabled"
);
assert!(out.dropped.is_empty());
}
#[test]
fn gap_blocks_delivery_until_filled() {
let mut sched = TsbpdScheduler::new(ISN, TIME_BASE_US, DELAY_MS, 0, false, None);
feed(&mut sched, 2, 20_000, 0);
feed(&mut sched, 1, 10_000, 0);
let far = TIME_BASE_US + 30_000 + DELAY_US + 1_000_000;
let out = tick_to(&mut sched, far);
assert!(out.delivered.is_empty(), "no delivery when gap present");
let out = feed(&mut sched, 0, 0, far);
assert_eq!(
out.delivered,
vec![0, 1, 2],
"filling gap unblocks all buffered packets"
);
assert!(out.dropped.is_empty());
}
#[test]
fn custom_drop_threshold() {
let custom_threshold = 50_000u64; let mut sched =
TsbpdScheduler::new(ISN, TIME_BASE_US, DELAY_MS, 0, true, Some(custom_threshold));
let pkt_tsbpd = TIME_BASE_US + DELAY_US;
let now = pkt_tsbpd + custom_threshold + 1;
let out = feed(&mut sched, 0, 0, now);
assert!(
out.delivered.is_empty(),
"must not deliver when past custom threshold"
);
assert_eq!(out.dropped, vec![0]);
}
#[test]
fn drift_affects_play_time() {
let drift_us = 5000u64;
let mut sched = TsbpdScheduler::new(ISN, TIME_BASE_US, DELAY_MS, drift_us, false, None);
let pkt_tsbpd = TIME_BASE_US + DELAY_US + drift_us;
let out = tick_to(&mut sched, pkt_tsbpd - 1);
assert!(out.delivered.is_empty(), "drift delays release");
feed(&mut sched, 0, 0, pkt_tsbpd);
assert_eq!(
sched.buffered_count(),
0,
"drift-adjusted delivery at play time"
);
}
#[test]
fn gradual_tick_releases_incrementally() {
let mut sched = TsbpdScheduler::new(ISN, TIME_BASE_US, DELAY_MS, 0, false, None);
for i in 0..10u32 {
feed(&mut sched, i, i * 5000, 0);
}
let mut all_delivered = Vec::new();
let start_tsbpd = TIME_BASE_US + DELAY_US;
for offset in 0..60 {
let out = tick_to(&mut sched, start_tsbpd + offset * 1000);
all_delivered.extend(out.delivered);
}
assert_eq!(all_delivered, (0..10).collect::<Vec<_>>());
}
#[test]
fn duplicate_arrival_is_noop() {
let mut sched = TsbpdScheduler::new(ISN, TIME_BASE_US, DELAY_MS, 0, false, None);
let play = TIME_BASE_US + DELAY_US;
feed(&mut sched, 0, 0, play);
assert_eq!(sched.buffered_count(), 0);
assert_eq!(sched.next_release(), 1);
let out = feed(&mut sched, 0, 0, play);
assert!(out.delivered.is_empty());
assert_eq!(sched.next_release(), 1);
}