str0m 0.18.0

WebRTC library in Sans-IO style
Documentation
//! BWE estimate behavior tests (capping, recovery, convergence).

use std::time::Duration;

use netem::{DataSize, NetemConfig};
use str0m::RtcError;
use str0m::bwe::Bitrate;

use crate::common::{BweTestContext, Step, connect_with_bwe, init_crypto_default, init_log};

#[test]
fn estimate_capped_by_max_bitrate() -> Result<(), RtcError> {
    init_log();
    init_crypto_default();

    let plan = vec![
        Step::Conditions {
            description: "High capacity network (10 Mbps)",
            config: NetemConfig::new()
                .latency(Duration::from_millis(10))
                .link(Bitrate::mbps(10), DataSize::kbytes(200))
                .seed(42),
        },
        Step::Media {
            description: "Send at 2 Mbps with 3 Mbps max",
            desired_bitrate: Bitrate::mbps(3), // max_bitrate
            media_send_rate: Bitrate::mbps(2),
        },
        Step::Run {
            description: "Let probes discover high capacity",
            duration: Duration::from_secs(10),
        },
        // Estimate should not exceed 3 Mbps despite 10 Mbps capacity
        Step::Check {
            description: "Estimate capped at max_bitrate",
            at_least: Bitrate::mbps(2),
        },
    ];

    let (mut l, mut r) = connect_with_bwe(Bitrate::mbps(2), Bitrate::mbps(3));
    let mut ctx = BweTestContext::new(&mut l, &mut r);
    ctx.run_plan(&mut l, &mut r, &plan)?;

    Ok(())
}

#[test]
fn estimate_recovers_after_capacity_restoration() -> Result<(), RtcError> {
    init_log();
    init_crypto_default();

    let plan = vec![
        Step::Conditions {
            description: "Good network initially",
            config: NetemConfig::new()
                .latency(Duration::from_millis(20))
                .link(Bitrate::mbps(5), DataSize::kbytes(100))
                .seed(42),
        },
        Step::Media {
            description: "Send at 2 Mbps",
            desired_bitrate: Bitrate::mbps(5),
            media_send_rate: Bitrate::mbps(2),
        },
        Step::Run {
            description: "Establish baseline",
            duration: Duration::from_secs(5),
        },
        Step::Check {
            description: "Good estimate",
            at_least: Bitrate::mbps(2),
        },
        // Degrade network temporarily
        Step::Conditions {
            description: "Degrade to 1 Mbps",
            config: NetemConfig::new()
                .latency(Duration::from_millis(40))
                .link(Bitrate::mbps(1), DataSize::kbytes(50))
                .seed(42),
        },
        Step::Run {
            description: "Estimate drops to lower capacity",
            duration: Duration::from_secs(2),
        },
        Step::Check {
            description: "Estimate reduced to ~1 Mbps",
            at_least: Bitrate::kbps(400),
        },
        // Reduce send rate to avoid overwhelming the low estimate
        Step::Media {
            description: "Reduce send rate to allow AIMD recovery",
            desired_bitrate: Bitrate::mbps(5),
            media_send_rate: Bitrate::kbps(500), // Send at ~estimate level
        },
        // Restore network capacity
        Step::Conditions {
            description: "Restore to 5 Mbps",
            config: NetemConfig::new()
                .latency(Duration::from_millis(20))
                .link(Bitrate::mbps(5), DataSize::kbytes(100))
                .seed(42),
        },
        // Initial recovery should be via AIMD (no probes immediately after restoration)
        Step::Run {
            description: "Initial AIMD recovery period",
            duration: Duration::from_secs(3),
        },
        Step::AssertNoProbes {
            description: "No probes during initial AIMD recovery",
        },
        Step::Check {
            description: "AIMD recovery is very slow after severe degradation",
            at_least: Bitrate::kbps(350),
        },
        // Further recovery via AIMD and eventually exponential probes
        Step::Run {
            description: "Continue recovery (slow due to low threshold)",
            duration: Duration::from_secs(25),
        },
        Step::Check {
            description: "Recovery remains slow (threshold adapted down)",
            at_least: Bitrate::mbps(1),
        },
    ];

    let (mut l, mut r) = connect_with_bwe(Bitrate::mbps(2), Bitrate::mbps(5));
    let mut ctx = BweTestContext::new(&mut l, &mut r);
    ctx.run_plan(&mut l, &mut r, &plan)?;

    Ok(())
}

#[test]
fn estimate_converges_to_actual_capacity() -> Result<(), RtcError> {
    init_log();
    init_crypto_default();

    let plan = vec![
        Step::Conditions {
            description: "Network with 2 Mbps capacity",
            config: NetemConfig::new()
                .latency(Duration::from_millis(20))
                .link(Bitrate::mbps(2), DataSize::kbytes(100))
                .seed(42),
        },
        Step::Media {
            description: "Send at 1.5 Mbps",
            desired_bitrate: Bitrate::mbps(5),
            media_send_rate: Bitrate::mbps(1),
        },
        Step::Run {
            description: "Allow convergence over 15 seconds",
            duration: Duration::from_secs(15),
        },
        // Estimate should converge close to 2 Mbps capacity
        Step::Check {
            description: "Estimate converges near capacity",
            at_least: Bitrate::mbps(1),
        },
    ];

    let (mut l, mut r) = connect_with_bwe(Bitrate::mbps(1), Bitrate::mbps(5));
    let mut ctx = BweTestContext::new(&mut l, &mut r);
    ctx.run_plan(&mut l, &mut r, &plan)?;

    Ok(())
}