str0m 0.23.0

WebRTC library in Sans-IO style
Documentation
//! Exported fuzz targets to get them part of the compilation with feature `_internal_test_exports`.

use std::collections::VecDeque;
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use std::time::{Duration, Instant};

use crate::change::{SdpAnswer, SdpOffer};
use crate::format::Codec;
use crate::net::{Protocol, Receive};
use crate::packet::{CodecDepacketizer, CodecExtra, Depacketizer, DepacketizingBuffer, RtpMeta};
use crate::rtp_::{Frequency, MediaTime, Rtcp, RtcpFb, RtpHeader};
use crate::streams::register::ReceiverRegister;
use crate::streams::rtx_cache_buf::EvictingBuffer;
use crate::{Input, Output, Rtc};

use super::Rng;
#[allow(unused)]
use super::setup::{random_config, random_extmap};

pub fn rtx_buffer(data: &[u8]) {
    if data.len() < 4 {
        return;
    }

    let buf_size = u16::from_be_bytes([data[0], data[1]]);
    let max_age = data[2] as u64;
    let max_size = data[3] as usize;
    let mut buf = EvictingBuffer::new(buf_size as usize, Duration::from_secs(max_age), max_size);
    let mut now = Instant::now();
    let mut pos = 0;

    for d in &data[4..] {
        now += Duration::from_millis(*d as u64);
        if d % 2 == 0 {
            buf.maybe_evict(now)
        } else {
            pos += *d as u64;
            buf.push(pos, now, d);
        }
    }
}

pub fn rtp_header(data: &[u8]) -> Option<()> {
    let mut rng = Rng::new(data);
    let exts = random_extmap(&mut rng, 10)?;
    let len = rng.usize(76)?;
    RtpHeader::_parse(rng.slice(len)?, &exts);
    Some(())
}

pub fn rtp_packet(data: &[u8]) -> Option<()> {
    let mut rng = Rng::new(data);
    let exts = random_extmap(&mut rng, 10)?;
    // Maximum RTP packet size is typically around 1500 bytes (MTU)
    let packet_len = rng.usize(1500)?;
    let packet = rng.slice(packet_len)?;

    // Parse the header from the packet
    let header = RtpHeader::_parse(packet, &exts)?;

    // Extract the payload (data after the header)
    let _payload = packet.get(header.header_len..)?;

    Some(())
}

pub fn sdp_offer(data: &[u8]) -> Option<()> {
    let str = std::str::from_utf8(data).ok()?;
    let _ = SdpOffer::from_sdp_string(str);
    Some(())
}

pub fn sdp_answer(data: &[u8]) -> Option<()> {
    let str = std::str::from_utf8(data).ok()?;
    let _ = SdpAnswer::from_sdp_string(str);
    Some(())
}

pub fn depack(data: &[u8]) -> Option<()> {
    let mut rng = Rng::new(data);

    let codec = match rng.u8(10)? {
        0 => Codec::Opus,
        1 => Codec::Vp8,
        2 => Codec::Vp9,
        3 => Codec::H264,
        4 => Codec::H265,
        5 => Codec::H266,
        6 => Codec::Av1,
        7 => Codec::PCMU,
        8 => Codec::PCMA,
        9 => Codec::G722,
        10 => Codec::CN,
        _ => unreachable!(),
    };

    let mut depack = DepacketizingBuffer::new(codec.into(), rng.usize(300)?);

    let exts = random_extmap(&mut rng, 10)?;

    let start = Instant::now();

    loop {
        let do_push = rng.bool()?;

        if do_push {
            let hlen = rng.usize(76)?;
            let header = RtpHeader::_parse(rng.slice(hlen)?, &exts)?;
            let meta = RtpMeta {
                received: start + Duration::from_millis(rng.u64(10000)?),
                time: MediaTime::new(rng.u64(u64::MAX)?, Frequency::MICROS),
                seq_no: rng.u64(u64::MAX)?.into(),
                header,
                last_sender_info: None,
            };
            let len = rng.usize(1200)?;
            let data = rng.slice(len)?.to_vec();
            depack.push(meta, data);
        } else {
            depack.pop();
        }
    }
}

