use bytes::{Buf, BufMut, BytesMut};
use crate::error::Error;
use crate::{Result, RtpSsrc};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RtcpGoodbye {
pub sources: Vec<RtpSsrc>,
pub reason: Option<String>,
}
impl RtcpGoodbye {
pub fn new() -> Self {
Self {
sources: Vec::new(),
reason: None,
}
}
pub fn new_for_source(ssrc: RtpSsrc) -> Self {
let mut bye = Self::new();
bye.add_source(ssrc);
bye
}
pub fn new_with_reason(ssrc: RtpSsrc, reason: String) -> Self {
let mut bye = Self::new_for_source(ssrc);
bye.reason = Some(reason);
bye
}
pub fn add_source(&mut self, ssrc: RtpSsrc) {
self.sources.push(ssrc);
}
pub fn set_reason(&mut self, reason: String) {
self.reason = Some(reason);
}
pub fn size(&self) -> usize {
let mut size = self.sources.len() * 4;
if let Some(reason) = &self.reason {
size += 1; size += reason.len(); let padding = (4 - ((1 + reason.len()) % 4)) % 4;
size += padding;
}
size
}
pub fn serialize(&self) -> Result<BytesMut> {
let mut buf = BytesMut::with_capacity(self.size());
for ssrc in &self.sources {
buf.put_u32(*ssrc);
}
if let Some(reason) = &self.reason {
let reason_len = reason.len().min(255);
buf.put_u8(reason_len as u8);
buf.put_slice(&reason.as_bytes()[0..reason_len]);
let padding_bytes = (4 - ((1 + reason_len) % 4)) % 4;
for _ in 0..padding_bytes {
buf.put_u8(0);
}
}
Ok(buf)
}
}
pub fn parse_bye(buf: &mut impl Buf, source_count: u8) -> Result<RtcpGoodbye> {
let mut sources = Vec::with_capacity(source_count as usize);
for _ in 0..source_count {
if buf.remaining() < 4 {
return Err(Error::BufferTooSmall {
required: 4,
available: buf.remaining(),
});
}
sources.push(buf.get_u32());
}
let reason = if buf.has_remaining() {
if buf.remaining() < 1 {
return Err(Error::BufferTooSmall {
required: 1,
available: buf.remaining(),
});
}
let reason_len = buf.get_u8() as usize;
if buf.remaining() < reason_len {
return Err(Error::BufferTooSmall {
required: reason_len,
available: buf.remaining(),
});
}
let mut reason_bytes = vec![0u8; reason_len];
buf.copy_to_slice(&mut reason_bytes);
let padding_bytes = (4 - ((1 + reason_len) % 4)) % 4;
for _ in 0..padding_bytes {
if buf.has_remaining() {
buf.advance(1);
}
}
Some(String::from_utf8_lossy(&reason_bytes).to_string())
} else {
None
};
Ok(RtcpGoodbye { sources, reason })
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bye_creation() {
let bye = RtcpGoodbye::new();
assert!(bye.sources.is_empty());
assert!(bye.reason.is_none());
let bye = RtcpGoodbye::new_for_source(0x12345678);
assert_eq!(bye.sources.len(), 1);
assert_eq!(bye.sources[0], 0x12345678);
assert!(bye.reason.is_none());
let bye = RtcpGoodbye::new_with_reason(0x12345678, "Leaving session".to_string());
assert_eq!(bye.sources.len(), 1);
assert_eq!(bye.sources[0], 0x12345678);
assert_eq!(bye.reason, Some("Leaving session".to_string()));
}
#[test]
fn test_add_source() {
let mut bye = RtcpGoodbye::new();
bye.add_source(0x12345678);
bye.add_source(0xabcdef01);
assert_eq!(bye.sources.len(), 2);
assert_eq!(bye.sources[0], 0x12345678);
assert_eq!(bye.sources[1], 0xabcdef01);
}
#[test]
fn test_set_reason() {
let mut bye = RtcpGoodbye::new_for_source(0x12345678);
bye.set_reason("Leaving session".to_string());
assert_eq!(bye.reason, Some("Leaving session".to_string()));
}
#[test]
fn test_size_calculation() {
let bye = RtcpGoodbye::new();
assert_eq!(bye.size(), 0);
let bye = RtcpGoodbye::new_for_source(0x12345678);
assert_eq!(bye.size(), 4);
let mut bye = RtcpGoodbye::new();
bye.add_source(0x12345678);
bye.add_source(0xabcdef01);
assert_eq!(bye.size(), 8);
let bye = RtcpGoodbye::new_with_reason(0x12345678, "Bye".to_string());
assert_eq!(bye.size(), 8);
let bye = RtcpGoodbye::new_with_reason(0x12345678, "Goodbye".to_string());
assert_eq!(bye.size(), 12);
let bye = RtcpGoodbye::new_with_reason(0x12345678, "A".to_string());
assert_eq!(bye.size(), 8);
}
#[test]
fn test_serialize_parse() {
let original = RtcpGoodbye::new_for_source(0x12345678);
let serialized = original.serialize().unwrap();
let parsed = parse_bye(&mut serialized.freeze(), 1).unwrap();
assert_eq!(parsed.sources.len(), 1);
assert_eq!(parsed.sources[0], 0x12345678);
assert!(parsed.reason.is_none());
let mut original = RtcpGoodbye::new();
original.add_source(0x12345678);
original.add_source(0xabcdef01);
original.set_reason("Leaving session".to_string());
let serialized = original.serialize().unwrap();
let parsed = parse_bye(&mut serialized.freeze(), 2).unwrap();
assert_eq!(parsed.sources.len(), 2);
assert_eq!(parsed.sources[0], 0x12345678);
assert_eq!(parsed.sources[1], 0xabcdef01);
assert_eq!(parsed.reason, Some("Leaving session".to_string()));
}
}