From d12db3ad8bac3877ecf99a91c83db9986498e93a Mon Sep 17 00:00:00 2001 From: Matthew Waters Date: Mon, 22 Jun 2026 17:22:46 +1000 Subject: [PATCH] Add support for concrete extension implementations --- benches/write.rs | 21 ++- fuzz/fuzz_targets/rtp_from_bytes.rs | 7 + src/builder.rs | 162 +++++++++-------- src/edit.rs | 12 +- src/extension.rs | 123 +++++++++++++ src/extension/mid.rs | 79 +++++++++ src/extension/one.rs | 260 ++++++++++++++++++++++++++++ src/extension/two.rs | 255 +++++++++++++++++++++++++++ src/lib.rs | 7 + src/packet.rs | 36 ++-- 10 files changed, 855 insertions(+), 107 deletions(-) create mode 100644 src/extension.rs create mode 100644 src/extension/mid.rs create mode 100644 src/extension/one.rs create mode 100644 src/extension/two.rs diff --git a/benches/write.rs b/benches/write.rs index 28a2032..b3f1364 100644 --- a/benches/write.rs +++ b/benches/write.rs @@ -3,9 +3,11 @@ use criterion::{ criterion_group, criterion_main, AxisScale, BenchmarkId, Criterion, PlotConfiguration, }; -use rtp_types::RtpPacketBuilder; +use rtp_types::{ + extension::ExtensionBlock, RtpExtensionsBlock, RtpExtensionsBlockWrite, RtpPacketBuilder, +}; -fn default_builder<'a>() -> RtpPacketBuilder<&'a [u8], &'a [u8]> { +fn default_builder<'a>() -> RtpPacketBuilder<&'a [u8], ExtensionBlock<'static>> { RtpPacketBuilder::new() .payload_type(96) .ssrc(0x12345678) @@ -13,15 +15,21 @@ fn default_builder<'a>() -> RtpPacketBuilder<&'a [u8], &'a [u8]> { .timestamp(0x200) } -fn write_into_vec<'a>(builder: &RtpPacketBuilder<&'a [u8], &'a [u8]>, data: &mut Vec) { +fn write_into_vec( + builder: &RtpPacketBuilder<&'_ [u8], E>, + data: &mut Vec, +) { builder.write_into_vec(data).unwrap(); } -fn write_into_slice<'a>(builder: &RtpPacketBuilder<&'a [u8], &'a [u8]>, slice: &mut [u8]) { +fn write_into_slice( + builder: &RtpPacketBuilder<&'_ [u8], E>, + slice: &mut [u8], +) { builder.write_into(slice).unwrap(); } -fn write_vec<'a>(builder: &RtpPacketBuilder<&'a [u8], &'a [u8]>) { +fn write_vec(builder: &RtpPacketBuilder<&'_ [u8], E>) { let _data = builder.write_vec().unwrap(); } @@ -55,7 +63,8 @@ fn bench_write(c: &mut Criterion) { ), ( "Extension", - default_builder().extension(0x2345, payload16.as_slice()), + default_builder() + .extension(ExtensionBlock::parse(0x2345, payload16.as_slice()).unwrap()), ), ("Payload16", default_builder().payload(payload16.as_slice())), ( diff --git a/fuzz/fuzz_targets/rtp_from_bytes.rs b/fuzz/fuzz_targets/rtp_from_bytes.rs index 4b196d0..e9d90d2 100644 --- a/fuzz/fuzz_targets/rtp_from_bytes.rs +++ b/fuzz/fuzz_targets/rtp_from_bytes.rs @@ -7,6 +7,13 @@ fuzz_target!(|data: &[u8]| { if let Some(padding) = packet.padding() { // ignore any padding as its contents in the original data are undefined assert_eq!(&built[..built.len() - padding as usize], &data[..data.len() - padding as usize]); + let _ = packet.extension::(); + if let Some(ext) = packet.extension::() { + for _ in ext.iter() {} + } + if let Some(ext) = packet.extension::() { + for _ in ext.iter() {} + } } else { assert_eq!(&built, &data); } diff --git a/src/builder.rs b/src/builder.rs index 64fd608..2d78039 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -2,7 +2,8 @@ use std::marker::PhantomData; -use crate::RtpPacket; +use crate::extension::ExtensionBlock; +use crate::{RtpExtensionsBlockWrite, RtpPacket}; /// Errors produced when wrting a packet #[derive(Debug, PartialEq, Eq, thiserror::Error)] @@ -33,7 +34,8 @@ pub enum RtpWriteError { /// Struct for building a new RTP packet. #[derive(Debug)] #[must_use = "The builder must be built to be used"] -pub struct RtpPacketBuilder { +pub struct RtpPacketBuilder> +{ padding: Option, csrcs: smallvec::SmallVec<[u32; 15]>, marker_bit: bool, @@ -41,19 +43,12 @@ pub struct RtpPacketBuilder { sequence_number: u16, timestamp: u32, ssrc: u32, - extension: Option<(u16, E)>, + extension: Option, payloads: smallvec::SmallVec<[P; 16]>, } -impl Default for RtpPacketBuilder { +impl Default for RtpPacketBuilder { fn default() -> Self { - Self::new() - } -} - -impl RtpPacketBuilder { - /// Construct a new packet builder. - pub fn new() -> RtpPacketBuilder { Self { padding: None, csrcs: smallvec::smallvec![], @@ -67,7 +62,16 @@ impl RtpPacketBuilder { payloads: smallvec::SmallVec::new(), } } +} + +impl RtpPacketBuilder> { + /// Construct a new packet builder. + pub fn new() -> Self { + Self::default() + } +} +impl RtpPacketBuilder { /// Set the number of padding bytes to use for this packet. pub fn padding(mut self, padding: u8) -> Self { self.padding = Some(padding); @@ -129,9 +133,18 @@ impl RtpPacketBuilder { } /// Set the extension header for this packet. - pub fn extension(mut self, extension_id: u16, extension_data: E) -> Self { - self.extension = Some((extension_id, extension_data)); - self + pub fn extension(self, extension: F) -> RtpPacketBuilder { + RtpPacketBuilder { + extension: Some(extension), + padding: self.padding, + csrcs: self.csrcs, + marker_bit: self.marker_bit, + payload_type: self.payload_type, + sequence_number: self.sequence_number, + timestamp: self.timestamp, + ssrc: self.ssrc, + payloads: self.payloads, + } } /// Clear any extension data configured for this packet. @@ -158,14 +171,15 @@ impl RtpPacketBuilder { /// Calculate the size required for writing the packet. pub fn calculate_size(&self) -> Result { let payload_len = self.payloads.iter().map(|p| p.len()).sum::(); - let extension_len = if let Some((_ext_id, ext_data)) = self.extension.as_ref() { - if ext_data.len() % 4 != 0 { + let extension_len = if let Some(ext) = self.extension.as_ref() { + let len = ext.byte_len(); + if len % 4 != 0 { return Err(RtpWriteError::ExtensionDataNotPadded); } - if ext_data.len() > u16::MAX as usize { + if len > u16::MAX as usize { return Err(RtpWriteError::PacketTooLarge); } - 4 + ext_data.len() + 4 + len } else { 0 }; @@ -232,7 +246,7 @@ impl RtpPacketBuilder { /// Write this packet using writer without any validity checks. pub fn write_unchecked( &self, - writer: &mut impl RtpPacketWriter, + writer: &mut impl RtpPacketWriter, ) -> O { let mut hdr = [0; RtpPacket::MIN_RTP_PACKET_LEN]; self.write_header_into(&mut hdr); @@ -242,10 +256,12 @@ impl RtpPacketBuilder { writer.push(csrc.to_be_bytes().as_ref()); } - if let Some((ext_id, ext_data)) = self.extension.as_ref() { + if let Some(ext) = self.extension.as_ref() { + let ext_id = ext.extension_id(); + let ext_len = ext.byte_len(); writer.push(ext_id.to_be_bytes().as_ref()); - writer.push(((ext_data.len() / 4) as u16).to_be_bytes().as_ref()); - writer.push_extension(ext_data); + writer.push(((ext_len / 4) as u16).to_be_bytes().as_ref()); + ext.write(writer); } for payload in self.payloads.iter() { @@ -262,7 +278,7 @@ impl RtpPacketBuilder { /// Write the packet using writer. pub fn write( &self, - writer: &mut impl RtpPacketWriter, + writer: &mut impl RtpPacketWriter, ) -> Result { let len = self.write_preconditions()?; if let Some(max_size) = writer.max_size() { @@ -276,7 +292,7 @@ impl RtpPacketBuilder { } } -impl RtpPacketBuilder<&[u8], &[u8]> { +impl RtpPacketBuilder<&[u8], E> { /// Write this packet into `buf` without any validity checks. Returns the number of bytes /// written. pub fn write_into_unchecked(&self, buf: &mut [u8]) -> usize { @@ -333,8 +349,6 @@ pub trait RtpPacketWriter { type Output; /// The type of the RTP payload to be stored in the output packet. type Payload: PayloadLength; - /// The type of the RTP extension data to be stored in the output packet:was. - type Extension: PayloadLength; /// Reserve a number of bytes in the output. Multiple calls are possible and provide the /// entire size to reserve. @@ -349,10 +363,6 @@ pub trait RtpPacketWriter { /// Provides data to append to the output. May be called multiple times per packet. fn push(&mut self, data: &[u8]); - /// Provides the extension data to add to the output. The extension should be written as-is - /// without any transformations. - fn push_extension(&mut self, extension_data: &Self::Extension); - /// Provides the payload data to add to the output. The payload should be written as-is /// without any transformations. fn push_payload(&mut self, payload: &Self::Payload); @@ -395,16 +405,15 @@ impl PayloadLength for &[T; N] { /// An implementation of a [`RtpPacketWriter`] that appends to a `Vec`. #[derive(Default, Debug)] -pub struct RtpPacketWriterVec<'a, 'b> { +pub struct RtpPacketWriterVec<'a> { output: Vec, padding: Option, - phantom: PhantomData<(&'a [u8], &'b [u8])>, + phantom: PhantomData<&'a [u8]>, } -impl<'a, 'b> RtpPacketWriter for RtpPacketWriterVec<'a, 'b> { +impl<'a> RtpPacketWriter for RtpPacketWriterVec<'a> { type Output = Vec; type Payload = &'a [u8]; - type Extension = &'b [u8]; fn reserve(&mut self, size: usize) { if self.output.len() < size { @@ -416,10 +425,6 @@ impl<'a, 'b> RtpPacketWriter for RtpPacketWriterVec<'a, 'b> { self.output.extend_from_slice(data) } - fn push_extension(&mut self, extension_data: &Self::Extension) { - self.push(extension_data) - } - fn push_payload(&mut self, data: &Self::Payload) { self.push(data) } @@ -443,14 +448,14 @@ impl<'a, 'b> RtpPacketWriter for RtpPacketWriterVec<'a, 'b> { /// An implementation of a [`RtpPacketWriter`] that writes to a `&mut [u8]`. Each packet will be /// written starting at the beginning of the provided slice. #[derive(Default, Debug)] -pub struct RtpPacketWriterMutSlice<'a, 'b, 'c> { +pub struct RtpPacketWriterMutSlice<'a, 'b> { output: &'a mut [u8], padding: Option, write_i: usize, - phantom: PhantomData<(&'b [u8], &'c [u8])>, + phantom: PhantomData<&'b [u8]>, } -impl<'a> RtpPacketWriterMutSlice<'a, '_, '_> { +impl<'a> RtpPacketWriterMutSlice<'a, '_> { /// Construct a new [`RtpPacketWriterMutSlice`] from the provided slice. pub fn new(buf: &'a mut [u8]) -> Self { Self { @@ -462,7 +467,7 @@ impl<'a> RtpPacketWriterMutSlice<'a, '_, '_> { } } -impl std::ops::Deref for RtpPacketWriterMutSlice<'_, '_, '_> { +impl std::ops::Deref for RtpPacketWriterMutSlice<'_, '_> { type Target = [u8]; fn deref(&self) -> &Self::Target { @@ -470,16 +475,15 @@ impl std::ops::Deref for RtpPacketWriterMutSlice<'_, '_, '_> { } } -impl std::ops::DerefMut for RtpPacketWriterMutSlice<'_, '_, '_> { +impl std::ops::DerefMut for RtpPacketWriterMutSlice<'_, '_> { fn deref_mut(&mut self) -> &mut Self::Target { self.output } } -impl<'b, 'c> RtpPacketWriter for RtpPacketWriterMutSlice<'_, 'b, 'c> { +impl<'b> RtpPacketWriter for RtpPacketWriterMutSlice<'_, 'b> { type Output = usize; type Payload = &'b [u8]; - type Extension = &'c [u8]; fn max_size(&self) -> Option { Some(self.output.len()) @@ -490,10 +494,6 @@ impl<'b, 'c> RtpPacketWriter for RtpPacketWriterMutSlice<'_, 'b, 'c> { self.write_i += data.len(); } - fn push_extension(&mut self, extension_data: &Self::Extension) { - self.push(extension_data) - } - fn push_payload(&mut self, data: &Self::Payload) { self.push(data) } @@ -519,13 +519,13 @@ impl<'b, 'c> RtpPacketWriter for RtpPacketWriterMutSlice<'_, 'b, 'c> { /// written will be appended to the provide `Vec`. You can `clear()` the vec in between packets /// to have each packet written from the beginning of the vec. #[derive(Debug)] -pub struct RtpPacketWriterMutVec<'a, 'b, 'c> { +pub struct RtpPacketWriterMutVec<'a, 'b> { output: &'a mut Vec, padding: Option, - phantom: PhantomData<(&'b [u8], &'c [u8])>, + phantom: PhantomData<&'b [u8]>, } -impl<'a> RtpPacketWriterMutVec<'a, '_, '_> { +impl<'a> RtpPacketWriterMutVec<'a, '_> { /// Construct a new [`RtpPacketWriterMutVec`] from a provided mutable `Vec`. pub fn new(buf: &'a mut Vec) -> Self { Self { @@ -536,7 +536,7 @@ impl<'a> RtpPacketWriterMutVec<'a, '_, '_> { } } -impl std::ops::Deref for RtpPacketWriterMutVec<'_, '_, '_> { +impl std::ops::Deref for RtpPacketWriterMutVec<'_, '_> { type Target = Vec; fn deref(&self) -> &Self::Target { @@ -544,25 +544,20 @@ impl std::ops::Deref for RtpPacketWriterMutVec<'_, '_, '_> { } } -impl std::ops::DerefMut for RtpPacketWriterMutVec<'_, '_, '_> { +impl std::ops::DerefMut for RtpPacketWriterMutVec<'_, '_> { fn deref_mut(&mut self) -> &mut Self::Target { self.output } } -impl<'b, 'c> RtpPacketWriter for RtpPacketWriterMutVec<'_, 'b, 'c> { +impl<'b> RtpPacketWriter for RtpPacketWriterMutVec<'_, 'b> { type Output = (); type Payload = &'b [u8]; - type Extension = &'c [u8]; fn push(&mut self, data: &[u8]) { self.output.extend(data); } - fn push_extension(&mut self, extension_data: &Self::Extension) { - self.push(extension_data) - } - fn push_payload(&mut self, data: &Self::Payload) { self.push(data) } @@ -582,6 +577,8 @@ impl<'b, 'c> RtpPacketWriter for RtpPacketWriterMutVec<'_, 'b, 'c> { #[cfg(test)] mod tests { + use crate::RtpExtensionsBlock; + use super::*; #[test] @@ -625,7 +622,7 @@ mod tests { assert_eq!(rtp.timestamp(), 0x0); assert_eq!(rtp.ssrc(), 0x0); assert_eq!(rtp.csrc().count(), 0); - assert_eq!(rtp.extension(), None); + assert_eq!(rtp.extension::(), None); assert_eq!(rtp.payload(), &[]); } } @@ -667,7 +664,7 @@ mod tests { let mut csrc = rtp.csrc(); assert_eq!(csrc.next(), Some(0x0b0c0d0e)); assert_eq!(csrc.next(), None); - assert_eq!(rtp.extension(), None); + assert_eq!(rtp.extension::(), None); assert_eq!(rtp.payload(), &[]); } } @@ -751,7 +748,7 @@ mod tests { .timestamp(0x03040506) .ssrc(0x0708090a) .add_csrc(0x0b0c0d0e) - .extension(0x9876, extension_data.as_ref()); + .extension(ExtensionBlock::parse(0x9876, extension_data.as_ref()).unwrap()); let size = builder.write_into(&mut data).unwrap(); let buf = builder.write_vec().unwrap(); let buf2 = builder.write_vec_unchecked(); @@ -777,7 +774,10 @@ mod tests { let mut csrc = rtp.csrc(); assert_eq!(csrc.next(), Some(0x0b0c0d0e)); assert_eq!(csrc.next(), None); - assert_eq!(rtp.extension(), Some((0x9876, extension_data.as_ref()))); + assert_eq!( + rtp.extension(), + ExtensionBlock::parse(0x9876, extension_data.as_ref()).ok() + ); assert_eq!(rtp.payload(), &[]); } } @@ -795,7 +795,7 @@ mod tests { .timestamp(0x03040506) .ssrc(0x0708090a) .add_csrc(0x0b0c0d0e) - .extension(0x9876, extension_data.as_ref()) + .extension(ExtensionBlock::parse(0x9876, extension_data.as_ref()).unwrap()) .clear_extension(); let size = builder.write_into(&mut data).unwrap(); let buf = builder.write_vec().unwrap(); @@ -822,7 +822,7 @@ mod tests { let mut csrc = rtp.csrc(); assert_eq!(csrc.next(), Some(0x0b0c0d0e)); assert_eq!(csrc.next(), None); - assert_eq!(rtp.extension(), None); + assert_eq!(rtp.extension::(), None); assert_eq!(rtp.payload(), &[]); } } @@ -841,7 +841,7 @@ mod tests { .timestamp(0x03040506) .ssrc(0x0708090a) .add_csrc(0x0b0c0d0e) - .extension(0x9876, extension_data.as_ref()) + .extension(ExtensionBlock::parse(0x9876, extension_data.as_ref()).unwrap()) .payload(payload_data.as_ref()) .padding(7); let size = builder.write_into(&mut data).unwrap(); @@ -869,7 +869,10 @@ mod tests { let mut csrc = rtp.csrc(); assert_eq!(csrc.next(), Some(0x0b0c0d0e)); assert_eq!(csrc.next(), None); - assert_eq!(rtp.extension(), Some((0x9876, extension_data.as_ref()))); + assert_eq!( + rtp.extension(), + ExtensionBlock::parse(0x9876, extension_data.as_ref()).ok() + ); assert_eq!(rtp.payload(), payload_data.as_ref()); } } @@ -912,7 +915,7 @@ mod tests { let mut csrc = rtp.csrc(); assert_eq!(csrc.next(), Some(0x0b0c0d0e)); assert_eq!(csrc.next(), None); - assert_eq!(rtp.extension(), None); + assert_eq!(rtp.extension::(), None); assert_eq!(rtp.payload(), &[]); } } @@ -939,7 +942,7 @@ mod tests { let mut vec = vec![]; let builder = RtpPacketBuilder::new() .payload_type(96) - .extension(0x9876, [1].as_ref()); + .extension(ExtensionBlock::parse(0x9876, &[1]).unwrap()); assert_eq!( builder.write_into(&mut data), Err(RtpWriteError::ExtensionDataNotPadded) @@ -1027,7 +1030,7 @@ mod tests { assert_eq!(csrc.next(), Some(i)); } assert_eq!(csrc.next(), None); - assert_eq!(rtp.extension(), None); + assert_eq!(rtp.extension::(), None); assert_eq!(rtp.payload(), &[]); } } @@ -1075,7 +1078,7 @@ mod tests { let extension_data = [0; u16::MAX as usize + 1]; let builder = RtpPacketBuilder::new() .payload_type(96) - .extension(0x9876, extension_data.as_ref()); + .extension(ExtensionBlock::parse(0x9876, extension_data.as_ref()).unwrap()); assert_eq!( builder.write_into(&mut data), Err(RtpWriteError::PacketTooLarge) @@ -1116,7 +1119,7 @@ mod tests { assert_eq!( RtpPacketBuilder::new() .payload_type(96) - .extension(0x9876, [].as_ref()) + .extension(ExtensionBlock::parse(0x9876, &[]).unwrap()) .write_into(&mut data), Err(RtpWriteError::OutputTooSmall(16)) ); @@ -1165,7 +1168,6 @@ mod tests { impl RtpPacketWriter for TestRtpWriterCustomPayload { type Output = Vec; type Payload = TestPayload; - type Extension = TestPayload; fn reserve(&mut self, size: usize) { if let Some(output) = self.output.as_mut() { @@ -1195,10 +1197,6 @@ mod tests { self.push(&payload.0) } - fn push_extension(&mut self, extension_data: &Self::Extension) { - self.push(&extension_data.0) - } - fn padding(&mut self, size: u8) { self.padding = Some(size); } @@ -1228,7 +1226,7 @@ mod tests { .timestamp(0x03040506) .ssrc(0x0708090a) .add_csrc(0x0b0c0d0e) - .extension(0x9876, TestPayload(extension_data.0.clone())) + .extension(ExtensionBlock::parse(0x9876, &extension_data.0).unwrap()) .payload(TestPayload(payload_data.0.clone())) .padding(1); let max_size = builder.calculate_size().unwrap(); @@ -1252,9 +1250,9 @@ mod tests { let mut csrc = rtp.csrc(); assert_eq!(csrc.next(), Some(0x0b0c0d0e)); assert_eq!(csrc.next(), None); - let (ext_id, ext_data) = rtp.extension().unwrap(); - assert_eq!(ext_id, 0x9876); - assert_eq!(ext_data, extension_data.0); + let ext = rtp.extension::().unwrap(); + assert_eq!(ext.extension_id(), 0x9876); + assert_eq!(ext.data(), extension_data.0); assert_eq!(rtp.payload(), payload_data.0); } diff --git a/src/edit.rs b/src/edit.rs index ba6f0c8..95deb03 100644 --- a/src/edit.rs +++ b/src/edit.rs @@ -112,6 +112,8 @@ impl<'a> RtpPacketMut<'a> { #[cfg(test)] mod tests { + use crate::{extension::ExtensionBlock, RtpExtensionsBlock}; + use super::*; #[test] @@ -129,7 +131,7 @@ mod tests { assert_eq!(rtp.timestamp(), 0x03040506); assert_eq!(rtp.ssrc(), 0x07080910); assert_eq!(rtp.csrc().count(), 0); - assert_eq!(rtp.extension(), None); + assert_eq!(rtp.extension::(), None); assert_eq!(rtp.payload(), &[0x11]); assert_eq!(rtp.payload_mut(), [0x11].as_mut_slice()); rtp.set_marker_bit(true); @@ -178,7 +180,7 @@ mod tests { assert_eq!(rtp.csrc().count(), 0); assert_eq!( rtp.extension(), - Some((0x0b0c, [0x0d, 0x0e, 0x0f, 0x10].as_ref())) + ExtensionBlock::parse(0x0b0c, [0x0d, 0x0e, 0x0f, 0x10].as_ref()).ok() ); assert_eq!(rtp.payload(), &[]); assert_eq!(rtp.payload_mut(), &mut []); @@ -196,7 +198,7 @@ mod tests { rtp.set_extension_id(0x1234); assert_eq!( rtp.extension(), - Some((0x1234, [0x0d, 0x0e, 0x0f, 0x10].as_ref())) + ExtensionBlock::parse(0x1234, [0x0d, 0x0e, 0x0f, 0x10].as_ref()).ok() ); assert_eq!( rtp.extension_mut(), @@ -220,7 +222,7 @@ mod tests { assert_eq!(rtp.timestamp(), 0x03040506); assert_eq!(rtp.ssrc(), 0x0708090a); assert_eq!(rtp.csrc().count(), 0); - assert_eq!(rtp.extension(), None); + assert_eq!(rtp.extension::(), None); assert_eq!(rtp.payload(), &[0x0b, 0x0c, 0x0d, 0x0e]); rtp.set_marker_bit(true); @@ -254,7 +256,7 @@ mod tests { assert_eq!(rtp.timestamp(), 0x56789abc); assert_eq!(rtp.ssrc(), 0xdef01234); assert_eq!(rtp.csrc().count(), 0); - assert_eq!(rtp.extension(), None); + assert_eq!(rtp.extension::(), None); assert_eq!(rtp.payload(), &[0x0b, 0x0c]); assert_eq!(rtp.payload_len(), 2); diff --git a/src/extension.rs b/src/extension.rs new file mode 100644 index 0000000..09f2fce --- /dev/null +++ b/src/extension.rs @@ -0,0 +1,123 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! # RTP Header extensions +//! +//! An implementation of parsing, writing, and editing RTP header extensions as specified in [RFC 8285] +//! +//! [RFC 8285]: https://tools.ietf.org/html/rfc8285 + +use crate::{builder::RtpPacketWriter, RtpParseError}; + +mod mid; +pub(crate) mod one; +pub(crate) mod two; + +pub use mid::Mid; + +/// Extension trait for parsing a byte sequence into a concrete extension block. +pub trait RtpExtensionsBlock<'a> { + /// Parse the provided extension id and bytes into a concrete type. + /// + /// The ID is 'defined by the RTP profile'. + fn parse(id: u16, data: &'a [u8]) -> Result + where + Self: core::marker::Sized; +} + +/// Serialize an extension block (container format) back to wire bytes. +pub trait RtpExtensionsBlockWrite { + /// The profile-defined extension ID for this block format. + fn extension_id(&self) -> u16; + + /// The number of bytes used by this extension not including the 4 byte extension header. + fn byte_len(&self) -> usize; + + /// Write the extension data using the writer. + fn write(&self, writer: &mut impl RtpPacketWriter); +} + +/// An unparsed RTP extension block as stored in a RTP packet. +#[derive(Debug, PartialEq, Eq)] +pub struct ExtensionBlock<'a> { + ext_id: u16, + data: &'a [u8], +} + +impl<'a> ExtensionBlock<'a> { + /// The extension data. + pub fn data(&self) -> &[u8] { + self.data + } + + /// Try to parse this extension data as a more concrete implementation. + pub fn try_as>(&self) -> Result { + E::parse(self.ext_id, self.data) + } +} + +impl<'a> RtpExtensionsBlock<'a> for ExtensionBlock<'a> { + fn parse(id: u16, data: &'a [u8]) -> Result + where + Self: core::marker::Sized, + { + Ok(Self { ext_id: id, data }) + } +} + +impl RtpExtensionsBlockWrite for ExtensionBlock<'_> { + fn extension_id(&self) -> u16 { + self.ext_id + } + fn write(&self, writer: &mut impl RtpPacketWriter) { + writer.push(self.data); + } + + fn byte_len(&self) -> usize { + self.data.len() + } +} + +/// Errors produced when adding an extension +#[derive(Debug, PartialEq, Eq, thiserror::Error)] +#[non_exhaustive] +pub enum RtpExtensionWriteError { + /// The extension ID is not within the allowed range. + #[error("Extension Id ({}) is not within the allowd range[{}, {}]", .id, .range.start, .range.end)] + IdOutOfRange { + /// ID that is out of range. + id: usize, + /// Allowed range. + range: core::ops::Range, + }, + /// The extension length is not within the allowed range. + #[error("Extension length ({}) is not within the allowd range[{}, {}]", .len, .range.start, .range.end)] + LengthOutOfRange { + /// Length that is out of range. + len: usize, + /// Allowed range. + range: core::ops::Range, + }, +} + +/// Extension trait for parsing a byte sequence into a concrete extension. +pub trait RtpExtension<'a> { + /// The unique URI for this extension implementation. + const URI: &'static str; + + /// Parse the provided network bytes into a concrete extension. + fn parse(data: &'a [u8]) -> Result + where + Self: core::marker::Sized; +} + +/// Serialize an individual sub-extension into network bytes. +pub trait RtpExtensionWrite: core::fmt::Debug { + /// The unique URI identifying this extension type. + fn uri(&self) -> &str; + + /// The number of bytes used by this extension not including any headers. + fn byte_len(&self) -> usize; + + /// Write the extension data into the provided bytes. + fn write_into(&self, bytes: &mut [u8]) -> usize; +} diff --git a/src/extension/mid.rs b/src/extension/mid.rs new file mode 100644 index 0000000..69deb47 --- /dev/null +++ b/src/extension/mid.rs @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use crate::{RtpExtension, RtpExtensionWrite, RtpParseError}; + +/// The Media Idenficiation tag as specified in [RFC 8843] +/// +/// An implementation of parsing, writing, and editing The BUNDLE Media Identification tag as specified in [RFC 8843] +/// +/// [RFC 8843]: https://tools.ietf.org/html/rfc8843 +#[derive(Debug, Clone)] +pub struct Mid { + mid: String, +} + +impl Mid { + /// Create a new Media Identification tag + pub fn new(mid: String) -> Self { + Self { mid } + } + + /// The Media Identification tag. + pub fn mid(&self) -> &str { + &self.mid + } +} + +impl RtpExtension<'_> for Mid { + const URI: &'static str = "urn:ietf:params:rtp-hdrext:sdes:mid"; + + fn parse(data: &'_ [u8]) -> Result + where + Self: core::marker::Sized, + { + let mid = core::str::from_utf8(data).map_err(|_| RtpParseError::InvalidData)?; + Ok(Self { + mid: mid.to_string(), + }) + } +} + +impl RtpExtensionWrite for Mid { + fn uri(&self) -> &str { + "urn:ietf:params:rtp-hdrext:sdes:mid" + } + + fn byte_len(&self) -> usize { + self.mid.len() + } + + fn write_into(&self, bytes: &mut [u8]) -> usize { + let len = self.byte_len(); + bytes[..len].copy_from_slice(self.mid.as_bytes()); + len + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn parse_valid() { + let data = [0x20]; + let ext = Mid::parse(&data).unwrap(); + assert_eq!(ext.mid(), " "); + let mut new_data = [0; 16]; + assert_eq!(ext.write_into(&mut new_data), 1); + assert_eq!(&new_data[..1], &data); + } + + #[test] + fn write() { + let ext = Mid::new("a".to_string()); + assert_eq!(ext.mid(), "a"); + let mut new_data = [0; 16]; + assert_eq!(ext.write_into(&mut new_data), 1); + assert_eq!(&new_data[..1], b"a"); + } +} diff --git a/src/extension/one.rs b/src/extension/one.rs new file mode 100644 index 0000000..d31f17f --- /dev/null +++ b/src/extension/one.rs @@ -0,0 +1,260 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use crate::builder::RtpPacketWriter; +use crate::extension::RtpExtensionWriteError; +use crate::{RtpExtensionWrite, RtpExtensionsBlock, RtpExtensionsBlockWrite, RtpParseError}; + +/// A one byte RTP extension as specified in RFC 8285 Section 4.2. +#[derive(Debug)] +pub struct RtpSingleByteExtension<'a> { + data: &'a [u8], +} + +struct RtpSingleByteExtensionIter<'a> { + data: &'a [u8], + offset: usize, +} + +impl<'a> Iterator for RtpSingleByteExtensionIter<'a> { + type Item = (u8, &'a [u8]); + + fn next(&mut self) -> Option { + loop { + if self.offset >= self.data.len() { + return None; + } + let data = &self.data[self.offset..]; + let id = (data[0] & 0xf0) >> 4; + let len = (data[0] & 0x0f) as usize + 1; + + if self.offset + 1 + len > self.data.len() { + return None; + } + if id == 15 { + return None; + } + self.offset += 1; + + // id 0 is padding, and id 15 is reserved and all subsequent data should be ignored + if id == 0 { + continue; + } + let data = &self.data[self.offset..self.offset + len]; + self.offset += len; + return Some((id, data)); + } + } +} + +impl<'a> RtpSingleByteExtension<'a> { + /// An iterator over the values in this extension. + /// + /// Returns the extension identifier and the associated data with the extension. + pub fn iter(&self) -> impl Iterator { + RtpSingleByteExtensionIter { + data: self.data, + offset: 0, + } + } +} + +impl<'a> RtpExtensionsBlock<'a> for RtpSingleByteExtension<'a> { + fn parse(id: u16, data: &'a [u8]) -> Result { + if id != 0xbede { + return Err(RtpParseError::UnsupportedExtensionImplementation); + } + + if data.is_empty() || data.len() % 4 != 0 { + return Err(RtpParseError::Truncated { + expected: data.len() + 4 - data.len() % 4, + actual: data.len(), + }); + } + + Ok(Self { data }) + } +} + +impl<'a> RtpExtensionsBlockWrite for RtpSingleByteExtension<'a> { + fn extension_id(&self) -> u16 { + 0xbede + } + + fn byte_len(&self) -> usize { + self.data.len() + } + + fn write(&self, writer: &mut impl RtpPacketWriter) { + writer.push(self.data); + } +} + +/// Builder for single byte RTP Header extensions as specified in RFC 8285 Section 4.2. +#[derive(Debug, Default)] +pub struct RtpSingleByteExtensionBuilder { + extensions: smallvec::SmallVec<[(u8, Box); 8]>, +} + +impl RtpSingleByteExtensionBuilder { + /// Add an extension to this extension block. + pub fn push_extension( + &mut self, + id: u8, + ext: E, + ) -> Result<(), RtpExtensionWriteError> { + if !(1..15).contains(&id) { + return Err(RtpExtensionWriteError::IdOutOfRange { + id: id as usize, + range: 1..15, + }); + } + let len = ext.byte_len(); + if !(1..=16).contains(&len) { + return Err(RtpExtensionWriteError::LengthOutOfRange { len, range: 1..16 }); + } + self.extensions.push((id, Box::new(ext))); + Ok(()) + } +} + +impl RtpExtensionsBlockWrite for RtpSingleByteExtensionBuilder { + fn extension_id(&self) -> u16 { + 0xbede + } + + fn byte_len(&self) -> usize { + let len = self + .extensions + .iter() + .map(|(_id, ext)| ext.byte_len() + 1) + .sum::(); + (len + 3) & !0x3 + } + + fn write(&self, writer: &mut impl RtpPacketWriter) { + let mut data: smallvec::SmallVec<[u8; 16]> = smallvec::smallvec![0; self.byte_len()]; + let mut offset = 0; + for (id, ext) in self.extensions.iter() { + let id = *id; + data[offset] = (id & 0xf) << 4 | ((ext.byte_len() - 1) & 0xf) as u8; + offset += 1; + offset += ext.write_into(&mut data[offset..]); + } + writer.push(&data); + } +} + +#[cfg(test)] +mod tests { + use crate::{extension::Mid, RtpExtension, RtpPacketWriterVec}; + + use super::*; + + #[test] + fn parse_single_byte() { + let data = [0x00, 0x20, 0x94, 0x11, 0x15, 0x99, 0x00, 0x00]; + let ext = RtpSingleByteExtension::parse(0xbede, &data).unwrap(); + let mut it = ext.iter(); + assert_eq!(it.next(), Some((0x2, [0x94].as_ref()))); + assert_eq!(it.next(), Some((0x1, [0x15, 0x99].as_ref()))); + assert_eq!(it.next(), None); + } + + #[test] + fn parse_single_byte_id15_stops_processing() { + let data = [0x00, 0x20, 0x94, 0xf1, 0x11, 0x99, 0x00, 0x00]; + let ext = RtpSingleByteExtension::parse(0xbede, &data).unwrap(); + let mut it = ext.iter(); + assert_eq!(it.next(), Some((0x2, [0x94].as_ref()))); + assert_eq!(it.next(), None); + } + + #[test] + fn parse_single_byte_wrong_id() { + let data = [0x20, 0x94, 0x00, 0x00]; + assert!(matches!( + RtpSingleByteExtension::parse(0x0, &data), + Err(RtpParseError::UnsupportedExtensionImplementation) + )); + } + + #[test] + fn parse_single_byte_wrong_padding() { + let data = [0x20, 0x94, 0x00]; + assert!(matches!( + RtpSingleByteExtension::parse(0xbede, &data), + Err(RtpParseError::Truncated { + expected: 4, + actual: 3 + }) + )); + } + + #[test] + fn parse_serialize_roundtrip() { + let data = [0x20, 0x94, 0x11, 0x15]; + let ext = RtpSingleByteExtension::parse(0xbede, &data).unwrap(); + assert_eq!(ext.extension_id(), 0xbede); + let mut writer = RtpPacketWriterVec::default(); + ext.write(&mut writer); + assert_eq!(writer.finish(), data); + } + + #[test] + fn builder_single_extension_not_multiple_of_4() { + let ext = Mid::new("1".to_string()); + let mut block = RtpSingleByteExtensionBuilder::default(); + block.push_extension(1, ext).unwrap(); + let mut writer = RtpPacketWriterVec::default(); + block.write(&mut writer); + let data = writer.finish(); + assert_eq!(&data, &[0x10, 0x31, 0x0, 0x0]); + let ext = RtpSingleByteExtension::parse(0xbede, &data).unwrap(); + let mut it = ext.iter(); + let (id, ext_data) = it.next().unwrap(); + assert_eq!(id, 1); + let mid = Mid::parse(ext_data).unwrap(); + assert_eq!(mid.mid(), "1"); + } + + #[test] + fn builder_single_extension_multiple_of_4() { + let ext = Mid::new("123".to_string()); + let mut block = RtpSingleByteExtensionBuilder::default(); + block.push_extension(1, ext).unwrap(); + let mut writer = RtpPacketWriterVec::default(); + block.write(&mut writer); + let data = writer.finish(); + assert_eq!(&data, &[0x12, 0x31, 0x32, 0x33]); + let ext = RtpSingleByteExtension::parse(0xbede, &data).unwrap(); + let mut it = ext.iter(); + let (id, ext_data) = it.next().unwrap(); + assert_eq!(id, 1); + let mid = Mid::parse(ext_data).unwrap(); + assert_eq!(mid.mid(), "123"); + } + + #[test] + fn builder_id_out_range() { + let ext = Mid::new("1".to_string()); + let mut block = RtpSingleByteExtensionBuilder::default(); + assert!( + matches!(block.push_extension(0, ext.clone()), Err(RtpExtensionWriteError::IdOutOfRange { id: 0, range }) if range.start == 1 && range.end == 15) + ); + assert!( + matches!(block.push_extension(15, ext.clone()), Err(RtpExtensionWriteError::IdOutOfRange { id: 15, range }) if range.start == 1 && range.end == 15) + ); + assert!( + matches!(block.push_extension(0xff, ext), Err(RtpExtensionWriteError::IdOutOfRange { id: 0xff, range }) if range.start == 1 && range.end == 15) + ); + } + + #[test] + fn builder_length_out_range() { + let ext = Mid::new("".to_string()); + let mut block = RtpSingleByteExtensionBuilder::default(); + assert!( + matches!(block.push_extension(1, ext), Err(RtpExtensionWriteError::LengthOutOfRange { len: 0, range }) if range.start == 1 && range.end == 16) + ); + } +} diff --git a/src/extension/two.rs b/src/extension/two.rs new file mode 100644 index 0000000..bbaf49e --- /dev/null +++ b/src/extension/two.rs @@ -0,0 +1,255 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use crate::{ + builder::RtpPacketWriter, extension::RtpExtensionWriteError, RtpExtensionWrite, + RtpExtensionsBlock, RtpExtensionsBlockWrite, RtpParseError, +}; + +/// A two byte RTP extension as specified in RFC 8285 Section 4.3. +#[derive(Debug)] +pub struct RtpTwoByteExtension<'a> { + data: &'a [u8], + app_bits: u8, +} + +struct RtpTwoByteExtensionIter<'a> { + data: &'a [u8], + offset: usize, +} + +impl<'a> Iterator for RtpTwoByteExtensionIter<'a> { + type Item = (u8, &'a [u8]); + + fn next(&mut self) -> Option { + loop { + if self.offset + 1 >= self.data.len() { + return None; + } + let data = &self.data[self.offset..]; + let id = data[0]; + let len = data[1] as usize; + if id == 0 { + // padding byte + self.offset += 1; + continue; + }; + + if self.offset + 2 + len > self.data.len() { + return None; + } + self.offset += 2; + + let data = &self.data[self.offset..][..len]; + self.offset += len; + return Some((id, data)); + } + } +} + +impl<'a> RtpTwoByteExtension<'a> { + /// An iterator over the values in this extension. + /// + /// Returns the extension identifier and the associated data with the extension. + pub fn iter(&self) -> impl Iterator { + RtpTwoByteExtensionIter { + data: self.data, + offset: 0, + } + } + + /// Returns the application specific value. + /// + /// This value has the extension id 256 and is only valid if negotiated out of band. + pub fn app_bits(&self) -> u8 { + self.app_bits + } +} + +impl<'a> RtpExtensionsBlock<'a> for RtpTwoByteExtension<'a> { + fn parse(id: u16, data: &'a [u8]) -> Result { + if id & 0xfff0 != 0x1000 { + return Err(RtpParseError::UnsupportedExtensionImplementation); + } + + if data.is_empty() || data.len() % 4 != 0 { + return Err(RtpParseError::Truncated { + expected: data.len() + 4 - data.len() % 4, + actual: data.len(), + }); + } + + Ok(Self { + data, + app_bits: (id & 0x0000f) as u8, + }) + } +} + +impl<'a> RtpExtensionsBlockWrite for RtpTwoByteExtension<'a> { + fn extension_id(&self) -> u16 { + 0x1000 | ((self.app_bits & 0x0f) as u16) + } + + fn byte_len(&self) -> usize { + self.data.len() + } + + fn write( + &self, + writer: &mut impl crate::prelude::RtpPacketWriter, + ) { + writer.push(self.data); + } +} + +/// Builder for single byte RTP Header extensions as specified in RFC 8285 Section 4.2. +#[derive(Debug, Default)] +pub struct RtpTwoByteExtensionBuilder { + app_bits: u8, + extensions: smallvec::SmallVec<[(u8, Box); 8]>, +} + +impl RtpTwoByteExtensionBuilder { + /// Add an extension to this extension block. + pub fn push_extension( + &mut self, + id: u8, + ext: E, + ) -> Result<(), RtpExtensionWriteError> { + if !(1..255).contains(&id) { + return Err(RtpExtensionWriteError::IdOutOfRange { + id: id as usize, + range: 1..255, + }); + } + self.extensions.push((id, Box::new(ext))); + Ok(()) + } + + /// Set the application bits on the created RTP header extension. + /// + /// Panics: + /// - if `app_bits` >= 0x10 + pub fn app_bits(&mut self, app_bits: u8) { + assert!(app_bits <= 0xf); + self.app_bits = app_bits; + } +} + +impl RtpExtensionsBlockWrite for RtpTwoByteExtensionBuilder { + fn extension_id(&self) -> u16 { + 0x1000 | self.app_bits as u16 + } + + fn byte_len(&self) -> usize { + let len = self + .extensions + .iter() + .map(|(_id, ext)| ext.byte_len() + 2) + .sum::(); + (len + 3) & !0x3 + } + + fn write(&self, writer: &mut impl RtpPacketWriter) { + let mut data: smallvec::SmallVec<[u8; 16]> = smallvec::smallvec![0; self.byte_len()]; + let mut offset = 0; + for (id, ext) in self.extensions.iter() { + let id = *id; + data[offset] = id; + offset += 1; + data[offset] = ext.byte_len() as u8; + offset += 1; + offset += ext.write_into(&mut data[offset..]); + } + writer.push(&data); + } +} + +#[cfg(test)] +mod tests { + use crate::{builder::RtpPacketWriter, extension::Mid, RtpExtension, RtpPacketWriterVec}; + + use super::*; + + #[test] + fn parse_two_byte() { + let data = [0x02, 0x01, 0x94, 0x00]; + let ext = RtpTwoByteExtension::parse(0x100a, &data).unwrap(); + let mut it = ext.iter(); + assert_eq!(it.next(), Some((0x2, [0x94].as_ref()))); + assert_eq!(it.next(), None); + } + + #[test] + fn parse_two_byte_with_app() { + let data = [0x00, 0x02, 0x01, 0x94]; + let ext = RtpTwoByteExtension::parse(0x100a, &data).unwrap(); + let mut it = ext.iter(); + assert_eq!(it.next(), Some((0x2, [0x94].as_ref()))); + assert_eq!(it.next(), None); + assert_eq!(ext.app_bits(), 0xa); + } + + #[test] + fn parse_serialize_roundtrip() { + let data = [0x02, 0x01, 0x94, 0x00]; + let ext = RtpTwoByteExtension::parse(0x1000, &data).unwrap(); + assert_eq!(ext.extension_id(), 0x1000); + let mut writer = RtpPacketWriterVec::default(); + ext.write(&mut writer); + assert_eq!(writer.finish(), data); + } + + #[test] + fn parse_serialize_roundtrip_with_app() { + let data = [0x00, 0x02, 0x01, 0x94]; + let ext = RtpTwoByteExtension::parse(0x100a, &data).unwrap(); + assert_eq!(ext.extension_id(), 0x100a); + let mut writer = RtpPacketWriterVec::default(); + ext.write(&mut writer); + assert_eq!(writer.finish(), data); + } + + #[test] + fn builder_single_extension_not_multiple_of_4() { + let ext = Mid::new("1".to_string()); + let mut block = RtpTwoByteExtensionBuilder::default(); + block.push_extension(1, ext).unwrap(); + let mut writer = RtpPacketWriterVec::default(); + block.write(&mut writer); + let data = writer.finish(); + assert_eq!(&data, &[0x1, 0x1, 0x31, 0x0]); + let ext = RtpTwoByteExtension::parse(0x1000, &data).unwrap(); + let mut it = ext.iter(); + let (id, ext_data) = it.next().unwrap(); + assert_eq!(id, 1); + let mid = Mid::parse(ext_data).unwrap(); + assert_eq!(mid.mid(), "1"); + } + + #[test] + fn builder_single_extension_multiple_of_4() { + let ext = Mid::new("12".to_string()); + let mut block = RtpTwoByteExtensionBuilder::default(); + block.push_extension(1, ext).unwrap(); + let mut writer = RtpPacketWriterVec::default(); + block.write(&mut writer); + let data = writer.finish(); + assert_eq!(&data, &[0x1, 0x2, 0x31, 0x32]); + let ext = RtpTwoByteExtension::parse(0x1000, &data).unwrap(); + let mut it = ext.iter(); + let (id, ext_data) = it.next().unwrap(); + assert_eq!(id, 1); + let mid = Mid::parse(ext_data).unwrap(); + assert_eq!(mid.mid(), "12"); + } + + #[test] + fn builder_id_out_range() { + let ext = Mid::new("1".to_string()); + let mut block = RtpTwoByteExtensionBuilder::default(); + assert!( + matches!(block.push_extension(0, ext.clone()), Err(RtpExtensionWriteError::IdOutOfRange { id: 0, range }) if range.start == 1 && range.end == 255) + ); + } +} diff --git a/src/lib.rs b/src/lib.rs index 719f87b..d936766 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,6 +11,7 @@ mod builder; mod edit; +pub mod extension; mod packet; pub use builder::{ @@ -18,9 +19,15 @@ pub use builder::{ RtpWriteError, }; pub use edit::RtpPacketMut; +pub use extension::one::{RtpSingleByteExtension, RtpSingleByteExtensionBuilder}; +pub use extension::two::{RtpTwoByteExtension, RtpTwoByteExtensionBuilder}; +pub use extension::{RtpExtension, RtpExtensionWrite, RtpExtensionsBlock, RtpExtensionsBlockWrite}; pub use packet::{RtpPacket, RtpParseError}; /// Prelude module for defined/implementable traits pub mod prelude { pub use crate::builder::{PayloadLength, RtpPacketWriter}; + pub use crate::extension::{ + RtpExtension, RtpExtensionWrite, RtpExtensionsBlock, RtpExtensionsBlockWrite, + }; } diff --git a/src/packet.rs b/src/packet.rs index b6ea01e..e671ab2 100644 --- a/src/packet.rs +++ b/src/packet.rs @@ -2,6 +2,8 @@ use std::fmt; +use crate::{extension::ExtensionBlock, RtpExtensionsBlock}; + /// An error produced when parsing a packet. #[derive(Debug, thiserror::Error)] #[non_exhaustive] @@ -20,6 +22,12 @@ pub enum RtpParseError { /// The padding byte does not contain a valid value. #[error("Padding contains invalid value {}", .0)] PaddingInvalid(u8), + /// The extension identifier is not supported by this implementation. + #[error("Unsupported extension implementation")] + UnsupportedExtensionImplementation, + /// The extension data is invalid. + #[error("Invalid extension data")] + InvalidData, } /// A parsed RTP packet. A wrapper around a byte slice. Each field is only accessed when needed. @@ -52,7 +60,7 @@ impl fmt::Debug for RtpPacket<'_> { .field("timestamp", &self.timestamp()) .field("ssrc", &self.ssrc()) .field("csrc", &DebugCsrc(self)) - .field("extension", &self.extension()) + .field("extension", &self.extension::()) .field("payload", &self.payload()) .field("padding", &self.padding()) .finish() @@ -233,12 +241,12 @@ impl<'a> RtpPacket<'a> { /// Returns the extension data for this packet. The first value is an identifier and is /// 'defined by the RTP profile'. The second value is the extension data. - pub fn extension(&self) -> Option<(u16, &[u8])> { + pub fn extension>(&self) -> Option { if self.extension_bit() { let offset = self.extension_offset(); let id = (self.data[offset] as u16) << 8 | self.data[offset + 1] as u16; let offset = offset + 4; - Some((id, &self.data[offset..][..self.extension_len()])) + E::parse(id, &self.data[offset..][..self.extension_len()]).ok() } else { None } @@ -271,7 +279,7 @@ impl<'a> RtpPacket<'a> { /// Creates a builder that will be able to reconstruct this packet byte for byte (excluding /// any padding bytes). Any aspect of the returned builder can be modified. - pub fn as_builder(&'a self) -> crate::RtpPacketBuilder<&'a [u8], &'a [u8]> { + pub fn as_builder(&'a self) -> crate::RtpPacketBuilder<&'a [u8], ExtensionBlock<'a>> { let mut builder = crate::RtpPacketBuilder::new() .marker_bit(self.marker_bit()) .payload_type(self.payload_type()) @@ -282,8 +290,8 @@ impl<'a> RtpPacket<'a> { for csrc in self.csrc() { builder = builder.add_csrc(csrc); } - if let Some((ext_id, ext_data)) = self.extension() { - builder = builder.extension(ext_id, ext_data); + if let Some(ext) = self.extension() { + builder = builder.extension(ext); } if let Some(padding) = self.padding() { builder = builder.padding(padding); @@ -311,8 +319,8 @@ mod tests { assert_eq!(rtp.timestamp(), 0x03040506); assert_eq!(rtp.ssrc(), 0x07080910); assert_eq!(rtp.csrc().count(), 0); - assert_eq!(rtp.extension(), None); - assert_eq!(rtp.payload(), &[]); + assert_eq!(rtp.extension::(), None); + assert!(rtp.payload().is_empty()); let built = rtp.as_builder().write_vec().unwrap(); assert_eq!(built, data.as_ref()); } @@ -349,8 +357,8 @@ mod tests { let mut csrc = rtp.csrc(); assert_eq!(csrc.next(), Some(0x11121314)); assert_eq!(csrc.next(), None); - assert_eq!(rtp.extension(), None); - assert_eq!(rtp.payload(), &[]); + assert_eq!(rtp.extension::(), None); + assert!(rtp.payload().is_empty()); let built = rtp.as_builder().write_vec().unwrap(); assert_eq!(built, data.as_ref()); } @@ -388,9 +396,9 @@ mod tests { assert_eq!(rtp.csrc().count(), 0); assert_eq!( rtp.extension(), - Some((0x0b0c, [0x0d, 0x0e, 0x0f, 0x10].as_ref())) + ExtensionBlock::parse(0x0b0c, [0x0d, 0x0e, 0x0f, 0x10].as_ref()).ok() ); - assert_eq!(rtp.payload(), &[]); + assert!(rtp.payload().is_empty()); let built = rtp.as_builder().write_vec().unwrap(); assert_eq!(built, data.as_ref()); } @@ -441,7 +449,7 @@ mod tests { assert_eq!(rtp.timestamp(), 0x03040506); assert_eq!(rtp.ssrc(), 0x0708090a); assert_eq!(rtp.csrc().count(), 0); - assert_eq!(rtp.extension(), None); + assert_eq!(rtp.extension::(), None); assert_eq!(rtp.payload(), &[0x0b, 0x0c, 0x0d, 0x0e]); let built = rtp.as_builder().write_vec().unwrap(); assert_eq!(built, data.as_ref()); @@ -463,7 +471,7 @@ mod tests { assert_eq!(rtp.timestamp(), 0x03040506); assert_eq!(rtp.ssrc(), 0x0708090a); assert_eq!(rtp.csrc().count(), 0); - assert_eq!(rtp.extension(), None); + assert_eq!(rtp.extension::(), None); assert_eq!(rtp.payload(), &[0x0b, 0x0c]); assert_eq!(rtp.payload_len(), 2); let built = rtp.as_builder().write_vec().unwrap();