use crate::error::Error;
use crate::{Result, RtpSsrc};
use bytes::{BufMut, BytesMut};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum RtcpSdesItemType {
End = 0,
CName = 1,
Name = 2,
Email = 3,
Phone = 4,
Location = 5,
Tool = 6,
Note = 7,
Private = 8,
}
impl TryFrom<u8> for RtcpSdesItemType {
type Error = Error;
fn try_from(value: u8) -> Result<Self> {
match value {
0 => Ok(RtcpSdesItemType::End),
1 => Ok(RtcpSdesItemType::CName),
2 => Ok(RtcpSdesItemType::Name),
3 => Ok(RtcpSdesItemType::Email),
4 => Ok(RtcpSdesItemType::Phone),
5 => Ok(RtcpSdesItemType::Location),
6 => Ok(RtcpSdesItemType::Tool),
7 => Ok(RtcpSdesItemType::Note),
8 => Ok(RtcpSdesItemType::Private),
_ => Err(Error::RtcpError(format!(
"Unknown SDES item type: {}",
value
))),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RtcpSdesItem {
pub item_type: RtcpSdesItemType,
pub value: String,
}
impl RtcpSdesItem {
pub fn new(item_type: RtcpSdesItemType, value: String) -> Self {
Self { item_type, value }
}
pub fn cname(value: String) -> Self {
Self::new(RtcpSdesItemType::CName, value)
}
pub fn name(value: String) -> Self {
Self::new(RtcpSdesItemType::Name, value)
}
pub fn tool(value: String) -> Self {
Self::new(RtcpSdesItemType::Tool, value)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RtcpSdesChunk {
pub ssrc: RtpSsrc,
pub items: Vec<RtcpSdesItem>,
}
impl RtcpSdesChunk {
pub fn new(ssrc: RtpSsrc) -> Self {
Self {
ssrc,
items: Vec::new(),
}
}
pub fn add_item(&mut self, item: RtcpSdesItem) {
self.items.push(item);
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RtcpSourceDescription {
pub chunks: Vec<RtcpSdesChunk>,
}
impl RtcpSourceDescription {
pub fn new() -> Self {
Self { chunks: Vec::new() }
}
pub fn add_chunk(&mut self, chunk: RtcpSdesChunk) {
self.chunks.push(chunk);
}
pub fn add_source(&mut self, ssrc: RtpSsrc, cname: Option<String>) {
let mut chunk = RtcpSdesChunk::new(ssrc);
if let Some(cname_value) = cname {
chunk.add_item(RtcpSdesItem::cname(cname_value));
}
self.add_chunk(chunk);
}
pub fn find_chunk(&self, ssrc: RtpSsrc) -> Option<&RtcpSdesChunk> {
self.chunks.iter().find(|chunk| chunk.ssrc == ssrc)
}
pub fn find_cname(&self, ssrc: RtpSsrc) -> Option<&str> {
if let Some(chunk) = self.find_chunk(ssrc) {
for item in &chunk.items {
if item.item_type == RtcpSdesItemType::CName {
return Some(&item.value);
}
}
}
None
}
pub fn serialize(&self) -> Result<BytesMut> {
let mut total_size = 0;
for chunk in &self.chunks {
total_size += 4;
for item in &chunk.items {
total_size += 2 + item.value.len();
}
total_size += 1;
if total_size % 4 != 0 {
total_size += 4 - (total_size % 4);
}
}
let mut buf = BytesMut::with_capacity(total_size);
for chunk in &self.chunks {
buf.put_u32(chunk.ssrc);
for item in &chunk.items {
buf.put_u8(item.item_type as u8);
buf.put_u8(item.value.len() as u8);
buf.put_slice(item.value.as_bytes());
}
buf.put_u8(RtcpSdesItemType::End as u8);
let padding_bytes = (4 - (buf.len() % 4)) % 4;
for _ in 0..padding_bytes {
buf.put_u8(0);
}
}
Ok(buf)
}
}
pub fn serialize_sdes(sdes: &RtcpSourceDescription) -> Result<BytesMut> {
sdes.serialize()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sdes_item_creation() {
let item = RtcpSdesItem::cname("user@example.com".to_string());
assert_eq!(item.item_type, RtcpSdesItemType::CName);
assert_eq!(item.value, "user@example.com");
let item = RtcpSdesItem::name("Test User".to_string());
assert_eq!(item.item_type, RtcpSdesItemType::Name);
assert_eq!(item.value, "Test User");
let item = RtcpSdesItem::tool("rVOIP RTP".to_string());
assert_eq!(item.item_type, RtcpSdesItemType::Tool);
assert_eq!(item.value, "rVOIP RTP");
}
#[test]
fn test_sdes_chunk() {
let mut chunk = RtcpSdesChunk::new(0x12345678);
chunk.add_item(RtcpSdesItem::cname("user@example.com".to_string()));
chunk.add_item(RtcpSdesItem::tool("rVOIP RTP".to_string()));
assert_eq!(chunk.ssrc, 0x12345678);
assert_eq!(chunk.items.len(), 2);
assert_eq!(chunk.items[0].item_type, RtcpSdesItemType::CName);
assert_eq!(chunk.items[0].value, "user@example.com");
assert_eq!(chunk.items[1].item_type, RtcpSdesItemType::Tool);
assert_eq!(chunk.items[1].value, "rVOIP RTP");
}
#[test]
fn test_sdes_packet() {
let mut sdes = RtcpSourceDescription::new();
let mut chunk1 = RtcpSdesChunk::new(0x12345678);
chunk1.add_item(RtcpSdesItem::cname("user1@example.com".to_string()));
chunk1.add_item(RtcpSdesItem::tool("rVOIP RTP".to_string()));
sdes.add_chunk(chunk1);
sdes.add_source(0xabcdef01, Some("user2@example.com".to_string()));
assert_eq!(sdes.chunks.len(), 2);
assert_eq!(sdes.chunks[0].ssrc, 0x12345678);
assert_eq!(sdes.chunks[0].items.len(), 2);
assert_eq!(sdes.chunks[1].ssrc, 0xabcdef01);
assert_eq!(sdes.chunks[1].items.len(), 1);
assert_eq!(sdes.chunks[1].items[0].item_type, RtcpSdesItemType::CName);
assert_eq!(sdes.chunks[1].items[0].value, "user2@example.com");
let chunk = sdes.find_chunk(0x12345678);
assert!(chunk.is_some());
assert_eq!(chunk.unwrap().ssrc, 0x12345678);
let cname = sdes.find_cname(0xabcdef01);
assert!(cname.is_some());
assert_eq!(cname.unwrap(), "user2@example.com");
let chunk = sdes.find_chunk(0x99999999);
assert!(chunk.is_none());
let cname = sdes.find_cname(0x99999999);
assert!(cname.is_none());
}
}