use bytes::{Buf, Bytes, BytesMut};
use super::{
RtcpApplicationDefined, RtcpExtendedReport, RtcpGoodbye, RtcpPacket, RtcpReceiverReport,
RtcpSenderReport, RtcpSourceDescription,
};
use crate::error::Error;
use crate::Result;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RtcpCompoundPacket {
pub packets: Vec<RtcpPacket>,
}
impl RtcpCompoundPacket {
pub fn new_with_sr(sr: RtcpSenderReport) -> Self {
let mut packets = Vec::new();
packets.push(RtcpPacket::SenderReport(sr));
Self { packets }
}
pub fn new_with_rr(rr: RtcpReceiverReport) -> Self {
let mut packets = Vec::new();
packets.push(RtcpPacket::ReceiverReport(rr));
Self { packets }
}
pub fn get_sr(&self) -> Option<&RtcpSenderReport> {
if let Some(RtcpPacket::SenderReport(sr)) = self.packets.first() {
Some(sr)
} else {
None
}
}
pub fn get_rr(&self) -> Option<&RtcpReceiverReport> {
if let Some(RtcpPacket::ReceiverReport(rr)) = self.packets.first() {
Some(rr)
} else {
None
}
}
pub fn add_sdes(&mut self, sdes: RtcpSourceDescription) {
self.packets.push(RtcpPacket::SourceDescription(sdes));
}
pub fn add_bye(&mut self, bye: RtcpGoodbye) {
self.packets.push(RtcpPacket::Goodbye(bye));
}
pub fn add_app(&mut self, app: RtcpApplicationDefined) {
self.packets.push(RtcpPacket::ApplicationDefined(app));
}
pub fn add_xr(&mut self, xr: RtcpExtendedReport) {
self.packets.push(RtcpPacket::ExtendedReport(xr));
}
pub fn validate(&self) -> Result<()> {
if self.packets.is_empty() {
return Err(Error::RtcpError(
"Compound packet must contain at least one packet".to_string(),
));
}
match self.packets[0] {
RtcpPacket::SenderReport(_) | RtcpPacket::ReceiverReport(_) => Ok(()),
_ => Err(Error::RtcpError(
"First packet in compound packet must be SR or RR".to_string(),
)),
}
}
pub fn serialize(&self) -> Result<Bytes> {
self.validate()?;
let mut buf = BytesMut::new();
for packet in &self.packets {
let packet_bytes = packet.serialize()?;
buf.extend_from_slice(&packet_bytes);
}
Ok(buf.freeze())
}
pub fn parse(data: &[u8]) -> Result<Self> {
let mut buf = Bytes::copy_from_slice(data);
let mut packets = Vec::new();
while buf.remaining() >= 4 {
if buf.remaining() < 4 {
break;
}
let mut peek_buf = buf.clone();
let _first_byte = peek_buf.get_u8();
let _packet_type_byte = peek_buf.get_u8();
let length = peek_buf.get_u16() as usize * 4;
let total_packet_size = 4 + length;
if buf.remaining() < total_packet_size {
return Err(Error::BufferTooSmall {
required: total_packet_size,
available: buf.remaining(),
});
}
let packet_data = &buf[0..total_packet_size];
let packet = RtcpPacket::parse(packet_data)?;
packets.push(packet);
buf.advance(total_packet_size);
}
let compound = Self { packets };
compound.validate()?;
Ok(compound)
}
}
#[cfg(test)]
mod tests {
use super::super::NtpTimestamp;
use super::*;
#[test]
fn test_compound_packet_validation() {
let sr = RtcpSenderReport {
ssrc: 0x12345678,
ntp_timestamp: NtpTimestamp {
seconds: 0,
fraction: 0,
},
rtp_timestamp: 0,
sender_packet_count: 0,
sender_octet_count: 0,
report_blocks: Vec::new(),
};
let compound = RtcpCompoundPacket::new_with_sr(sr);
assert!(compound.validate().is_ok());
let rr = RtcpReceiverReport {
ssrc: 0x12345678,
report_blocks: Vec::new(),
};
let compound = RtcpCompoundPacket::new_with_rr(rr);
assert!(compound.validate().is_ok());
let invalid = RtcpCompoundPacket {
packets: Vec::new(),
};
assert!(invalid.validate().is_err());
let bye = RtcpGoodbye {
sources: vec![0x12345678],
reason: None,
};
let mut invalid = RtcpCompoundPacket {
packets: Vec::new(),
};
invalid.add_bye(bye);
assert!(invalid.validate().is_err());
}
#[test]
fn test_compound_packet_serialize_parse() {
let sr = RtcpSenderReport {
ssrc: 0x12345678,
ntp_timestamp: NtpTimestamp {
seconds: 1234,
fraction: 5678,
},
rtp_timestamp: 0x87654321,
sender_packet_count: 100,
sender_octet_count: 12345,
report_blocks: Vec::new(),
};
let bye = RtcpGoodbye {
sources: vec![0x12345678],
reason: Some("Goodbye".to_string()),
};
let mut compound = RtcpCompoundPacket::new_with_sr(sr);
compound.add_bye(bye);
let bytes = compound.serialize().unwrap();
let parsed = RtcpCompoundPacket::parse(&bytes).unwrap();
assert_eq!(parsed.packets.len(), 2);
match &parsed.packets[0] {
RtcpPacket::SenderReport(sr) => {
assert_eq!(sr.ssrc, 0x12345678);
assert_eq!(sr.ntp_timestamp.seconds, 1234);
assert_eq!(sr.rtp_timestamp, 0x87654321);
}
_ => panic!("Expected SR packet"),
}
match &parsed.packets[1] {
RtcpPacket::Goodbye(bye) => {
assert_eq!(bye.sources.len(), 1);
assert_eq!(bye.sources[0], 0x12345678);
assert_eq!(bye.reason.as_deref(), Some("Goodbye"));
}
_ => panic!("Expected BYE packet"),
}
}
}