pub fn receive_register(data: &[u8]) -> Option<()> {
    let mut rng = Rng::new(data);
    let mut rr = ReceiverRegister::new(None);
    let start = Instant::now();
    loop {
        match rng.u8(2)? {
            0 => {
                let seq = rng.u64(u64::MAX / 2)?;
                let arrival = start + Duration::from_micros(rng.u64(u64::MAX / 100)?);
                let rtp_time = rng.u32(u32::MAX / 2)?;
                let clock_rate = rng.u32(u32::MAX / 2)?;
                rr.update(seq.into(), arrival, rtp_time, clock_rate);
            }
            1 => {
                rr.nack_report();
            }
            2 => {
                rr.reception_report();
            }
            _ => unreachable!(),
        }
    }
}

/// Fuzz the full `Rtc` receive path: instance init, datagram demux
/// (STUN/DTLS/RTP/RTCP), dispatch, and `poll_output` draining.
pub fn receive(data: &[u8]) -> Option<()> {
    let mut rng = Rng::new(data);

    let base = Instant::now();
    let mut rtc = Rtc::new(base);

    let source = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1)), 5000);
    let destination = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(2, 2, 2, 2)), 5001);

    let mut millis: u64 = 0;

    loop {
        // Drive time monotonically forward.
        millis += rng.u64(50)?;
        let now = base + Duration::from_millis(millis);

        if rng.bool()? {
            // Feed a datagram through the full demux + dispatch path.
            let len = rng.usize(2000)?;
            let buf = rng.slice(len)?;
            if let Ok(receive) = Receive::new(Protocol::Udp, source, destination, buf) {
                let _ = rtc.handle_input(Input::Receive(now, receive));
            }
        } else {
            let _ = rtc.handle_input(Input::Timeout(now));
        }

        // Drain outputs (bounded to avoid spinning on a single input).
        for _ in 0..1000 {
            match rtc.poll_output() {
                Ok(Output::Timeout(_)) => break,
                Ok(_) => continue,
                Err(_) => break,
            }
        }

        if !rtc.is_alive() {
            return Some(());
        }
    }
}

/// Fuzz STUN message parsing (the ICE input path).
pub fn stun(data: &[u8]) -> Option<()> {
    let _ = is::stun::StunMessage::parse(data);
    Some(())
}

/// Fuzz RTCP compound-packet parsing and the feedback conversion.
pub fn rtcp(data: &[u8]) -> Option<()> {
    let mut feedback = VecDeque::new();
    Rtcp::read_packet(data, &mut feedback);
    // Exercise the feedback conversion as well.
    let _ = RtcpFb::from_rtcp(feedback.drain(..)).count();
    Some(())
}

/// Fuzz each codec depacketizer directly (bypassing the buffer's gating),
/// exercising the raw `Depacketizer::depacketize` parsers.
pub fn depack_direct(data: &[u8]) -> Option<()> {
    let mut rng = Rng::new(data);

    let codec = match rng.u8(10)? {
        0 => Codec::Opus,
        1 => Codec::Vp8,
        2 => Codec::Vp9,
        3 => Codec::H264,
        4 => Codec::H265,
        5 => Codec::H266,
        6 => Codec::Av1,
        7 => Codec::PCMU,
        8 => Codec::PCMA,
        9 => Codec::G722,
        10 => Codec::CN,
        _ => unreachable!(),
    };

    let mut depack: CodecDepacketizer = codec.into();
    let mut out = Vec::new();
    let mut extra = CodecExtra::None;

    loop {
        let marker = rng.bool()?;
        let len = rng.usize(1500)?;
        let packet = rng.slice(len)?;

        let _ = depack.depacketize(packet, &mut out, &mut extra);
        let _ = depack.is_partition_head(packet);
        let _ = depack.is_partition_tail(marker, packet);

        // Keep memory bounded across iterations.
        if out.len() > 4_000_000 {
            out.clear();
        }
    }
}