1use crate::channel::ChannelId;
2use crate::ieee80211::FrameLayout;
3use crate::realtek::{
4 parse_rx_aggregate, parse_rx_aggregate_with_kind, AggregateError, RealtekRxPacket,
5 RxDescriptorKind, RxPacketType,
6};
7use crate::routes::{
8 PayloadRouteError, PayloadRouteEvent, PayloadRouteId, PayloadRouteManager, PayloadRuntimeKey,
9};
10use crate::rtp::{
11 DepacketizedFrame, RtpDepacketizer, RtpDepacketizerStatus, RtpHeader, RtpReorderBuffer,
12 RtpReorderStatus,
13};
14use crate::wfb::{FecCounters, WfbKeypair};
15
16#[derive(Debug, Clone)]
23pub struct ReceiverRuntime {
24 routes: PayloadRouteManager,
25 video_runtime: PayloadRuntimeKey,
26 video_route_id: PayloadRouteId,
27 rtp: RtpDepacketizer,
28 rtp_reorder: Option<RtpReorderBuffer>,
29}
30
31#[derive(Debug, Clone)]
33pub struct ReceiverBatchOptions {
34 pub accept_corrupted: bool,
36 pub raw_payload_routes: Vec<PayloadRouteId>,
38 pub rtp_payload_taps: Vec<RtpPayloadTap>,
40 pub depacketize_video: bool,
46}
47
48impl Default for ReceiverBatchOptions {
49 fn default() -> Self {
50 Self {
51 accept_corrupted: false,
52 raw_payload_routes: Vec::new(),
53 rtp_payload_taps: Vec::new(),
54 depacketize_video: true,
55 }
56 }
57}
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
66pub struct RtpPayloadTap {
67 pub route_id: PayloadRouteId,
69 pub payload_type: u8,
71}
72
73#[derive(Debug, Clone, PartialEq, Eq)]
75pub struct RoutePayload {
76 pub route_id: PayloadRouteId,
78 pub channel_id: ChannelId,
80 pub packet_seq: u64,
82 pub data: Vec<u8>,
84}
85
86#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
88pub struct ReceiverBatchCounters {
89 pub packets: usize,
91 pub accepted_packets: usize,
93 pub dropped_packets: usize,
95 pub crc_dropped: usize,
97 pub icv_dropped: usize,
99 pub report_dropped: usize,
101 pub ignored_frames: usize,
103 pub sessions: usize,
105 pub wfb_payloads: usize,
107 pub rtp_packets: usize,
109 pub video_frames: usize,
111 pub raw_payload_count: usize,
113 pub raw_payload_bytes: usize,
115 pub route_errors: usize,
117}
118
119#[derive(Debug, Clone, PartialEq, Eq)]
121pub struct ReceiverBatch {
122 pub frames: Vec<DepacketizedFrame>,
124 pub raw_payloads: Vec<RoutePayload>,
126 pub counters: ReceiverBatchCounters,
128 pub fec_counters: FecCounters,
130 pub rtp_status: RtpDepacketizerStatus,
132 pub rtp_reorder_status: RtpReorderStatus,
134}
135
136impl ReceiverRuntime {
137 pub fn from_routes(
142 routes: PayloadRouteManager,
143 video_runtime: PayloadRuntimeKey,
144 video_route_id: PayloadRouteId,
145 ) -> Self {
146 Self {
147 routes,
148 video_runtime,
149 video_route_id,
150 rtp: RtpDepacketizer::new(),
151 rtp_reorder: None,
152 }
153 }
154
155 pub fn with_plain_video_route(
159 frame_layout: FrameLayout,
160 video_route_id: PayloadRouteId,
161 channel_id: ChannelId,
162 key_slot: u64,
163 fec_k: usize,
164 fec_n: usize,
165 ) -> Result<Self, PayloadRouteError> {
166 let mut routes = PayloadRouteManager::new(frame_layout);
167 let video_runtime =
168 routes.add_plain_route(video_route_id, channel_id, key_slot, fec_k, fec_n)?;
169 Ok(Self::from_routes(routes, video_runtime, video_route_id))
170 }
171
172 pub fn with_keyed_video_route(
174 frame_layout: FrameLayout,
175 video_route_id: PayloadRouteId,
176 channel_id: ChannelId,
177 key_slot: u64,
178 keypair: WfbKeypair,
179 minimum_epoch: u64,
180 ) -> Result<Self, PayloadRouteError> {
181 let mut routes = PayloadRouteManager::new(frame_layout);
182 let video_runtime =
183 routes.add_keyed_route(video_route_id, channel_id, key_slot, keypair, minimum_epoch)?;
184 Ok(Self::from_routes(routes, video_runtime, video_route_id))
185 }
186
187 pub fn with_direct_video_route(
193 frame_layout: FrameLayout,
194 video_route_id: PayloadRouteId,
195 channel_id: ChannelId,
196 key_slot: u64,
197 ) -> Self {
198 let mut routes = PayloadRouteManager::new(frame_layout);
199 let video_runtime = routes.add_direct_route(video_route_id, channel_id, key_slot);
200 Self::from_routes(routes, video_runtime, video_route_id)
201 }
202
203 pub fn with_mock_video_route(
207 frame_layout: FrameLayout,
208 video_route_id: PayloadRouteId,
209 channel_id: ChannelId,
210 key_slot: u64,
211 ) -> Self {
212 Self::with_direct_video_route(frame_layout, video_route_id, channel_id, key_slot)
213 }
214
215 pub const fn video_runtime(&self) -> PayloadRuntimeKey {
217 self.video_runtime
218 }
219
220 pub const fn video_route_id(&self) -> PayloadRouteId {
222 self.video_route_id
223 }
224
225 pub fn routes(&self) -> &PayloadRouteManager {
227 &self.routes
228 }
229
230 pub fn routes_mut(&mut self) -> &mut PayloadRouteManager {
232 &mut self.routes
233 }
234
235 pub fn rtp_mut(&mut self) -> &mut RtpDepacketizer {
237 &mut self.rtp
238 }
239
240 pub fn rtp_status(&self) -> RtpDepacketizerStatus {
242 self.rtp.status()
243 }
244
245 pub fn rtp_reorder_status(&self) -> RtpReorderStatus {
247 self.rtp_reorder
248 .as_ref()
249 .map(RtpReorderBuffer::status)
250 .unwrap_or_default()
251 }
252
253 pub fn set_rtp_reorder_enabled(&mut self, enabled: bool) {
259 if enabled {
260 self.rtp_reorder
261 .get_or_insert_with(RtpReorderBuffer::default);
262 } else {
263 self.rtp_reorder = None;
264 }
265 }
266
267 pub const fn rtp_reorder_enabled(&self) -> bool {
269 self.rtp_reorder.is_some()
270 }
271
272 pub fn push_rtp_packet(
274 &mut self,
275 packet: &[u8],
276 ) -> Result<Vec<DepacketizedFrame>, crate::rtp::RtpError> {
277 let mut frames = Vec::new();
278 self.push_video_payload_into(packet, &mut frames)?;
279 Ok(frames)
280 }
281
282 fn push_video_payload_into(
283 &mut self,
284 payload: &[u8],
285 frames: &mut Vec<DepacketizedFrame>,
286 ) -> Result<usize, crate::rtp::RtpError> {
287 let before = frames.len();
288 if let Some(reorder) = self.rtp_reorder.as_mut() {
289 for ordered in reorder.push(payload)? {
290 if let Some(frame) = self.rtp.push(&ordered)? {
291 frames.push(frame);
292 }
293 }
294 } else if let Some(frame) = self.rtp.push(payload)? {
295 frames.push(frame);
296 }
297 Ok(frames.len() - before)
298 }
299
300 pub fn add_plain_route(
302 &mut self,
303 route_id: PayloadRouteId,
304 channel_id: ChannelId,
305 key_slot: u64,
306 fec_k: usize,
307 fec_n: usize,
308 ) -> Result<PayloadRuntimeKey, PayloadRouteError> {
309 self.routes
310 .add_plain_route(route_id, channel_id, key_slot, fec_k, fec_n)
311 }
312
313 pub fn add_keyed_route(
315 &mut self,
316 route_id: PayloadRouteId,
317 channel_id: ChannelId,
318 key_slot: u64,
319 keypair: WfbKeypair,
320 minimum_epoch: u64,
321 ) -> Result<PayloadRuntimeKey, PayloadRouteError> {
322 self.routes
323 .add_keyed_route(route_id, channel_id, key_slot, keypair, minimum_epoch)
324 }
325
326 pub fn add_direct_route(
328 &mut self,
329 route_id: PayloadRouteId,
330 channel_id: ChannelId,
331 key_slot: u64,
332 ) -> PayloadRuntimeKey {
333 self.routes.add_direct_route(route_id, channel_id, key_slot)
334 }
335
336 pub fn add_mock_route(
340 &mut self,
341 route_id: PayloadRouteId,
342 channel_id: ChannelId,
343 key_slot: u64,
344 ) -> PayloadRuntimeKey {
345 self.add_direct_route(route_id, channel_id, key_slot)
346 }
347
348 pub fn video_fec_counters(&self) -> FecCounters {
350 self.routes
351 .fec_counters(self.video_runtime)
352 .unwrap_or_default()
353 }
354
355 pub fn accepts_video_frame(&self, frame: &[u8]) -> bool {
357 self.routes.accepts_80211_frame(self.video_runtime, frame)
358 }
359
360 pub fn push_rx_transfer(
362 &mut self,
363 transfer: &[u8],
364 options: &ReceiverBatchOptions,
365 ) -> Result<ReceiverBatch, AggregateError> {
366 let packets = parse_rx_aggregate(transfer)?;
367 Ok(self.push_rx_packets(packets, options))
368 }
369
370 pub fn push_rx_transfer_with_kind(
375 &mut self,
376 transfer: &[u8],
377 descriptor_kind: RxDescriptorKind,
378 options: &ReceiverBatchOptions,
379 ) -> Result<ReceiverBatch, AggregateError> {
380 let packets = parse_rx_aggregate_with_kind(transfer, descriptor_kind)?;
381 Ok(self.push_rx_packets(packets, options))
382 }
383
384 pub fn push_rx_packets<'a>(
386 &mut self,
387 packets: impl IntoIterator<Item = RealtekRxPacket<'a>>,
388 options: &ReceiverBatchOptions,
389 ) -> ReceiverBatch {
390 let mut batch = self.empty_batch();
391
392 for packet in packets {
393 batch.counters.packets += 1;
394 if packet.attrib.crc_err && !options.accept_corrupted {
395 batch.counters.crc_dropped += 1;
396 continue;
397 }
398 if packet.attrib.icv_err && !options.accept_corrupted {
399 batch.counters.icv_dropped += 1;
400 continue;
401 }
402 if packet.attrib.pkt_rpt_type != RxPacketType::NormalRx {
403 batch.counters.report_dropped += 1;
404 continue;
405 }
406
407 batch.counters.accepted_packets += 1;
408 match self.routes.push_80211_frame(packet.data) {
409 Ok(events) => self.apply_route_events(events, options, &mut batch),
410 Err(_) => {
411 batch.counters.ignored_frames += 1;
412 batch.counters.route_errors += 1;
413 }
414 }
415 }
416
417 batch.counters.dropped_packets =
418 batch.counters.crc_dropped + batch.counters.icv_dropped + batch.counters.report_dropped;
419 batch.fec_counters = self.video_fec_counters();
420 batch
421 }
422
423 pub fn push_80211_frame(
425 &mut self,
426 frame: &[u8],
427 options: &ReceiverBatchOptions,
428 ) -> Result<ReceiverBatch, PayloadRouteError> {
429 let mut batch = self.empty_batch();
430 let events = self.routes.push_80211_frame(frame)?;
431 self.apply_route_events(events, options, &mut batch);
432 batch.fec_counters = self.video_fec_counters();
433 Ok(batch)
434 }
435
436 pub fn push_decrypted_80211_frame(
438 &mut self,
439 runtime: PayloadRuntimeKey,
440 frame: &[u8],
441 decrypted_fragment: &[u8],
442 options: &ReceiverBatchOptions,
443 ) -> Result<ReceiverBatch, PayloadRouteError> {
444 let mut batch = self.empty_batch();
445 let events = self
446 .routes
447 .push_decrypted_80211_frame(runtime, frame, decrypted_fragment)?;
448 self.apply_route_events(events, options, &mut batch);
449 batch.fec_counters = self.video_fec_counters();
450 Ok(batch)
451 }
452
453 pub fn push_decrypted_fragment(
455 &mut self,
456 runtime: PayloadRuntimeKey,
457 data_nonce: u64,
458 decrypted_fragment: &[u8],
459 options: &ReceiverBatchOptions,
460 ) -> Result<ReceiverBatch, PayloadRouteError> {
461 let mut batch = self.empty_batch();
462 let events =
463 self.routes
464 .push_decrypted_fragment(runtime, data_nonce, decrypted_fragment)?;
465 self.apply_route_events(events, options, &mut batch);
466 batch.fec_counters = self.video_fec_counters();
467 Ok(batch)
468 }
469
470 pub fn push_direct_payload(
472 &mut self,
473 runtime: PayloadRuntimeKey,
474 packet_seq: u64,
475 payload: &[u8],
476 options: &ReceiverBatchOptions,
477 ) -> Result<ReceiverBatch, PayloadRouteError> {
478 let mut batch = self.empty_batch();
479 let events = self
480 .routes
481 .push_direct_payload(runtime, packet_seq, payload)?;
482 self.apply_route_events(events, options, &mut batch);
483 batch.fec_counters = self.video_fec_counters();
484 Ok(batch)
485 }
486
487 pub fn push_mock_payload(
491 &mut self,
492 runtime: PayloadRuntimeKey,
493 packet_seq: u64,
494 payload: &[u8],
495 options: &ReceiverBatchOptions,
496 ) -> Result<ReceiverBatch, PayloadRouteError> {
497 self.push_direct_payload(runtime, packet_seq, payload, options)
498 }
499
500 fn empty_batch(&self) -> ReceiverBatch {
501 ReceiverBatch {
502 frames: Vec::new(),
503 raw_payloads: Vec::new(),
504 counters: ReceiverBatchCounters::default(),
505 fec_counters: self.video_fec_counters(),
506 rtp_status: self.rtp_status(),
507 rtp_reorder_status: self.rtp_reorder_status(),
508 }
509 }
510
511 fn apply_route_events(
512 &mut self,
513 events: Vec<PayloadRouteEvent>,
514 options: &ReceiverBatchOptions,
515 batch: &mut ReceiverBatch,
516 ) {
517 for event in events {
518 match event {
519 PayloadRouteEvent::IgnoredFrame => batch.counters.ignored_frames += 1,
520 PayloadRouteEvent::SessionEstablished { .. } => batch.counters.sessions += 1,
521 PayloadRouteEvent::Payload {
522 route_ids, payload, ..
523 } => {
524 if route_ids.contains(&self.video_route_id) {
525 batch.counters.wfb_payloads += 1;
526 batch.counters.rtp_packets += 1;
527 if options.depacketize_video {
528 if let Ok(frames) =
529 self.push_video_payload_into(&payload.data, &mut batch.frames)
530 {
531 batch.counters.video_frames += frames;
532 }
533 }
534 }
535
536 for &route_id in &route_ids {
537 if !options.raw_payload_routes.contains(&route_id) {
538 continue;
539 }
540 copy_raw_payload(route_id, &payload, batch);
541 }
542
543 if options.rtp_payload_taps.is_empty() {
544 continue;
545 }
546 let Ok(header) = RtpHeader::parse(&payload.data) else {
547 continue;
548 };
549 for tap in &options.rtp_payload_taps {
550 if header.payload_type != tap.payload_type {
551 continue;
552 }
553 if !route_ids.contains(&tap.route_id) {
554 continue;
555 }
556 copy_raw_payload(tap.route_id, &payload, batch);
557 }
558 }
559 }
560 }
561 batch.rtp_status = self.rtp_status();
562 batch.rtp_reorder_status = self.rtp_reorder_status();
563 }
564}
565
566fn copy_raw_payload(
567 route_id: PayloadRouteId,
568 payload: &crate::pipeline::RecoveredPayload,
569 batch: &mut ReceiverBatch,
570) {
571 batch.counters.raw_payload_count += 1;
572 batch.counters.raw_payload_bytes += payload.data.len();
573 batch.raw_payloads.push(RoutePayload {
574 route_id,
575 channel_id: payload.channel_id,
576 packet_seq: payload.packet_seq,
577 data: payload.data.clone(),
578 });
579}
580
581#[cfg(test)]
582mod tests {
583 use super::*;
584 use crate::RadioPort;
585
586 fn plain(payload: &[u8]) -> Vec<u8> {
587 let mut out = Vec::new();
588 out.push(0);
589 out.extend_from_slice(&(payload.len() as u16).to_be_bytes());
590 out.extend_from_slice(payload);
591 out
592 }
593
594 fn rtp(payload_type: u8, payload: &[u8]) -> Vec<u8> {
595 let mut packet = vec![0x80, payload_type & 0x7f];
596 packet.extend_from_slice(&7u16.to_be_bytes());
597 packet.extend_from_slice(&48_000u32.to_be_bytes());
598 packet.extend_from_slice(&0x1122_3344u32.to_be_bytes());
599 packet.extend_from_slice(payload);
600 packet
601 }
602
603 fn h264_stap_a_rtp() -> Vec<u8> {
604 let sps = [0x67, 0x42, 0x00, 0x1e, 0xab];
605 let pps = [0x68, 0xce, 0x06, 0xe2];
606 let idr = [0x65, 0x88, 0x84, 0x21];
607 let mut payload = vec![24];
608 for nalu in [&sps[..], &pps[..], &idr[..]] {
609 payload.extend_from_slice(&(nalu.len() as u16).to_be_bytes());
610 payload.extend_from_slice(nalu);
611 }
612 let mut packet = rtp(crate::rtp::RTP_PAYLOAD_TYPE_H264, &payload);
613 packet[1] |= 0x80;
614 packet
615 }
616
617 #[test]
618 fn decrypted_fragment_can_fan_out_to_raw_route() {
619 let route = PayloadRouteId::new(7);
620 let mut runtime = ReceiverRuntime::with_plain_video_route(
621 FrameLayout::WithFcs,
622 route,
623 ChannelId::default_video(),
624 0,
625 1,
626 1,
627 )
628 .unwrap();
629 let batch = runtime
630 .push_decrypted_fragment(
631 runtime.video_runtime(),
632 0,
633 &plain(b"payload bytes"),
634 &ReceiverBatchOptions {
635 raw_payload_routes: vec![route],
636 ..ReceiverBatchOptions::default()
637 },
638 )
639 .unwrap();
640
641 assert_eq!(batch.counters.wfb_payloads, 1);
642 assert_eq!(batch.counters.raw_payload_count, 1);
643 assert_eq!(batch.raw_payloads[0].data, b"payload bytes");
644 }
645
646 #[test]
647 fn rtp_payload_tap_copies_only_matching_payload_type() {
648 let video_route = PayloadRouteId::new(1);
649 let audio_route = PayloadRouteId::new(3);
650 let mut runtime = ReceiverRuntime::with_plain_video_route(
651 FrameLayout::WithFcs,
652 video_route,
653 ChannelId::default_video(),
654 0,
655 1,
656 1,
657 )
658 .unwrap();
659 runtime
660 .add_plain_route(audio_route, ChannelId::default_video(), 0, 1, 1)
661 .unwrap();
662
663 let ignored = runtime
664 .push_decrypted_fragment(
665 runtime.video_runtime(),
666 0,
667 &plain(&rtp(crate::rtp::RTP_PAYLOAD_TYPE_H264, b"video")),
668 &ReceiverBatchOptions {
669 rtp_payload_taps: vec![RtpPayloadTap {
670 route_id: audio_route,
671 payload_type: crate::rtp::RTP_PAYLOAD_TYPE_OPUS,
672 }],
673 ..ReceiverBatchOptions::default()
674 },
675 )
676 .unwrap();
677 assert_eq!(ignored.counters.raw_payload_count, 0);
678
679 let packet = rtp(crate::rtp::RTP_PAYLOAD_TYPE_OPUS, b"opus");
680 let batch = runtime
681 .push_decrypted_fragment(
682 runtime.video_runtime(),
683 1 << 8,
684 &plain(&packet),
685 &ReceiverBatchOptions {
686 rtp_payload_taps: vec![RtpPayloadTap {
687 route_id: audio_route,
688 payload_type: crate::rtp::RTP_PAYLOAD_TYPE_OPUS,
689 }],
690 ..ReceiverBatchOptions::default()
691 },
692 )
693 .unwrap();
694
695 assert_eq!(batch.counters.raw_payload_count, 1);
696 assert_eq!(batch.raw_payloads[0].route_id, audio_route);
697 assert_eq!(batch.raw_payloads[0].data, packet);
698 }
699
700 #[test]
701 fn rtp_reorder_is_opt_in() {
702 let mut runtime = ReceiverRuntime::with_plain_video_route(
703 FrameLayout::WithFcs,
704 PayloadRouteId::new(1),
705 ChannelId::default_video(),
706 0,
707 1,
708 1,
709 )
710 .unwrap();
711
712 assert!(!runtime.rtp_reorder_enabled());
713 assert_eq!(runtime.rtp_reorder_status(), RtpReorderStatus::default());
714
715 runtime.set_rtp_reorder_enabled(true);
716 assert!(runtime.rtp_reorder_enabled());
717
718 runtime.set_rtp_reorder_enabled(false);
719 assert!(!runtime.rtp_reorder_enabled());
720 assert_eq!(runtime.rtp_reorder_status(), RtpReorderStatus::default());
721 }
722
723 #[test]
724 fn auxiliary_route_does_not_count_as_video_payload() {
725 let video_route = PayloadRouteId::new(1);
726 let data_route = PayloadRouteId::new(2);
727 let mut runtime = ReceiverRuntime::with_plain_video_route(
728 FrameLayout::WithFcs,
729 video_route,
730 ChannelId::default_video(),
731 0,
732 1,
733 1,
734 )
735 .unwrap();
736 let data_runtime = runtime
737 .add_plain_route(
738 data_route,
739 ChannelId::from_link_port(crate::channel::DEFAULT_LINK_ID, RadioPort::TunnelRx),
740 0,
741 1,
742 1,
743 )
744 .unwrap();
745 let batch = runtime
746 .push_decrypted_fragment(
747 data_runtime,
748 0,
749 &plain(b"data bytes"),
750 &ReceiverBatchOptions {
751 raw_payload_routes: vec![data_route],
752 ..ReceiverBatchOptions::default()
753 },
754 )
755 .unwrap();
756
757 assert_eq!(batch.counters.wfb_payloads, 0);
758 assert_eq!(batch.counters.rtp_packets, 0);
759 assert_eq!(batch.counters.raw_payload_count, 1);
760 assert_eq!(batch.raw_payloads[0].data, b"data bytes");
761 }
762
763 #[test]
764 fn direct_payload_runtime_uses_same_video_route_and_rtp_depacketizer() {
765 let video_route = PayloadRouteId::new(1);
766 let mut runtime = ReceiverRuntime::with_direct_video_route(
767 FrameLayout::WithFcs,
768 video_route,
769 ChannelId::default_video(),
770 0,
771 );
772
773 let packet = h264_stap_a_rtp();
774 let batch = runtime
775 .push_direct_payload(
776 runtime.video_runtime(),
777 123,
778 &packet,
779 &ReceiverBatchOptions {
780 raw_payload_routes: vec![video_route],
781 ..ReceiverBatchOptions::default()
782 },
783 )
784 .unwrap();
785
786 assert_eq!(batch.counters.wfb_payloads, 1);
787 assert_eq!(batch.counters.rtp_packets, 1);
788 assert_eq!(batch.counters.video_frames, 1);
789 assert_eq!(batch.frames.len(), 1);
790 assert_eq!(batch.frames[0].codec, crate::rtp::Codec::H264);
791 assert!(batch.frames[0].is_keyframe);
792 assert_eq!(batch.raw_payloads[0].data, packet);
793 assert_eq!(batch.fec_counters, FecCounters::default());
794 }
795
796 #[test]
797 fn video_depacketization_can_be_delegated_without_losing_raw_rtp() {
798 let video_route = PayloadRouteId::new(1);
799 let mut runtime = ReceiverRuntime::with_direct_video_route(
800 FrameLayout::WithFcs,
801 video_route,
802 ChannelId::default_video(),
803 0,
804 );
805 let packet = h264_stap_a_rtp();
806
807 let batch = runtime
808 .push_direct_payload(
809 runtime.video_runtime(),
810 123,
811 &packet,
812 &ReceiverBatchOptions {
813 raw_payload_routes: vec![video_route],
814 depacketize_video: false,
815 ..ReceiverBatchOptions::default()
816 },
817 )
818 .unwrap();
819
820 assert_eq!(batch.counters.wfb_payloads, 1);
821 assert_eq!(batch.counters.rtp_packets, 1);
822 assert_eq!(batch.counters.video_frames, 0);
823 assert!(batch.frames.is_empty());
824 assert_eq!(batch.raw_payloads[0].data, packet);
825 assert_eq!(batch.rtp_status, RtpDepacketizerStatus::default());
826 }
827
828 #[test]
829 fn rx_transfer_accepts_explicit_jaguar3_descriptor_layout() {
830 let mut runtime = ReceiverRuntime::with_plain_video_route(
831 FrameLayout::WithFcs,
832 PayloadRouteId::new(1),
833 ChannelId::default_video(),
834 0,
835 1,
836 1,
837 )
838 .unwrap();
839 let mut transfer = vec![0u8; 32];
840 transfer[..4].copy_from_slice(&8u32.to_le_bytes());
841 transfer[24..32].copy_from_slice(&[0x08, 0, 0, 0, 0, 0, 0, 0]);
842
843 let batch = runtime
844 .push_rx_transfer_with_kind(
845 &transfer,
846 RxDescriptorKind::Jaguar3,
847 &ReceiverBatchOptions::default(),
848 )
849 .unwrap();
850
851 assert_eq!(batch.counters.packets, 1);
852 assert_eq!(batch.counters.accepted_packets, 1);
853 assert_eq!(batch.counters.ignored_frames, 1);
854 }
855}