File
Blob: firmware/vendor/str0m/src/_internal_test_exports/fuzz.rs
| 1 | //! Exported fuzz targets to get them part of the compilation with feature `_internal_test_exports`. |
| 2 | |
| 3 | use std::collections::VecDeque; |
| 4 | use std::net::{IpAddr, Ipv4Addr, SocketAddr}; |
| 5 | use std::time::{Duration, Instant}; |
| 6 | |
| 7 | use crate::change::{SdpAnswer, SdpOffer}; |
| 8 | use crate::format::Codec; |
| 9 | use crate::net::{Protocol, Receive}; |
| 10 | use crate::packet::{CodecDepacketizer, CodecExtra, Depacketizer, DepacketizingBuffer, RtpMeta}; |
| 11 | use crate::rtp_::{Frequency, MediaTime, Rtcp, RtcpFb, RtpHeader}; |
| 12 | use crate::streams::register::ReceiverRegister; |
| 13 | use crate::streams::rtx_cache_buf::EvictingBuffer; |
| 14 | use crate::{Input, Output, Rtc}; |
| 15 | |
| 16 | use super::Rng; |
| 17 | #[allow(unused)] |
| 18 | use super::setup::{random_config, random_extmap}; |
| 19 | |
| 20 | pub fn rtx_buffer(data: &[u8]) { |
| 21 | if data.len() < 4 { |
| 22 | return; |
| 23 | } |
| 24 | |
| 25 | let buf_size = u16::from_be_bytes([data[0], data[1]]); |
| 26 | let max_age = data[2] as u64; |
| 27 | let max_size = data[3] as usize; |
| 28 | let mut buf = EvictingBuffer::new(buf_size as usize, Duration::from_secs(max_age), max_size); |
| 29 | let mut now = Instant::now(); |
| 30 | let mut pos = 0; |
| 31 | |
| 32 | for d in &data[4..] { |
| 33 | now += Duration::from_millis(*d as u64); |
| 34 | if d % 2 == 0 { |
| 35 | buf.maybe_evict(now) |
| 36 | } else { |
| 37 | pos += *d as u64; |
| 38 | buf.push(pos, now, d); |
| 39 | } |
| 40 | } |
| 41 | } |
| 42 | |
| 43 | pub fn rtp_header(data: &[u8]) -> Option<()> { |
| 44 | let mut rng = Rng::new(data); |
| 45 | let exts = random_extmap(&mut rng, 10)?; |
| 46 | let len = rng.usize(76)?; |
| 47 | RtpHeader::_parse(rng.slice(len)?, &exts); |
| 48 | Some(()) |
| 49 | } |
| 50 | |
| 51 | pub fn rtp_packet(data: &[u8]) -> Option<()> { |
| 52 | let mut rng = Rng::new(data); |
| 53 | let exts = random_extmap(&mut rng, 10)?; |
| 54 | // Maximum RTP packet size is typically around 1500 bytes (MTU) |
| 55 | let packet_len = rng.usize(1500)?; |
| 56 | let packet = rng.slice(packet_len)?; |
| 57 | |
| 58 | // Parse the header from the packet |
| 59 | let header = RtpHeader::_parse(packet, &exts)?; |
| 60 | |
| 61 | // Extract the payload (data after the header) |
| 62 | let _payload = packet.get(header.header_len..)?; |
| 63 | |
| 64 | Some(()) |
| 65 | } |
| 66 | |
| 67 | pub fn sdp_offer(data: &[u8]) -> Option<()> { |
| 68 | let str = std::str::from_utf8(data).ok()?; |
| 69 | let _ = SdpOffer::from_sdp_string(str); |
| 70 | Some(()) |
| 71 | } |
| 72 | |
| 73 | pub fn sdp_answer(data: &[u8]) -> Option<()> { |
| 74 | let str = std::str::from_utf8(data).ok()?; |
| 75 | let _ = SdpAnswer::from_sdp_string(str); |
| 76 | Some(()) |
| 77 | } |
| 78 | |
| 79 | pub fn depack(data: &[u8]) -> Option<()> { |
| 80 | let mut rng = Rng::new(data); |
| 81 | |
| 82 | let codec = match rng.u8(10)? { |
| 83 | 0 => Codec::Opus, |
| 84 | 1 => Codec::Vp8, |
| 85 | 2 => Codec::Vp9, |
| 86 | 3 => Codec::H264, |
| 87 | 4 => Codec::H265, |
| 88 | 5 => Codec::H266, |
| 89 | 6 => Codec::Av1, |
| 90 | 7 => Codec::PCMU, |
| 91 | 8 => Codec::PCMA, |
| 92 | 9 => Codec::G722, |
| 93 | 10 => Codec::CN, |
| 94 | _ => unreachable!(), |
| 95 | }; |
| 96 | |
| 97 | let mut depack = DepacketizingBuffer::new(codec.into(), rng.usize(300)?); |
| 98 | |
| 99 | let exts = random_extmap(&mut rng, 10)?; |
| 100 | |
| 101 | let start = Instant::now(); |
| 102 | |
| 103 | loop { |
| 104 | let do_push = rng.bool()?; |
| 105 | |
| 106 | if do_push { |
| 107 | let hlen = rng.usize(76)?; |
| 108 | let header = RtpHeader::_parse(rng.slice(hlen)?, &exts)?; |
| 109 | let meta = RtpMeta { |
| 110 | received: start + Duration::from_millis(rng.u64(10000)?), |
| 111 | time: MediaTime::new(rng.u64(u64::MAX)?, Frequency::MICROS), |
| 112 | seq_no: rng.u64(u64::MAX)?.into(), |
| 113 | header, |
| 114 | last_sender_info: None, |
| 115 | }; |
| 116 | let len = rng.usize(1200)?; |
| 117 | let data = rng.slice(len)?.to_vec(); |
| 118 | depack.push(meta, data); |
| 119 | } else { |
| 120 | depack.pop(); |
| 121 | } |
| 122 | } |
| 123 | } |
| 124 | |
| 125 | pub fn receive_register(data: &[u8]) -> Option<()> { |
| 126 | let mut rng = Rng::new(data); |
| 127 | let mut rr = ReceiverRegister::new(None); |
| 128 | let start = Instant::now(); |
| 129 | loop { |
| 130 | match rng.u8(2)? { |
| 131 | 0 => { |
| 132 | let seq = rng.u64(u64::MAX / 2)?; |
| 133 | let arrival = start + Duration::from_micros(rng.u64(u64::MAX / 100)?); |
| 134 | let rtp_time = rng.u32(u32::MAX / 2)?; |
| 135 | let clock_rate = rng.u32(u32::MAX / 2)?; |
| 136 | rr.update(seq.into(), arrival, rtp_time, clock_rate); |
| 137 | } |
| 138 | 1 => { |
| 139 | rr.nack_report(); |
| 140 | } |
| 141 | 2 => { |
| 142 | rr.reception_report(); |
| 143 | } |
| 144 | _ => unreachable!(), |
| 145 | } |
| 146 | } |
| 147 | } |
| 148 | |
| 149 | /// Fuzz the full `Rtc` receive path: instance init, datagram demux |
| 150 | /// (STUN/DTLS/RTP/RTCP), dispatch, and `poll_output` draining. |
| 151 | pub fn receive(data: &[u8]) -> Option<()> { |
| 152 | let mut rng = Rng::new(data); |
| 153 | |
| 154 | let base = Instant::now(); |
| 155 | let mut rtc = Rtc::new(base); |
| 156 | |
| 157 | let source = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(1, 1, 1, 1)), 5000); |
| 158 | let destination = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(2, 2, 2, 2)), 5001); |
| 159 | |
| 160 | let mut millis: u64 = 0; |
| 161 | |
| 162 | loop { |
| 163 | // Drive time monotonically forward. |
| 164 | millis += rng.u64(50)?; |
| 165 | let now = base + Duration::from_millis(millis); |
| 166 | |
| 167 | if rng.bool()? { |
| 168 | // Feed a datagram through the full demux + dispatch path. |
| 169 | let len = rng.usize(2000)?; |
| 170 | let buf = rng.slice(len)?; |
| 171 | if let Ok(receive) = Receive::new(Protocol::Udp, source, destination, buf) { |
| 172 | let _ = rtc.handle_input(Input::Receive(now, receive)); |
| 173 | } |
| 174 | } else { |
| 175 | let _ = rtc.handle_input(Input::Timeout(now)); |
| 176 | } |
| 177 | |
| 178 | // Drain outputs (bounded to avoid spinning on a single input). |
| 179 | for _ in 0..1000 { |
| 180 | match rtc.poll_output() { |
| 181 | Ok(Output::Timeout(_)) => break, |
| 182 | Ok(_) => continue, |
| 183 | Err(_) => break, |
| 184 | } |
| 185 | } |
| 186 | |
| 187 | if !rtc.is_alive() { |
| 188 | return Some(()); |
| 189 | } |
| 190 | } |
| 191 | } |
| 192 | |
| 193 | /// Fuzz STUN message parsing (the ICE input path). |
| 194 | pub fn stun(data: &[u8]) -> Option<()> { |
| 195 | let _ = is::stun::StunMessage::parse(data); |
| 196 | Some(()) |
| 197 | } |
| 198 | |
| 199 | /// Fuzz RTCP compound-packet parsing and the feedback conversion. |
| 200 | pub fn rtcp(data: &[u8]) -> Option<()> { |
| 201 | let mut feedback = VecDeque::new(); |
| 202 | Rtcp::read_packet(data, &mut feedback); |
| 203 | // Exercise the feedback conversion as well. |
| 204 | let _ = RtcpFb::from_rtcp(feedback.drain(..)).count(); |
| 205 | Some(()) |
| 206 | } |
| 207 | |
| 208 | /// Fuzz each codec depacketizer directly (bypassing the buffer's gating), |
| 209 | /// exercising the raw `Depacketizer::depacketize` parsers. |
| 210 | pub fn depack_direct(data: &[u8]) -> Option<()> { |
| 211 | let mut rng = Rng::new(data); |
| 212 | |
| 213 | let codec = match rng.u8(10)? { |
| 214 | 0 => Codec::Opus, |
| 215 | 1 => Codec::Vp8, |
| 216 | 2 => Codec::Vp9, |
| 217 | 3 => Codec::H264, |
| 218 | 4 => Codec::H265, |
| 219 | 5 => Codec::H266, |
| 220 | 6 => Codec::Av1, |
| 221 | 7 => Codec::PCMU, |
| 222 | 8 => Codec::PCMA, |
| 223 | 9 => Codec::G722, |
| 224 | 10 => Codec::CN, |
| 225 | _ => unreachable!(), |
| 226 | }; |
| 227 | |
| 228 | let mut depack: CodecDepacketizer = codec.into(); |
| 229 | let mut out = Vec::new(); |
| 230 | let mut extra = CodecExtra::None; |
| 231 | |
| 232 | loop { |
| 233 | let marker = rng.bool()?; |
| 234 | let len = rng.usize(1500)?; |
| 235 | let packet = rng.slice(len)?; |
| 236 | |
| 237 | let _ = depack.depacketize(packet, &mut out, &mut extra); |
| 238 | let _ = depack.is_partition_head(packet); |
| 239 | let _ = depack.is_partition_tail(marker, packet); |
| 240 | |
| 241 | // Keep memory bounded across iterations. |
| 242 | if out.len() > 4_000_000 { |
| 243 | out.clear(); |
| 244 | } |
| 245 | } |
| 246 | } |