Skip to content
File

Blob: firmware/crates/radio-webrtc/src/audio/tests.rs

rust298 lines
1//! Authenticated RTP over an in-memory network with public fixture keys.
2use super::Audio;
3use radio_core::{music::MAX_OPUS_BYTES, protocol::SILENCE};
4use std::{
5 collections::VecDeque,
6 net::{Ipv4Addr, SocketAddr},
7 sync::Arc,
8 time::{Duration, Instant},
9};
10use str0m::{
11 Candidate, Event, Input, Output, Rtc,
12 crypto::dtls::DtlsCert,
13 format::{Codec, FormatParams},
14 media::{Direction, Frequency, MediaKind, Mid, Pt},
15 net::{Protocol, Receive, Transmit},
16 rtp::{RtpPacket, Ssrc},
17};
18 
19struct Endpoint {
20 rtc: Rtc,
21 outbound: VecDeque<Transmit>,
22 received: Vec<RtpPacket>,
23}
24 
25impl Endpoint {
26 fn new(port: u16, now: Instant) -> Self {
27 let mut config = Rtc::builder()
28 .set_crypto_provider(Arc::new(str0m_rust_crypto::default_provider()))
29 .set_dtls_cert(DtlsCert {
30 certificate: include_bytes!("../../tests/fixtures/certificate.der").to_vec(),
31 private_key: include_bytes!("../../tests/fixtures/key.der").to_vec(),
32 })
33 .clear_codecs()
34 .set_rtp_mode(true);
35 // A non-default dynamic PT detects accidental hard-coded Opus values.
36 config.codec_config().add_config(
37 103.into(),
38 None,
39 Codec::Opus,
40 Frequency::FORTY_EIGHT_KHZ,
41 Some(2),
42 FormatParams::default(),
43 );
44 let mut this = Self {
45 rtc: config.build(now),
46 outbound: VecDeque::new(),
47 received: Vec::new(),
48 };
49 let address = SocketAddr::from((Ipv4Addr::LOCALHOST, port));
50 this.rtc
51 .add_local_candidate(Candidate::host(address, "udp").unwrap());
52 this.drain();
53 this
54 }
55 
56 fn drain(&mut self) {
57 loop {
58 match self.rtc.poll_output().unwrap() {
59 Output::Timeout(_) => break,
60 Output::Transmit(packet) => self.outbound.push_back(packet),
61 Output::Event(Event::RtpPacket(packet)) => self.received.push(packet),
62 _ => {}
63 }
64 }
65 }
66 
67 fn receive(&mut self, packet: &Transmit, now: Instant) {
68 let receive = Receive::new(
69 Protocol::Udp,
70 packet.source,
71 packet.destination,
72 &packet.contents,
73 )
74 .unwrap();
75 let input = Input::Receive(now, receive);
76 assert!(self.rtc.accepts(&input));
77 self.rtc.handle_input(input).unwrap();
78 self.drain();
79 }
80 
81 fn timeout(&mut self, now: Instant) {
82 self.rtc.handle_input(Input::Timeout(now)).unwrap();
83 self.drain();
84 }
85}
86 
87struct Pair {
88 sender: Endpoint,
89 receiver: Endpoint,
90 audio: Audio,
91 pt: Pt,
92 ssrc: Ssrc,
93 now: Instant,
94 wire_audio: Vec<Transmit>,
95 drop_next: bool,
96}
97 
98impl Pair {
99 fn new() -> Self {
100 let now = Instant::now();
101 let mut sender = Endpoint::new(41000, now);
102 let mut receiver = Endpoint::new(42000, now);
103 let mut changes = sender.rtc.sdp_api();
104 let mid = changes.add_media(MediaKind::Audio, Direction::SendOnly, None, None, None);
105 let (offer, pending) = changes.apply().unwrap();
106 sender.drain();
107 let answer = receiver.rtc.sdp_api().accept_offer(offer).unwrap();
108 receiver.drain();
109 sender.rtc.sdp_api().accept_answer(pending, answer).unwrap();
110 sender.drain();
111 let pt = sender.rtc.media(mid).unwrap().remote_pts()[0];
112 let ssrc = sender
113 .rtc
114 .direct_api()
115 .stream_tx_by_mid(mid, None)
116 .unwrap()
117 .ssrc();
118 let mut this = Self {
119 sender,
120 receiver,
121 audio: Audio::new(mid),
122 pt,
123 ssrc,
124 now,
125 wire_audio: Vec::new(),
126 drop_next: false,
127 };
128 for _ in 0..5000 {
129 this.progress();
130 if this.sender.rtc.is_connected() && this.receiver.rtc.is_connected() {
131 return this;
132 }
133 }
134 panic!("public-fixture DTLS handshake timed out");
135 }
136 
137 fn progress(&mut self) {
138 self.now += Duration::from_millis(1);
139 self.sender.timeout(self.now);
140 self.receiver.timeout(self.now);
141 for _ in 0..100 {
142 if self.sender.outbound.is_empty() && self.receiver.outbound.is_empty() {
143 return;
144 }
145 while let Some(packet) = self.sender.outbound.pop_front() {
146 let is_audio =
147 packet.contents[0] & 0xc0 == 0x80 && !(192..=223).contains(&packet.contents[1]);
148 let drop = is_audio && std::mem::take(&mut self.drop_next);
149 if !drop {
150 self.receiver.receive(&packet, self.now);
151 }
152 if is_audio {
153 self.wire_audio.push(packet);
154 }
155 }
156 while let Some(packet) = self.receiver.outbound.pop_front() {
157 self.sender.receive(&packet, self.now);
158 }
159 }
160 panic!("in-memory network did not quiesce");
161 }
162 
163 fn send(&mut self, pts_ms: u32, bytes: &[u8]) {
164 self.audio
165 .write(&mut self.sender.rtc, pts_ms, bytes)
166 .unwrap();
167 self.sender.drain();
168 for _ in 0..20 {
169 self.progress();
170 }
171 }
172}
173 
174#[test]
175fn opus_payload_ownership_identity_and_clock_survive_rollover() {
176 let mut pair = Pair::new();
177 pair.audio.next_seq = 65_534.into();
178 // The second packet crosses the 32-bit RTP timestamp boundary.
179 let first_pts = 89_478_480_u32;
180 let packets = [
181 vec![0xf8; MAX_OPUS_BYTES],
182 SILENCE.to_vec(),
183 vec![0xfc; 240],
184 ];
185 for (index, expected) in packets.iter().enumerate() {
186 let mut producer_buffer = expected.clone();
187 pair.audio
188 .write(
189 &mut pair.sender.rtc,
190 first_pts + index as u32 * 20,
191 &producer_buffer,
192 )
193 .unwrap();
194 // The caller may immediately refill the music buffer, even before the
195 // str0m queue is flushed. The transmitted bytes must remain unchanged.
196 producer_buffer.fill(0);
197 pair.sender.drain();
198 for _ in 0..20 {
199 pair.progress();
200 }
201 }
202 assert_eq!(pair.receiver.received.len(), packets.len());
203 for (index, (received, expected)) in pair.receiver.received.iter().zip(&packets).enumerate() {
204 assert_eq!(received.payload.as_ref(), expected);
205 assert_eq!(received.header.payload_type, pair.pt);
206 assert_eq!(received.header.ssrc, pair.ssrc);
207 assert_eq!(received.header.sequence_number, (65_534 + index) as u16);
208 assert_eq!(
209 received.header.timestamp,
210 (first_pts + index as u32 * 20).wrapping_mul(48)
211 );
212 assert!(!received.header.marker);
213 }
214 assert_eq!(
215 pair.receiver.received[0].header.ext_vals.mid,
216 Some(pair.audio.mid)
217 );
218 assert_eq!(pair.wire_audio.len(), packets.len());
219 // 1500-byte IPv4 path minus the 20-byte IP and 8-byte UDP headers.
220 assert!(
221 pair.wire_audio
222 .iter()
223 .all(|packet| packet.contents.len() <= 1472)
224 );
225 assert!(pair.wire_audio[0].contents.len() > MAX_OPUS_BYTES);
226}
227 
228#[test]
229fn loss_and_replay_do_not_repeat_audio_or_reuse_sequences() {
230 let mut pair = Pair::new();
231 let start = *pair.audio.next_seq;
232 pair.send(u32::MAX - 15, SILENCE);
233 pair.drop_next = true;
234 pair.send(4, &[0xf8; 240]);
235 pair.send(24, SILENCE);
236 assert_eq!(pair.receiver.received.len(), 2);
237 assert_eq!(
238 pair.receiver.received[1].header.sequence_number,
239 (start + 2) as u16
240 );
241 assert_eq!(
242 pair.receiver.received[1]
243 .header
244 .timestamp
245 .wrapping_sub(pair.receiver.received[0].header.timestamp),
246 1920
247 );
248 
249 // The lost packet is still unseen by SRTP: a forged authentication tag
250 // must not be accepted as fresh audio.
251 let lost = &pair.wire_audio[1];
252 let mut corrupted = lost.contents.to_vec();
253 *corrupted.last_mut().unwrap() ^= 1;
254 pair.receiver.receive(
255 &Transmit {
256 proto: lost.proto,
257 source: lost.source,
258 destination: lost.destination,
259 contents: corrupted.into(),
260 },
261 pair.now,
262 );
263 assert_eq!(pair.receiver.received.len(), 2);
264 
265 // Replaying a valid encrypted packet cannot deliver its Opus payload again.
266 pair.receiver.receive(&pair.wire_audio[0], pair.now);
267 assert_eq!(pair.receiver.received.len(), 2);
268 let next = pair.audio.next_seq;
269 assert!(pair.audio.write(&mut pair.sender.rtc, 44, &[]).is_err());
270 assert!(
271 pair.audio
272 .write(&mut pair.sender.rtc, 44, &[0; MAX_OPUS_BYTES + 1])
273 .is_err()
274 );
275 assert_eq!(pair.audio.next_seq, next);
276 pair.send(44, &[0xfc; 240]);
277 assert_eq!(
278 pair.receiver.received[2].header.sequence_number,
279 (start + 3) as u16
280 );
281 
282 pair.sender.rtc.close().unwrap();
283 pair.sender.drain();
284 let next = pair.audio.next_seq;
285 assert!(pair.audio.write(&mut pair.sender.rtc, 64, SILENCE).is_err());
286 assert_eq!(pair.audio.next_seq, next);
287}
288 
289#[test]
290fn audio_requires_an_authenticated_transport() {
291 let mut endpoint = Endpoint::new(43000, Instant::now());
292 let mut audio = Audio::new(Mid::new());
293 let next = audio.next_seq;
294 assert!(audio.write(&mut endpoint.rtc, 0, SILENCE).is_err());
295 assert_eq!(audio.next_seq, next);
296 assert!(endpoint.outbound.is_empty());
297}