Skip to content
File

Blob: firmware/vendor/str0m/tests/api-errors.rs

rust264 lines
1//! Tests for API misuse and error conditions.
2 
3use std::net::Ipv4Addr;
4use std::time::Duration;
5 
6use str0m::media::{Direction, MediaKind};
7use str0m::{Event, RtcError};
8 
9mod common;
10use common::{Peer, TestRtc, init_crypto_default, init_log, negotiate, progress};
11 
12/// Test that disconnect() API works and is_alive() returns false after.
13#[test]
14fn api_disconnect_and_is_alive() -> Result<(), RtcError> {
15 init_log();
16 init_crypto_default();
17 
18 let mut l = TestRtc::new(Peer::Left);
19 let mut r = TestRtc::new(Peer::Right);
20 
21 l.add_host_candidate((Ipv4Addr::new(1, 1, 1, 1), 1000).into());
22 r.add_host_candidate((Ipv4Addr::new(2, 2, 2, 2), 2000).into());
23 
24 let (offer, pending) = l.span.in_scope(|| {
25 let mut change = l.rtc.sdp_api();
26 let _ = change.add_channel("test".into());
27 change.apply().unwrap()
28 });
29 
30 let answer = r.span.in_scope(|| r.rtc.sdp_api().accept_offer(offer))?;
31 l.span
32 .in_scope(|| l.rtc.sdp_api().accept_answer(pending, answer))?;
33 
34 // Connect first
35 loop {
36 if l.is_connected() && r.is_connected() {
37 break;
38 }
39 if l.duration() > Duration::from_secs(5) {
40 panic!("Failed to connect");
41 }
42 progress(&mut l, &mut r)?;
43 }
44 
45 assert!(l.rtc.is_alive(), "Should be alive before disconnect");
46 
47 // Disconnect
48 l.rtc.disconnect();
49 
50 assert!(!l.rtc.is_alive(), "Should not be alive after disconnect");
51 
52 Ok(())
53}
54 
55/// Test that RecvOnly direction is properly set.
56#[test]
57fn api_error_not_sending_direction() -> Result<(), RtcError> {
58 init_log();
59 init_crypto_default();
60 
61 let mut l = TestRtc::new(Peer::Left);
62 let mut r = TestRtc::new(Peer::Right);
63 
64 l.add_host_candidate((Ipv4Addr::new(1, 1, 1, 1), 1000).into());
65 r.add_host_candidate((Ipv4Addr::new(2, 2, 2, 2), 2000).into());
66 
67 // L is RecvOnly - cannot send
68 let mid = negotiate(&mut l, &mut r, |change| {
69 change.add_media(MediaKind::Audio, Direction::RecvOnly, None, None, None)
70 });
71 
72 loop {
73 if l.is_connected() && r.is_connected() {
74 break;
75 }
76 progress(&mut l, &mut r)?;
77 }
78 
79 // L's direction should be RecvOnly
80 let media = l.media(mid);
81 assert!(media.is_some(), "Media should exist");
82 assert_eq!(
83 media.unwrap().direction(),
84 Direction::RecvOnly,
85 "L should be RecvOnly"
86 );
87 
88 // R's direction should be SendOnly (inverse)
89 let r_media = r.media(mid);
90 assert!(r_media.is_some(), "R media should exist");
91 assert_eq!(
92 r_media.unwrap().direction(),
93 Direction::SendOnly,
94 "R should be SendOnly (inverse of L's RecvOnly)"
95 );
96 
97 Ok(())
98}
99 
100/// Test that SendOnly direction prevents receiving.
101#[test]
102fn api_error_not_receiving_direction() -> Result<(), RtcError> {
103 init_log();
104 init_crypto_default();
105 
106 let mut l = TestRtc::new(Peer::Left);
107 let mut r = TestRtc::new(Peer::Right);
108 
109 l.add_host_candidate((Ipv4Addr::new(1, 1, 1, 1), 1000).into());
110 r.add_host_candidate((Ipv4Addr::new(2, 2, 2, 2), 2000).into());
111 
112 // L is SendOnly
113 let mid = negotiate(&mut l, &mut r, |change| {
114 change.add_media(MediaKind::Audio, Direction::SendOnly, None, None, None)
115 });
116 
117 loop {
118 if l.is_connected() && r.is_connected() {
119 break;
120 }
121 progress(&mut l, &mut r)?;
122 }
123 
124 // L's direction is SendOnly, so R is RecvOnly and cannot request keyframes to L
125 // (no sender source on R's side for this media)
126 let media = l.media(mid);
127 assert!(media.is_some(), "Media should exist");
128 assert_eq!(
129 media.unwrap().direction(),
130 Direction::SendOnly,
131 "L should be SendOnly"
132 );
133 
134 Ok(())
135}
136 
137/// Test that operations on disconnected Rtc are handled gracefully.
138#[test]
139fn api_operations_after_disconnect() -> Result<(), RtcError> {
140 init_log();
141 init_crypto_default();
142 
143 let mut l = TestRtc::new(Peer::Left);
144 let mut r = TestRtc::new(Peer::Right);
145 
146 l.add_host_candidate((Ipv4Addr::new(1, 1, 1, 1), 1000).into());
147 r.add_host_candidate((Ipv4Addr::new(2, 2, 2, 2), 2000).into());
148 
149 let mid = negotiate(&mut l, &mut r, |change| {
150 change.add_media(MediaKind::Audio, Direction::SendRecv, None, None, None)
151 });
152 
153 loop {
154 if l.is_connected() && r.is_connected() {
155 break;
156 }
157 progress(&mut l, &mut r)?;
158 }
159 
160 // Disconnect
161 l.rtc.disconnect();
162 
163 // After disconnect, writer should be None or return error on write
164 // Get params before writer to avoid borrow issues
165 let params = l.params_opus();
166 let pt = params.pt();
167 let wallclock = l.start + l.duration();
168 let time = l.duration().into();
169 
170 if let Some(w) = l.writer(mid) {
171 // Write may fail or succeed but won't actually send anything
172 let _ = w.write(pt, wallclock, time, vec![0u8; 80]);
173 }
174 
175 Ok(())
176}
177 
178/// Test IceConnectionStateChange events are generated.
179#[test]
180fn api_ice_state_change_events() -> Result<(), RtcError> {
181 init_log();
182 init_crypto_default();
183 
184 let mut l = TestRtc::new(Peer::Left);
185 let mut r = TestRtc::new(Peer::Right);
186 
187 l.add_host_candidate((Ipv4Addr::new(1, 1, 1, 1), 1000).into());
188 r.add_host_candidate((Ipv4Addr::new(2, 2, 2, 2), 2000).into());
189 
190 let (offer, pending) = l.span.in_scope(|| {
191 let mut change = l.rtc.sdp_api();
192 let _ = change.add_channel("test".into());
193 change.apply().unwrap()
194 });
195 
196 let answer = r.span.in_scope(|| r.rtc.sdp_api().accept_offer(offer))?;
197 l.span
198 .in_scope(|| l.rtc.sdp_api().accept_answer(pending, answer))?;
199 
200 loop {
201 if l.is_connected() && r.is_connected() {
202 break;
203 }
204 if l.duration() > Duration::from_secs(5) {
205 panic!("Failed to connect");
206 }
207 progress(&mut l, &mut r)?;
208 }
209 
210 // Check that we received ICE state change events
211 let ice_events: Vec<_> = l
212 .events
213 .iter()
214 .filter(|(_, e)| matches!(e, Event::IceConnectionStateChange(_)))
215 .collect();
216 
217 assert!(
218 !ice_events.is_empty(),
219 "Should have received IceConnectionStateChange events"
220 );
221 
222 Ok(())
223}
224 
225/// Test Connected event is generated.
226#[test]
227fn api_connected_event() -> Result<(), RtcError> {
228 init_log();
229 init_crypto_default();
230 
231 let mut l = TestRtc::new(Peer::Left);
232 let mut r = TestRtc::new(Peer::Right);
233 
234 l.add_host_candidate((Ipv4Addr::new(1, 1, 1, 1), 1000).into());
235 r.add_host_candidate((Ipv4Addr::new(2, 2, 2, 2), 2000).into());
236 
237 let (offer, pending) = l.span.in_scope(|| {
238 let mut change = l.rtc.sdp_api();
239 let _ = change.add_channel("test".into());
240 change.apply().unwrap()
241 });
242 
243 let answer = r.span.in_scope(|| r.rtc.sdp_api().accept_offer(offer))?;
244 l.span
245 .in_scope(|| l.rtc.sdp_api().accept_answer(pending, answer))?;
246 
247 loop {
248 if l.is_connected() && r.is_connected() {
249 break;
250 }
251 if l.duration() > Duration::from_secs(5) {
252 panic!("Failed to connect");
253 }
254 progress(&mut l, &mut r)?;
255 }
256 
257 // Check that we received Connected event
258 let connected = l.events.iter().any(|(_, e)| matches!(e, Event::Connected));
259 
260 assert!(connected, "Should have received Connected event");
261 
262 Ok(())
263}