1use crate::channel::ChannelId;
2use crate::ieee80211::FrameLayout;
3use crate::realtek::{parse_rx_aggregate, AggregateError, RealtekRxPacket, RxPacketType};
4use crate::routes::{
5 PayloadRouteError, PayloadRouteEvent, PayloadRouteId, PayloadRouteManager, PayloadRuntimeKey,
6};
7use crate::rtp::{DepacketizedFrame, RtpDepacketizer, RtpHeader};
8use crate::wfb::{FecCounters, WfbKeypair};
9
10#[derive(Debug, Clone)]
17pub struct ReceiverRuntime {
18 routes: PayloadRouteManager,
19 video_runtime: PayloadRuntimeKey,
20 video_route_id: PayloadRouteId,
21 rtp: RtpDepacketizer,
22}
23
24#[derive(Debug, Clone, Default)]
26pub struct ReceiverBatchOptions {
27 pub accept_corrupted: bool,
29 pub raw_payload_routes: Vec<PayloadRouteId>,
31 pub rtp_payload_taps: Vec<RtpPayloadTap>,
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub struct RtpPayloadTap {
43 pub route_id: PayloadRouteId,
45 pub payload_type: u8,
47}
48
49#[derive(Debug, Clone, PartialEq, Eq)]
51pub struct RoutePayload {
52 pub route_id: PayloadRouteId,
54 pub channel_id: ChannelId,
56 pub packet_seq: u64,
58 pub data: Vec<u8>,
60}
61
62#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
64pub struct ReceiverBatchCounters {
65 pub packets: usize,
67 pub accepted_packets: usize,
69 pub dropped_packets: usize,
71 pub crc_dropped: usize,
73 pub icv_dropped: usize,
75 pub report_dropped: usize,
77 pub ignored_frames: usize,
79 pub sessions: usize,
81 pub wfb_payloads: usize,
83 pub rtp_packets: usize,
85 pub video_frames: usize,
87 pub raw_payload_count: usize,
89 pub raw_payload_bytes: usize,
91 pub route_errors: usize,
93}
94
95#[derive(Debug, Clone, PartialEq, Eq)]
97pub struct ReceiverBatch {
98 pub frames: Vec<DepacketizedFrame>,
100 pub raw_payloads: Vec<RoutePayload>,
102 pub counters: ReceiverBatchCounters,
104 pub fec_counters: FecCounters,
106}
107
108impl ReceiverRuntime {
109 pub fn from_routes(
114 routes: PayloadRouteManager,
115 video_runtime: PayloadRuntimeKey,
116 video_route_id: PayloadRouteId,
117 ) -> Self {
118 Self {
119 routes,
120 video_runtime,
121 video_route_id,
122 rtp: RtpDepacketizer::new(),
123 }
124 }
125
126 pub fn with_plain_video_route(
130 frame_layout: FrameLayout,
131 video_route_id: PayloadRouteId,
132 channel_id: ChannelId,
133 key_slot: u64,
134 fec_k: usize,
135 fec_n: usize,
136 ) -> Result<Self, PayloadRouteError> {
137 let mut routes = PayloadRouteManager::new(frame_layout);
138 let video_runtime =
139 routes.add_plain_route(video_route_id, channel_id, key_slot, fec_k, fec_n)?;
140 Ok(Self::from_routes(routes, video_runtime, video_route_id))
141 }
142
143 pub fn with_keyed_video_route(
145 frame_layout: FrameLayout,
146 video_route_id: PayloadRouteId,
147 channel_id: ChannelId,
148 key_slot: u64,
149 keypair: WfbKeypair,
150 minimum_epoch: u64,
151 ) -> Result<Self, PayloadRouteError> {
152 let mut routes = PayloadRouteManager::new(frame_layout);
153 let video_runtime =
154 routes.add_keyed_route(video_route_id, channel_id, key_slot, keypair, minimum_epoch)?;
155 Ok(Self::from_routes(routes, video_runtime, video_route_id))
156 }
157
158 pub const fn video_runtime(&self) -> PayloadRuntimeKey {
160 self.video_runtime
161 }
162
163 pub const fn video_route_id(&self) -> PayloadRouteId {
165 self.video_route_id
166 }
167
168 pub fn routes(&self) -> &PayloadRouteManager {
170 &self.routes
171 }
172
173 pub fn routes_mut(&mut self) -> &mut PayloadRouteManager {
175 &mut self.routes
176 }
177
178 pub fn rtp_mut(&mut self) -> &mut RtpDepacketizer {
180 &mut self.rtp
181 }
182
183 pub fn add_plain_route(
185 &mut self,
186 route_id: PayloadRouteId,
187 channel_id: ChannelId,
188 key_slot: u64,
189 fec_k: usize,
190 fec_n: usize,
191 ) -> Result<PayloadRuntimeKey, PayloadRouteError> {
192 self.routes
193 .add_plain_route(route_id, channel_id, key_slot, fec_k, fec_n)
194 }
195
196 pub fn add_keyed_route(
198 &mut self,
199 route_id: PayloadRouteId,
200 channel_id: ChannelId,
201 key_slot: u64,
202 keypair: WfbKeypair,
203 minimum_epoch: u64,
204 ) -> Result<PayloadRuntimeKey, PayloadRouteError> {
205 self.routes
206 .add_keyed_route(route_id, channel_id, key_slot, keypair, minimum_epoch)
207 }
208
209 pub fn video_fec_counters(&self) -> FecCounters {
211 self.routes
212 .fec_counters(self.video_runtime)
213 .unwrap_or_default()
214 }
215
216 pub fn accepts_video_frame(&self, frame: &[u8]) -> bool {
218 self.routes.accepts_80211_frame(self.video_runtime, frame)
219 }
220
221 pub fn push_rx_transfer(
223 &mut self,
224 transfer: &[u8],
225 options: &ReceiverBatchOptions,
226 ) -> Result<ReceiverBatch, AggregateError> {
227 let packets = parse_rx_aggregate(transfer)?;
228 Ok(self.push_rx_packets(packets, options))
229 }
230
231 pub fn push_rx_packets<'a>(
233 &mut self,
234 packets: impl IntoIterator<Item = RealtekRxPacket<'a>>,
235 options: &ReceiverBatchOptions,
236 ) -> ReceiverBatch {
237 let mut batch = self.empty_batch();
238
239 for packet in packets {
240 batch.counters.packets += 1;
241 if packet.attrib.crc_err && !options.accept_corrupted {
242 batch.counters.crc_dropped += 1;
243 continue;
244 }
245 if packet.attrib.icv_err && !options.accept_corrupted {
246 batch.counters.icv_dropped += 1;
247 continue;
248 }
249 if packet.attrib.pkt_rpt_type != RxPacketType::NormalRx {
250 batch.counters.report_dropped += 1;
251 continue;
252 }
253
254 batch.counters.accepted_packets += 1;
255 match self.routes.push_80211_frame(packet.data) {
256 Ok(events) => self.apply_route_events(events, options, &mut batch),
257 Err(_) => {
258 batch.counters.ignored_frames += 1;
259 batch.counters.route_errors += 1;
260 }
261 }
262 }
263
264 batch.counters.dropped_packets =
265 batch.counters.crc_dropped + batch.counters.icv_dropped + batch.counters.report_dropped;
266 batch.fec_counters = self.video_fec_counters();
267 batch
268 }
269
270 pub fn push_80211_frame(
272 &mut self,
273 frame: &[u8],
274 options: &ReceiverBatchOptions,
275 ) -> Result<ReceiverBatch, PayloadRouteError> {
276 let mut batch = self.empty_batch();
277 let events = self.routes.push_80211_frame(frame)?;
278 self.apply_route_events(events, options, &mut batch);
279 batch.fec_counters = self.video_fec_counters();
280 Ok(batch)
281 }
282
283 pub fn push_decrypted_80211_frame(
285 &mut self,
286 runtime: PayloadRuntimeKey,
287 frame: &[u8],
288 decrypted_fragment: &[u8],
289 options: &ReceiverBatchOptions,
290 ) -> Result<ReceiverBatch, PayloadRouteError> {
291 let mut batch = self.empty_batch();
292 let events = self
293 .routes
294 .push_decrypted_80211_frame(runtime, frame, decrypted_fragment)?;
295 self.apply_route_events(events, options, &mut batch);
296 batch.fec_counters = self.video_fec_counters();
297 Ok(batch)
298 }
299
300 pub fn push_decrypted_fragment(
302 &mut self,
303 runtime: PayloadRuntimeKey,
304 data_nonce: u64,
305 decrypted_fragment: &[u8],
306 options: &ReceiverBatchOptions,
307 ) -> Result<ReceiverBatch, PayloadRouteError> {
308 let mut batch = self.empty_batch();
309 let events =
310 self.routes
311 .push_decrypted_fragment(runtime, data_nonce, decrypted_fragment)?;
312 self.apply_route_events(events, options, &mut batch);
313 batch.fec_counters = self.video_fec_counters();
314 Ok(batch)
315 }
316
317 fn empty_batch(&self) -> ReceiverBatch {
318 ReceiverBatch {
319 frames: Vec::new(),
320 raw_payloads: Vec::new(),
321 counters: ReceiverBatchCounters::default(),
322 fec_counters: self.video_fec_counters(),
323 }
324 }
325
326 fn apply_route_events(
327 &mut self,
328 events: Vec<PayloadRouteEvent>,
329 options: &ReceiverBatchOptions,
330 batch: &mut ReceiverBatch,
331 ) {
332 for event in events {
333 match event {
334 PayloadRouteEvent::IgnoredFrame => batch.counters.ignored_frames += 1,
335 PayloadRouteEvent::SessionEstablished { .. } => batch.counters.sessions += 1,
336 PayloadRouteEvent::Payload {
337 route_ids, payload, ..
338 } => {
339 if route_ids.contains(&self.video_route_id) {
340 batch.counters.wfb_payloads += 1;
341 batch.counters.rtp_packets += 1;
342 if let Ok(Some(frame)) = self.rtp.push(&payload.data) {
343 batch.counters.video_frames += 1;
344 batch.frames.push(frame);
345 }
346 }
347
348 for &route_id in &route_ids {
349 if !options.raw_payload_routes.contains(&route_id) {
350 continue;
351 }
352 copy_raw_payload(route_id, &payload, batch);
353 }
354
355 if options.rtp_payload_taps.is_empty() {
356 continue;
357 }
358 let Ok(header) = RtpHeader::parse(&payload.data) else {
359 continue;
360 };
361 for tap in &options.rtp_payload_taps {
362 if header.payload_type != tap.payload_type {
363 continue;
364 }
365 if !route_ids.contains(&tap.route_id) {
366 continue;
367 }
368 copy_raw_payload(tap.route_id, &payload, batch);
369 }
370 }
371 }
372 }
373 }
374}
375
376fn copy_raw_payload(
377 route_id: PayloadRouteId,
378 payload: &crate::pipeline::RecoveredPayload,
379 batch: &mut ReceiverBatch,
380) {
381 batch.counters.raw_payload_count += 1;
382 batch.counters.raw_payload_bytes += payload.data.len();
383 batch.raw_payloads.push(RoutePayload {
384 route_id,
385 channel_id: payload.channel_id,
386 packet_seq: payload.packet_seq,
387 data: payload.data.clone(),
388 });
389}
390
391#[cfg(test)]
392mod tests {
393 use super::*;
394 use crate::RadioPort;
395
396 fn plain(payload: &[u8]) -> Vec<u8> {
397 let mut out = Vec::new();
398 out.push(0);
399 out.extend_from_slice(&(payload.len() as u16).to_be_bytes());
400 out.extend_from_slice(payload);
401 out
402 }
403
404 fn rtp(payload_type: u8, payload: &[u8]) -> Vec<u8> {
405 let mut packet = vec![0x80, payload_type & 0x7f];
406 packet.extend_from_slice(&7u16.to_be_bytes());
407 packet.extend_from_slice(&48_000u32.to_be_bytes());
408 packet.extend_from_slice(&0x1122_3344u32.to_be_bytes());
409 packet.extend_from_slice(payload);
410 packet
411 }
412
413 #[test]
414 fn decrypted_fragment_can_fan_out_to_raw_route() {
415 let route = PayloadRouteId::new(7);
416 let mut runtime = ReceiverRuntime::with_plain_video_route(
417 FrameLayout::WithFcs,
418 route,
419 ChannelId::default_video(),
420 0,
421 1,
422 1,
423 )
424 .unwrap();
425 let batch = runtime
426 .push_decrypted_fragment(
427 runtime.video_runtime(),
428 0,
429 &plain(b"payload bytes"),
430 &ReceiverBatchOptions {
431 raw_payload_routes: vec![route],
432 ..ReceiverBatchOptions::default()
433 },
434 )
435 .unwrap();
436
437 assert_eq!(batch.counters.wfb_payloads, 1);
438 assert_eq!(batch.counters.raw_payload_count, 1);
439 assert_eq!(batch.raw_payloads[0].data, b"payload bytes");
440 }
441
442 #[test]
443 fn rtp_payload_tap_copies_only_matching_payload_type() {
444 let video_route = PayloadRouteId::new(1);
445 let audio_route = PayloadRouteId::new(3);
446 let mut runtime = ReceiverRuntime::with_plain_video_route(
447 FrameLayout::WithFcs,
448 video_route,
449 ChannelId::default_video(),
450 0,
451 1,
452 1,
453 )
454 .unwrap();
455 runtime
456 .add_plain_route(audio_route, ChannelId::default_video(), 0, 1, 1)
457 .unwrap();
458
459 let ignored = runtime
460 .push_decrypted_fragment(
461 runtime.video_runtime(),
462 0,
463 &plain(&rtp(crate::rtp::RTP_PAYLOAD_TYPE_H264, b"video")),
464 &ReceiverBatchOptions {
465 rtp_payload_taps: vec![RtpPayloadTap {
466 route_id: audio_route,
467 payload_type: crate::rtp::RTP_PAYLOAD_TYPE_OPUS,
468 }],
469 ..ReceiverBatchOptions::default()
470 },
471 )
472 .unwrap();
473 assert_eq!(ignored.counters.raw_payload_count, 0);
474
475 let packet = rtp(crate::rtp::RTP_PAYLOAD_TYPE_OPUS, b"opus");
476 let batch = runtime
477 .push_decrypted_fragment(
478 runtime.video_runtime(),
479 1 << 8,
480 &plain(&packet),
481 &ReceiverBatchOptions {
482 rtp_payload_taps: vec![RtpPayloadTap {
483 route_id: audio_route,
484 payload_type: crate::rtp::RTP_PAYLOAD_TYPE_OPUS,
485 }],
486 ..ReceiverBatchOptions::default()
487 },
488 )
489 .unwrap();
490
491 assert_eq!(batch.counters.raw_payload_count, 1);
492 assert_eq!(batch.raw_payloads[0].route_id, audio_route);
493 assert_eq!(batch.raw_payloads[0].data, packet);
494 }
495
496 #[test]
497 fn auxiliary_route_does_not_count_as_video_payload() {
498 let video_route = PayloadRouteId::new(1);
499 let data_route = PayloadRouteId::new(2);
500 let mut runtime = ReceiverRuntime::with_plain_video_route(
501 FrameLayout::WithFcs,
502 video_route,
503 ChannelId::default_video(),
504 0,
505 1,
506 1,
507 )
508 .unwrap();
509 let data_runtime = runtime
510 .add_plain_route(
511 data_route,
512 ChannelId::from_link_port(crate::channel::DEFAULT_LINK_ID, RadioPort::TunnelRx),
513 0,
514 1,
515 1,
516 )
517 .unwrap();
518 let batch = runtime
519 .push_decrypted_fragment(
520 data_runtime,
521 0,
522 &plain(b"data bytes"),
523 &ReceiverBatchOptions {
524 raw_payload_routes: vec![data_route],
525 ..ReceiverBatchOptions::default()
526 },
527 )
528 .unwrap();
529
530 assert_eq!(batch.counters.wfb_payloads, 0);
531 assert_eq!(batch.counters.rtp_packets, 0);
532 assert_eq!(batch.counters.raw_payload_count, 1);
533 assert_eq!(batch.raw_payloads[0].data, b"data bytes");
534 }
535}