use bytes::{BufMut, Bytes, BytesMut};
use crate::error::Error;
use crate::Result;
pub const ONE_BYTE_EXTENSION_ID: u16 = 0xBEDE;
pub const TWO_BYTE_EXTENSION_ID_BASE: u16 = 0x1000;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ExtensionFormat {
Legacy,
OneByte,
TwoByte,
}
impl ExtensionFormat {
pub fn from_extension_id(id: u16) -> Self {
if id == ONE_BYTE_EXTENSION_ID {
Self::OneByte
} else if (id & 0xFFF0) == TWO_BYTE_EXTENSION_ID_BASE {
Self::TwoByte
} else {
Self::Legacy
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ExtensionElement {
pub id: u8,
pub data: Bytes,
}
impl ExtensionElement {
pub fn new(id: u8, data: impl Into<Bytes>) -> Self {
Self {
id,
data: data.into(),
}
}
pub fn validate(&self, format: ExtensionFormat) -> Result<()> {
match format {
ExtensionFormat::OneByte => {
if self.id < 1 || self.id > 14 {
return Err(Error::InvalidParameter(format!(
"Invalid extension ID for one-byte format: {} (must be 1-14)",
self.id
)));
}
if self.data.len() > 16 {
return Err(Error::InvalidParameter(format!(
"Invalid extension length for one-byte format: {} (must be ≤ 16 bytes)",
self.data.len()
)));
}
}
ExtensionFormat::TwoByte => {
if self.id < 1 {
return Err(Error::InvalidParameter(format!(
"Invalid extension ID for two-byte format: {} (must be 1-255)",
self.id
)));
}
if self.data.len() > 255 {
return Err(Error::InvalidParameter(format!(
"Invalid extension length for two-byte format: {} (must be ≤ 255 bytes)",
self.data.len()
)));
}
}
ExtensionFormat::Legacy => {
}
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RtpHeaderExtensions {
pub format: ExtensionFormat,
pub profile_id: u16,
pub elements: Vec<ExtensionElement>,
}
impl Default for RtpHeaderExtensions {
fn default() -> Self {
Self {
format: ExtensionFormat::OneByte, profile_id: ONE_BYTE_EXTENSION_ID,
elements: Vec::new(),
}
}
}
impl RtpHeaderExtensions {
pub fn new_one_byte() -> Self {
Self {
format: ExtensionFormat::OneByte,
profile_id: ONE_BYTE_EXTENSION_ID,
elements: Vec::new(),
}
}
pub fn new_two_byte() -> Self {
Self {
format: ExtensionFormat::TwoByte,
profile_id: TWO_BYTE_EXTENSION_ID_BASE,
elements: Vec::new(),
}
}
pub fn new_legacy(profile_id: u16) -> Self {
Self {
format: ExtensionFormat::Legacy,
profile_id,
elements: Vec::new(),
}
}
pub fn from_extension_data(profile_id: u16, data: &[u8]) -> Result<Self> {
let format = ExtensionFormat::from_extension_id(profile_id);
let elements = match format {
ExtensionFormat::OneByte => Self::parse_one_byte_extensions(data)?,
ExtensionFormat::TwoByte => Self::parse_two_byte_extensions(data)?,
ExtensionFormat::Legacy => {
vec![ExtensionElement::new(0, Bytes::copy_from_slice(data))]
}
};
Ok(Self {
format,
profile_id,
elements,
})
}
pub fn add_extension(&mut self, id: u8, data: impl Into<Bytes>) -> Result<()> {
let element = ExtensionElement::new(id, data);
element.validate(self.format)?;
self.elements.push(element);
Ok(())
}
pub fn get_extension(&self, id: u8) -> Option<&ExtensionElement> {
self.elements.iter().find(|ext| ext.id == id)
}
pub fn remove_extension(&mut self, id: u8) -> Option<ExtensionElement> {
if let Some(idx) = self.elements.iter().position(|ext| ext.id == id) {
Some(self.elements.remove(idx))
} else {
None
}
}
pub fn clear(&mut self) {
self.elements.clear();
}
pub fn is_empty(&self) -> bool {
self.elements.is_empty()
}
pub fn len(&self) -> usize {
self.elements.len()
}
pub fn size(&self) -> usize {
let mut size = 0;
match self.format {
ExtensionFormat::OneByte => {
for element in &self.elements {
size += 1 + element.data.len();
}
if size % 4 != 0 {
size += 4 - (size % 4);
}
}
ExtensionFormat::TwoByte => {
for element in &self.elements {
size += 2 + element.data.len();
}
if size % 4 != 0 {
size += 4 - (size % 4);
}
}
ExtensionFormat::Legacy => {
if let Some(element) = self.elements.first() {
size = element.data.len();
if size % 4 != 0 {
size += 4 - (size % 4);
}
}
}
}
size
}
pub fn serialize(&self) -> Result<BytesMut> {
let size = self.size();
let mut buf = BytesMut::with_capacity(size);
match self.format {
ExtensionFormat::OneByte => {
for element in &self.elements {
element.validate(self.format)?;
if element.id < 1 || element.id > 14 {
return Err(Error::InvalidParameter(format!(
"Invalid extension ID for one-byte format: {} (must be 1-14)",
element.id
)));
}
let length = element.data.len();
if length > 16 {
return Err(Error::InvalidParameter(format!(
"Extension data too long for one-byte header: {} (max is 16 bytes)",
length
)));
}
let ext_byte = (element.id << 4) | ((length as u8 - 1) & 0x0F);
buf.put_u8(ext_byte);
buf.put_slice(&element.data);
}
buf.put_u8(0x00);
while buf.len() % 4 != 0 {
buf.put_u8(0x00);
}
}
ExtensionFormat::TwoByte => {
for element in &self.elements {
element.validate(self.format)?;
if element.id < 1 {
return Err(Error::InvalidParameter(format!(
"Invalid extension ID for two-byte format: {} (must be 1-255)",
element.id
)));
}
buf.put_u8(element.id);
let length = element.data.len();
if length > 255 {
return Err(Error::InvalidParameter(format!(
"Extension data too long for two-byte header: {} (max is 255 bytes)",
length
)));
}
buf.put_u8(length as u8);
buf.put_slice(&element.data);
}
buf.put_u8(0x00);
while buf.len() % 4 != 0 {
buf.put_u8(0x00);
}
}
ExtensionFormat::Legacy => {
if let Some(element) = self.elements.first() {
buf.put_slice(&element.data);
while buf.len() % 4 != 0 {
buf.put_u8(0x00);
}
}
}
}
Ok(buf)
}
fn parse_one_byte_extensions(data: &[u8]) -> Result<Vec<ExtensionElement>> {
let mut elements = Vec::new();
let mut i = 0;
while i < data.len() {
let header_byte = data[i];
i += 1;
if header_byte == 0 {
break;
}
if (header_byte & 0xF0) == 0xF0 {
continue;
}
let id = (header_byte >> 4) & 0x0F;
let length = ((header_byte & 0x0F) + 1) as usize;
if i + length > data.len() {
return Err(Error::BufferTooSmall {
required: i + length,
available: data.len(),
});
}
let ext_data = Bytes::copy_from_slice(&data[i..i + length]);
i += length;
elements.push(ExtensionElement::new(id, ext_data));
}
Ok(elements)
}
fn parse_two_byte_extensions(data: &[u8]) -> Result<Vec<ExtensionElement>> {
let mut elements = Vec::new();
let mut i = 0;
while i < data.len() {
let id = data[i];
i += 1;
if id == 0 {
break;
}
if i >= data.len() {
return Err(Error::BufferTooSmall {
required: i,
available: data.len(),
});
}
let length = data[i] as usize;
i += 1;
if i + length > data.len() {
return Err(Error::BufferTooSmall {
required: i + length,
available: data.len(),
});
}
let ext_data = Bytes::copy_from_slice(&data[i..i + length]);
i += length;
elements.push(ExtensionElement::new(id, ext_data));
}
Ok(elements)
}
}
pub mod ids {
pub const AUDIO_LEVEL: u8 = 1;
pub const TIMESTAMP_OFFSET: u8 = 2;
pub const VIDEO_ORIENTATION: u8 = 3;
pub const TRANSPORT_CC: u8 = 4;
pub const FRAME_MARKING: u8 = 5;
pub const SDES: u8 = 6;
}
pub mod uris {
pub const AUDIO_LEVEL: &str = "urn:ietf:params:rtp-hdrext:ssrc-audio-level";
pub const TIMESTAMP_OFFSET: &str = "urn:ietf:params:rtp-hdrext:toffset";
pub const VIDEO_ORIENTATION: &str = "urn:3gpp:video-orientation";
pub const TRANSPORT_CC: &str =
"http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01";
pub const FRAME_MARKING: &str = "urn:ietf:params:rtp-hdrext:framemarking";
pub const STREAM_ID: &str = "urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id";
pub const REPAIR_RTP_STREAM_ID: &str = "urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id";
pub const MID: &str = "urn:ietf:params:rtp-hdrext:sdes:mid";
pub const ABS_SEND_TIME: &str = "http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time";
pub const COLOR_SPACE: &str = "http://www.webrtc.org/experiments/rtp-hdrext/color-space";
pub const VIDEO_CONTENT_TYPE: &str =
"http://www.webrtc.org/experiments/rtp-hdrext/video-content-type";
pub const VIDEO_TIMING: &str = "http://www.webrtc.org/experiments/rtp-hdrext/video-timing";
pub const PLAYOUT_DELAY: &str = "http://www.webrtc.org/experiments/rtp-hdrext/playout-delay";
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_extension_format_detection() {
assert_eq!(
ExtensionFormat::from_extension_id(0xBEDE),
ExtensionFormat::OneByte
);
assert_eq!(
ExtensionFormat::from_extension_id(0x1000),
ExtensionFormat::TwoByte
);
assert_eq!(
ExtensionFormat::from_extension_id(0x1005),
ExtensionFormat::TwoByte
);
assert_eq!(
ExtensionFormat::from_extension_id(0x100F),
ExtensionFormat::TwoByte
);
assert_eq!(
ExtensionFormat::from_extension_id(0x2000),
ExtensionFormat::Legacy
);
assert_eq!(
ExtensionFormat::from_extension_id(0x0000),
ExtensionFormat::Legacy
);
}
#[test]
fn test_one_byte_extensions() {
let mut ext = RtpHeaderExtensions::new_one_byte();
ext.add_extension(1, vec![1, 2, 3, 4]).unwrap();
ext.add_extension(2, vec![5, 6]).unwrap();
ext.add_extension(3, vec![7]).unwrap();
assert_eq!(ext.format, ExtensionFormat::OneByte);
assert_eq!(ext.profile_id, ONE_BYTE_EXTENSION_ID);
assert_eq!(ext.len(), 3);
assert!(!ext.is_empty());
assert_eq!(
ext.get_extension(1).unwrap().data,
Bytes::from_static(&[1, 2, 3, 4])
);
assert_eq!(
ext.get_extension(2).unwrap().data,
Bytes::from_static(&[5, 6])
);
assert_eq!(ext.get_extension(3).unwrap().data, Bytes::from_static(&[7]));
let serialized = ext.serialize().unwrap();
let parsed =
RtpHeaderExtensions::from_extension_data(ONE_BYTE_EXTENSION_ID, &serialized).unwrap();
assert_eq!(parsed.format, ExtensionFormat::OneByte);
assert_eq!(parsed.len(), 3);
assert_eq!(
parsed.get_extension(1).unwrap().data,
Bytes::from_static(&[1, 2, 3, 4])
);
assert_eq!(
parsed.get_extension(2).unwrap().data,
Bytes::from_static(&[5, 6])
);
assert_eq!(
parsed.get_extension(3).unwrap().data,
Bytes::from_static(&[7])
);
}
#[test]
fn test_two_byte_extensions() {
let mut ext = RtpHeaderExtensions::new_two_byte();
ext.add_extension(1, vec![1, 2, 3, 4]).unwrap();
ext.add_extension(2, vec![5, 6]).unwrap();
ext.add_extension(100, vec![7]).unwrap();
assert_eq!(ext.format, ExtensionFormat::TwoByte);
assert_eq!(ext.profile_id, TWO_BYTE_EXTENSION_ID_BASE);
assert_eq!(ext.len(), 3);
assert!(!ext.is_empty());
assert_eq!(
ext.get_extension(1).unwrap().data,
Bytes::from_static(&[1, 2, 3, 4])
);
assert_eq!(
ext.get_extension(2).unwrap().data,
Bytes::from_static(&[5, 6])
);
assert_eq!(
ext.get_extension(100).unwrap().data,
Bytes::from_static(&[7])
);
let serialized = ext.serialize().unwrap();
let parsed =
RtpHeaderExtensions::from_extension_data(TWO_BYTE_EXTENSION_ID_BASE, &serialized)
.unwrap();
assert_eq!(parsed.format, ExtensionFormat::TwoByte);
assert_eq!(parsed.len(), 3);
assert_eq!(
parsed.get_extension(1).unwrap().data,
Bytes::from_static(&[1, 2, 3, 4])
);
assert_eq!(
parsed.get_extension(2).unwrap().data,
Bytes::from_static(&[5, 6])
);
assert_eq!(
parsed.get_extension(100).unwrap().data,
Bytes::from_static(&[7])
);
}
#[test]
fn test_legacy_extensions() {
let raw_data = vec![1, 2, 3, 4, 5, 6, 7, 8];
let legacy_ext = RtpHeaderExtensions::from_extension_data(0x1234, &raw_data).unwrap();
assert_eq!(legacy_ext.format, ExtensionFormat::Legacy);
assert_eq!(legacy_ext.profile_id, 0x1234);
assert_eq!(legacy_ext.len(), 1);
assert_eq!(legacy_ext.elements[0].id, 0);
assert_eq!(legacy_ext.elements[0].data, Bytes::from(raw_data));
}
#[test]
fn test_invalid_extensions() {
let mut one_byte = RtpHeaderExtensions::new_one_byte();
assert!(one_byte.add_extension(15, vec![1, 2, 3]).is_err());
let long_data: Vec<u8> = (0..20).collect(); assert!(one_byte.add_extension(1, long_data).is_err());
assert!(one_byte.add_extension(1, vec![1, 2, 3]).is_ok());
assert!(one_byte.add_extension(14, vec![4, 5, 6]).is_ok());
let mut two_byte = RtpHeaderExtensions::new_two_byte();
assert!(two_byte.add_extension(0, vec![1, 2, 3]).is_err());
let very_long_data = vec![0; 256]; assert!(two_byte.add_extension(1, very_long_data).is_err());
assert!(two_byte.add_extension(1, vec![1, 2, 3]).is_ok());
assert!(two_byte.add_extension(255, vec![4, 5, 6]).is_ok());
}
#[test]
fn test_remove_extension() {
let mut ext = RtpHeaderExtensions::new_one_byte();
ext.add_extension(1, vec![1, 2, 3]).unwrap();
ext.add_extension(2, vec![4, 5, 6]).unwrap();
assert_eq!(ext.len(), 2);
assert!(ext.get_extension(1).is_some());
assert!(ext.get_extension(2).is_some());
let removed = ext.remove_extension(1);
assert!(removed.is_some());
assert_eq!(removed.unwrap().data, Bytes::from_static(&[1, 2, 3]));
assert_eq!(ext.len(), 1);
assert!(ext.get_extension(1).is_none());
assert!(ext.get_extension(2).is_some());
assert!(ext.remove_extension(3).is_none());
ext.clear();
assert_eq!(ext.len(), 0);
assert!(ext.is_empty());
}
#[test]
fn test_one_byte_serialization_alignment() {
let mut ext = RtpHeaderExtensions::new_one_byte();
ext.add_extension(1, vec![42]).unwrap();
let serialized = ext.serialize().unwrap();
assert_eq!(serialized.len(), 4);
assert_eq!(serialized.len() % 4, 0);
}
#[test]
fn test_two_byte_serialization_alignment() {
let mut ext = RtpHeaderExtensions::new_two_byte();
ext.add_extension(1, vec![42]).unwrap();
let serialized = ext.serialize().unwrap();
assert_eq!(serialized.len(), 4);
assert_eq!(serialized.len() % 4, 0);
}
}