Skip to content
File

Blob: firmware/vendor/str0m/src/rtp/rtcp/rtcpfb.rs

rust176 lines
1use super::{AppSpecificFeedback, DlrrItem, FirEntry, NackEntry, ReceptionReport, Remb};
2use super::{ReportBlock, ReportList, Rrtr, Rtcp, Sdes, SenderInfo, Ssrc, Twcc};
3 
4/// Normalization of [`Rtcp`] so we can deal with one SSRC at a time.
5#[allow(clippy::large_enum_variant)]
6#[derive(Debug)]
7pub enum RtcpFb {
8 SenderInfo(SenderInfo), // tx -> rx
9 ReceptionReport(ReceptionReport), // rx -> tx
10 DlrrItem(DlrrItem), // rx <- tx
11 Rrtr((Rrtr, Ssrc)), // rx -> tx
12 SourceDescription(Sdes), // tx -> rx
13 Goodbye(Ssrc), // tx -> rx
14 Nack(Ssrc, ReportList<NackEntry>), // rx -> tx
15 Pli(Ssrc), // rx -> tx
16 Fir(FirEntry), // rx -> tx
17 Twcc(Twcc), // rx -> tx
18 Remb(Remb), // rx -> tx
19 AppSpecificFeedback(AppSpecificFeedback), // not stream-routed
20}
21 
22impl RtcpFb {
23 pub fn is_for_rx(&self) -> bool {
24 matches!(
25 self,
26 RtcpFb::SenderInfo(_)
27 | RtcpFb::SourceDescription(_)
28 | RtcpFb::Goodbye(_)
29 | RtcpFb::DlrrItem(_)
30 )
31 }
32 
33 pub fn from_rtcp<T: IntoIterator<Item = Rtcp>>(t: T) -> impl Iterator<Item = RtcpFb> {
34 let mut q = Vec::new();
35 let iter = t.into_iter();
36 for pkt in iter {
37 match pkt {
38 Rtcp::SenderReport(v) => {
39 q.push(RtcpFb::SenderInfo(v.sender_info));
40 q.extend(v.reports.into_iter().map(RtcpFb::ReceptionReport));
41 }
42 Rtcp::ReceiverReport(v) => {
43 q.extend(v.reports.into_iter().map(RtcpFb::ReceptionReport));
44 }
45 Rtcp::ExtendedReport(v) => {
46 for block in v.blocks {
47 match block {
48 ReportBlock::Rrtr(b) => q.push(RtcpFb::Rrtr((b, v.ssrc))),
49 ReportBlock::Dlrr(v) => {
50 q.extend(v.items.iter().map(|i| RtcpFb::DlrrItem(*i)))
51 }
52 }
53 }
54 }
55 Rtcp::SourceDescription(v) => {
56 q.extend(v.reports.into_iter().map(RtcpFb::SourceDescription));
57 }
58 Rtcp::Goodbye(v) => {
59 q.extend(v.reports.into_iter().map(RtcpFb::Goodbye));
60 }
61 Rtcp::Nack(v) => {
62 q.push(RtcpFb::Nack(v.ssrc, v.reports));
63 }
64 Rtcp::Pli(v) => {
65 q.push(RtcpFb::Pli(v.ssrc));
66 }
67 Rtcp::Fir(v) => {
68 q.extend(v.reports.into_iter().map(RtcpFb::Fir));
69 }
70 Rtcp::Twcc(v) => {
71 q.push(RtcpFb::Twcc(v));
72 }
73 Rtcp::Remb(v) => {
74 q.push(RtcpFb::Remb(v));
75 }
76 Rtcp::AppSpecificFeedback(v) => {
77 q.push(RtcpFb::AppSpecificFeedback(v));
78 }
79 }
80 }
81 q.into_iter()
82 }
83 
84 pub fn ssrc(&self) -> Ssrc {
85 match self {
86 RtcpFb::SenderInfo(v) => v.ssrc,
87 RtcpFb::ReceptionReport(v) => v.ssrc,
88 RtcpFb::DlrrItem(v) => v.ssrc,
89 RtcpFb::Rrtr((_, ssrc)) => *ssrc,
90 RtcpFb::SourceDescription(v) => v.ssrc,
91 RtcpFb::Goodbye(v) => *v,
92 RtcpFb::Nack(v, _) => *v,
93 RtcpFb::Pli(v) => *v,
94 RtcpFb::Fir(v) => v.ssrc,
95 RtcpFb::Twcc(v) => v.ssrc,
96 RtcpFb::Remb(v) => v.ssrcs.first().map(|ssrc| (*ssrc).into()).unwrap_or(v.ssrc),
97 RtcpFb::AppSpecificFeedback(v) => v.media_ssrc,
98 }
99 }
100}
101 
102#[cfg(test)]
103mod tests {
104 use super::super::AppSpecificFeedback;
105 use super::*;
106 use std::collections::VecDeque;
107 
108 #[test]
109 fn app_specific_feedback_passes_through_rtcpfb() {
110 let fb = AppSpecificFeedback {
111 sender_ssrc: 100.into(),
112 media_ssrc: 200.into(),
113 payload: vec![0xDE, 0xAD, 0xBE, 0xEF].into(),
114 };
115 
116 let rtcp_items = vec![Rtcp::AppSpecificFeedback(fb)];
117 let result: Vec<_> = RtcpFb::from_rtcp(rtcp_items).collect();
118 
119 assert_eq!(result.len(), 1);
120 assert!(matches!(&result[0], RtcpFb::AppSpecificFeedback(v) if v.media_ssrc == 200.into()));
121 }
122 
123 #[test]
124 fn app_specific_feedback_write_and_parse_round_trip() {
125 use crate::rtp_::RtcpPacket;
126 
127 let fb = AppSpecificFeedback {
128 sender_ssrc: 1001.into(),
129 media_ssrc: 2002.into(),
130 payload: vec![0x01, 0x00, 0x00, 0x44, 0xAA, 0xBB, 0xCC, 0xDD].into(),
131 };
132 
133 // Write the RTCP packet
134 let rtcp = Rtcp::AppSpecificFeedback(fb.clone());
135 let mut buf = [0u8; 256];
136 let written = rtcp.write_to(&mut buf);
137 
138 // Parse it back via the Rtcp parser
139 let mut parsed = VecDeque::new();
140 Rtcp::read_packet(&buf[..written], &mut parsed);
141 
142 assert_eq!(parsed.len(), 1);
143 match parsed.pop_front().unwrap() {
144 Rtcp::AppSpecificFeedback(parsed_fb) => {
145 assert_eq!(parsed_fb.sender_ssrc, fb.sender_ssrc);
146 assert_eq!(parsed_fb.media_ssrc, fb.media_ssrc);
147 assert_eq!(parsed_fb.payload, fb.payload);
148 }
149 _ => panic!("Expected AppSpecificFeedback"),
150 }
151 }
152 
153 #[test]
154 fn app_specific_feedback_not_confused_with_remb() {
155 use crate::rtp_::RtcpPacket;
156 
157 // A non-REMB FMT=15 payload should parse as AppSpecificFeedback, not Remb
158 let fb = AppSpecificFeedback {
159 sender_ssrc: 42.into(),
160 media_ssrc: 0.into(),
161 // Payload that does NOT start with "REMB" magic bytes
162 payload: vec![0x01, 0x02, 0x03, 0x04].into(),
163 };
164 
165 let rtcp = Rtcp::AppSpecificFeedback(fb);
166 let mut buf = [0u8; 256];
167 let written = rtcp.write_to(&mut buf);
168 
169 let mut parsed = VecDeque::new();
170 Rtcp::read_packet(&buf[..written], &mut parsed);
171 
172 assert_eq!(parsed.len(), 1);
173 assert!(matches!(parsed[0], Rtcp::AppSpecificFeedback(_)));
174 }
175}