use bytes::{Buf, BufMut, Bytes, BytesMut};
use std::str;
use crate::error::Error;
use crate::{Result, RtpSsrc};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RtcpApplicationDefined {
pub ssrc: RtpSsrc,
pub name: [u8; 4],
pub data: Bytes,
}
impl RtcpApplicationDefined {
pub fn new(ssrc: RtpSsrc, name: [u8; 4]) -> Self {
Self {
ssrc,
name,
data: Bytes::new(),
}
}
pub fn new_with_name(ssrc: RtpSsrc, name_str: &str) -> Result<Self> {
if name_str.len() != 4 {
return Err(Error::InvalidParameter(format!(
"APP name must be exactly 4 characters, got {}",
name_str.len()
)));
}
let mut name = [0; 4];
name.copy_from_slice(name_str.as_bytes());
Ok(Self::new(ssrc, name))
}
pub fn new_with_data(ssrc: RtpSsrc, name: [u8; 4], data: Bytes) -> Self {
Self { ssrc, name, data }
}
pub fn name_str(&self) -> String {
String::from_utf8_lossy(&self.name).to_string()
}
pub fn set_data(&mut self, data: Bytes) {
self.data = data;
}
pub fn size(&self) -> usize {
let mut size = 8;
if !self.data.is_empty() {
size += self.data.len();
let padding = (4 - (self.data.len() % 4)) % 4;
size += padding;
}
size
}
pub fn serialize(&self) -> Result<BytesMut> {
let mut buf = BytesMut::with_capacity(self.size());
buf.put_u32(self.ssrc);
buf.put_slice(&self.name);
if !self.data.is_empty() {
buf.put_slice(&self.data);
let padding_bytes = (4 - (self.data.len() % 4)) % 4;
for _ in 0..padding_bytes {
buf.put_u8(0);
}
}
Ok(buf)
}
}
pub fn parse_app(buf: &mut impl Buf) -> Result<RtcpApplicationDefined> {
if buf.remaining() < 8 {
return Err(Error::BufferTooSmall {
required: 8,
available: buf.remaining(),
});
}
let ssrc = buf.get_u32();
let mut name = [0u8; 4];
buf.copy_to_slice(&mut name);
let data = if buf.has_remaining() {
let data_bytes = buf.copy_to_bytes(buf.remaining());
data_bytes
} else {
Bytes::new()
};
Ok(RtcpApplicationDefined { ssrc, name, data })
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_app_creation() {
let app = RtcpApplicationDefined::new(0x12345678, *b"TEST");
assert_eq!(app.ssrc, 0x12345678);
assert_eq!(app.name, *b"TEST");
assert!(app.data.is_empty());
let app = RtcpApplicationDefined::new_with_name(0x12345678, "TEST").unwrap();
assert_eq!(app.ssrc, 0x12345678);
assert_eq!(app.name, *b"TEST");
assert!(app.data.is_empty());
let result = RtcpApplicationDefined::new_with_name(0x12345678, "TOOLONG");
assert!(result.is_err());
let result = RtcpApplicationDefined::new_with_name(0x12345678, "ABC");
assert!(result.is_err());
}
#[test]
fn test_app_with_data() {
let data = Bytes::from_static(b"application data");
let app = RtcpApplicationDefined::new_with_data(0x12345678, *b"TEST", data.clone());
assert_eq!(app.ssrc, 0x12345678);
assert_eq!(app.name, *b"TEST");
assert_eq!(app.data, data);
}
#[test]
fn test_name_string() {
let app = RtcpApplicationDefined::new(0x12345678, *b"TEST");
assert_eq!(app.name_str(), "TEST");
}
#[test]
fn test_set_data() {
let mut app = RtcpApplicationDefined::new(0x12345678, *b"TEST");
let data = Bytes::from_static(b"application data");
app.set_data(data.clone());
assert_eq!(app.data, data);
}
#[test]
fn test_size_calculation() {
let app = RtcpApplicationDefined::new(0x12345678, *b"TEST");
assert_eq!(app.size(), 8);
let data = Bytes::from_static(b"test");
let app = RtcpApplicationDefined::new_with_data(0x12345678, *b"TEST", data);
assert_eq!(app.size(), 12);
let data = Bytes::from_static(b"test data");
let app = RtcpApplicationDefined::new_with_data(0x12345678, *b"TEST", data);
assert_eq!(app.size(), 20); }
#[test]
fn test_serialize_parse() {
let original = RtcpApplicationDefined::new(0x12345678, *b"TEST");
let serialized = original.serialize().unwrap();
let parsed = parse_app(&mut serialized.freeze()).unwrap();
assert_eq!(parsed.ssrc, original.ssrc);
assert_eq!(parsed.name, original.name);
assert!(parsed.data.is_empty());
let data = Bytes::from_static(b"test data that needs padding");
let original = RtcpApplicationDefined::new_with_data(0x12345678, *b"TEST", data);
let serialized = original.serialize().unwrap();
let parsed = parse_app(&mut serialized.freeze()).unwrap();
assert_eq!(parsed.ssrc, original.ssrc);
assert_eq!(parsed.name, original.name);
assert!(parsed.data.starts_with(b"test data that needs padding"));
}
}