├── .github ├── dependabot.yml └── workflows │ └── rust.yml ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── README.md ├── benches └── bench.rs ├── fuzz ├── .gitignore ├── Cargo.toml └── fuzz_targets │ └── fuzz_target_1.rs ├── release.toml └── src ├── builder.rs ├── lib.rs └── reader.rs /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | open-pull-requests-limit: 10 8 | 9 | - package-ecosystem: github-actions 10 | directory: / 11 | schedule: 12 | interval: weekly -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v4 12 | - name: Check formatting 13 | run: cargo fmt --check 14 | - name: Build 15 | run: cargo build --verbose 16 | - name: Run tests 17 | run: cargo test --verbose --all-targets 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | .idea 4 | rtp-rs.iml 5 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # ChangeLog 2 | 3 | ## Unreleased 4 | 5 | ### Changed 6 | - Switched to Rust 2021 edition. 7 | 8 | ### Added 9 | - `RtpPacketBuildError` and `RtpReaderError` now implement `std::error::Error` 10 | 11 | ## 0.6.0 - 2021-05-13 12 | ### Added 13 | - The `Seq` type now implements `Ord` (in addition to the `PartialOrd` implementation that was already available). 14 | - [@WolverinDEV](https://github.com/WolverinDEV) added an `RtpPacketBuilder` 🎊 15 | 16 | ### Changed 17 | - `RtpHeaderError` was renamed to `RtpReaderError` (the type relates to read-failures specifically, and we now 18 | have support for writing too) 19 | - `RtpHeader::padding()` now returns `Option`, rather than `bool`, exposing how many padding bytes are present as 20 | well as the fact that padding is in use. 21 | - We no longer reject RTP packets that contain headers but no payload 22 | 23 | ## 0.5.0 - 2019-09-29 24 | ### Fixed 25 | - Trailing padding-bytes, indicated by the `padding` flag in the RTP header, are now excluded from the data returned by 26 | the `payload()` method. 27 | - Since we now pay attention to padding data, `RtpReader::new()` will fail if the indicated padding length is 28 | nonsensical (less than one byte, or greater than the available space). 29 | ### Changed 30 | - `RtpHeaderError` gains a new `PaddingLengthInvalid` variant 31 | - The `Seq` methods `next()` and `precedes()` now both take `self` by value, which _clippy_ points out should be more 32 | efficient, 33 | ### Added 34 | - `csrc()` method to expose any CSRC header values that might be present (rarely used RTP feature). 35 | - API docs are now provided for all public items 36 | 37 | ## 0.4.0 - 2019-09-25 38 | ### Changed 39 | - `RtpReader::extension()` no longer returns the boolean flag indicating if an extension is present, and instead 40 | produces an `Option` which is `Some` when an extension header is present. The `Option` contains a `(u16, &[u8])` 41 | tuple when an extension header is present, where the `u16` value is the extension header id, and the byte-slice is 42 | the extension header payload. 43 | ### Fixed 44 | - Should no longer panic if the RTP packet length is not sufficient to contain all 4 bytes of an extension header 45 | (where the extension header is flagged to be present) -- thanks to [@ts252](https://github.com/ts252) for spotting. 46 | - Extension header lengths where incorrectly calculated in earlier releases, which would have resulted in invalid RTP 47 | payload data being produced for any packets that actually had an extension (the extension length header is a count of 48 | 32-bit values, not a count of 8-bit values as the previous implementation had assumed). 49 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rtp-rs" 3 | version = "0.6.1-dev" 4 | authors = ["David Holroyd ", "Markus Hadenfeldt "] 5 | keywords = [ "RTP", "rfc3550" ] 6 | description = "Parser and generator for RTP packet structure" 7 | repository = "https://github.com/dholroyd/rtp-rs" 8 | license = "MIT/Apache-2.0" 9 | categories = [ "multimedia::video", "parser-implementations", "network-programming" ] 10 | edition = "2021" 11 | readme = "README.md" 12 | 13 | [lib] 14 | name = "rtp_rs" 15 | path = "src/lib.rs" 16 | 17 | [dev-dependencies] 18 | criterion = "0.5" 19 | 20 | [[bench]] 21 | name = "bench" 22 | harness = false 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rtp-rs 2 | 3 | [![crates.io version](https://img.shields.io/crates/v/rtp-rs.svg)](https://crates.io/crates/rtp-rs) 4 | [![Documentation](https://docs.rs/rtp-rs/badge.svg)](https://docs.rs/rtp-rs) 5 | 6 | [ChangeLog](https://github.com/dholroyd/rtp-rs/blob/master/CHANGELOG.md) 7 | 8 | Rust reader and builder for Realtime Transport Protocol packet structure. 9 | 10 | This crate provides efficient read access to the fields and payload of an RTP packet. 11 | The provided type is just a wrapper around a `&[u8]` borrowed byte slice; It is zero-copy 12 | and zero-allocation, with RTP header fields only being read if calling code actually uses 13 | a given accessor method. 14 | 15 | Does not support actually reading UDP from the network, or any kind or RTP session management 16 | (i.e. no buffering to handle packet reordering, loss notification, pacing, etc.) 17 | 18 | # Supported RTP syntax 19 | 20 | - [x] reading 21 | - [x] all simple header fields 22 | - [x] extension header (if present, the `u16` identifier and `&[u8]`value are exposed without further interpretation) 23 | - [x] building 24 | - [x] all simple header fields 25 | - [x] extension header 26 | - [x] padding to specified number of bytes 27 | 28 | -------------------------------------------------------------------------------- /benches/bench.rs: -------------------------------------------------------------------------------- 1 | use criterion::*; 2 | use rtp_rs::{RtpPacketBuilder, RtpReader}; 3 | 4 | fn rtp_reader(c: &mut Criterion) { 5 | let data = [ 6 | 0x80u8, 0xe0u8, 0x27u8, 0x38u8, 0x64u8, 0xe4u8, 0x05u8, 0xa7u8, 0xa2u8, 0x42u8, 0xafu8, 7 | 0x01u8, 0x3cu8, 0x41u8, 0xa4u8, 0xa3u8, 0x5du8, 0x13u8, 0xf9u8, 0xcau8, 0x2cu8, 0x7eu8, 8 | 0xa9u8, 0x77u8, 0xaau8, 0xdeu8, 0xf7u8, 0xcau8, 0xa4u8, 0x28u8, 0xfeu8, 0xdfu8, 0xc8u8, 9 | 0x68u8, 0xf1u8, 0xd9u8, 0x4fu8, 0x69u8, 0x96u8, 0xa0u8, 0x57u8, 0xbau8, 0xfbu8, 0x07u8, 10 | 0xc4u8, 0xc4u8, 0xd4u8, 0xfeu8, 0xf8u8, 0xc7u8, 0xb2u8, 0x0du8, 0x01u8, 0x12u8, 0x14u8, 11 | 0x36u8, 0x69u8, 0x75u8, 0xf2u8, 0xb4u8, 0xb5u8, 0xf2u8, 0x54u8, 0x2eu8, 0xc2u8, 0x66u8, 12 | 0x51u8, 0xebu8, 0x41u8, 0x80u8, 0x96u8, 0xceu8, 0x8eu8, 0x60u8, 0xb2u8, 0x44u8, 0xaeu8, 13 | 0xe5u8, 0x43u8, 0xadu8, 0x7bu8, 0x48u8, 0x89u8, 0x44u8, 0xb0u8, 0x48u8, 0x67u8, 0x6au8, 14 | 0x84u8, 0x7au8, 0x0au8, 0x8fu8, 0x71u8, 0x50u8, 0x69u8, 0xe6u8, 0xb1u8, 0x05u8, 0x40u8, 15 | 0xb9u8, 0x8cu8, 0xafu8, 0x42u8, 0xcbu8, 0x58u8, 0x83u8, 0xcbu8, 0x32u8, 0x64u8, 0xd2u8, 16 | 0x2au8, 0x7du8, 0x4eu8, 0xf5u8, 0xbcu8, 0x33u8, 0xfeu8, 0xb7u8, 0x0cu8, 0xe4u8, 0x8eu8, 17 | 0x38u8, 0xbcu8, 0x3au8, 0x1eu8, 0xd2u8, 0x56u8, 0x13u8, 0x23u8, 0x47u8, 0xcfu8, 0x42u8, 18 | 0xa9u8, 0xbbu8, 0xcfu8, 0x48u8, 0xf3u8, 0x11u8, 0xc7u8, 0xfdu8, 0x73u8, 0x2du8, 0xe1u8, 19 | 0xeau8, 0x47u8, 0x5cu8, 0x5du8, 0x11u8, 0x96u8, 0x1eu8, 0xc4u8, 0x70u8, 0x32u8, 0x77u8, 20 | 0xabu8, 0x31u8, 0x7au8, 0xb1u8, 0x22u8, 0x14u8, 0x8du8, 0x2bu8, 0xecu8, 0x3du8, 0x67u8, 21 | 0x97u8, 0xa4u8, 0x40u8, 0x21u8, 0x1eu8, 0xceu8, 0xb0u8, 0x63u8, 0x01u8, 0x75u8, 0x77u8, 22 | 0x03u8, 0x15u8, 0xcdu8, 0x35u8, 0xa1u8, 0x2fu8, 0x4bu8, 0xa0u8, 0xacu8, 0x8du8, 0xd7u8, 23 | 0x78u8, 0x02u8, 0x23u8, 0xcbu8, 0xfdu8, 0x82u8, 0x4eu8, 0x0bu8, 0x79u8, 0x7fu8, 0x39u8, 24 | 0x70u8, 0x26u8, 0x66u8, 0x37u8, 0xe9u8, 0x93u8, 0x91u8, 0x7bu8, 0xc4u8, 0x80u8, 0xa9u8, 25 | 0x18u8, 0x23u8, 0xb3u8, 0xa1u8, 0x04u8, 0x72u8, 0x53u8, 0xa0u8, 0xb4u8, 0xffu8, 0x79u8, 26 | 0x1fu8, 0x07u8, 0xe2u8, 0x5du8, 0x01u8, 0x7du8, 0x63u8, 0xc1u8, 0x16u8, 0x89u8, 0x23u8, 27 | 0x4au8, 0x17u8, 0xbbu8, 0x6du8, 0x0du8, 0x81u8, 0x1au8, 0xbbu8, 0x94u8, 0x5bu8, 0xcbu8, 28 | 0x2du8, 0xdeu8, 0x98u8, 0x40u8, 0x22u8, 0x62u8, 0x41u8, 0xc2u8, 0x9bu8, 0x95u8, 0x85u8, 29 | 0x60u8, 0xf0u8, 0xdeu8, 0x6fu8, 0xeeu8, 0x93u8, 0xccu8, 0x15u8, 0x76u8, 0xfbu8, 0xf8u8, 30 | 0x8au8, 0x1du8, 0xe1u8, 0x83u8, 0x12u8, 0xabu8, 0x25u8, 0x6au8, 0x7bu8, 0x89u8, 0xedu8, 31 | 0x70u8, 0x4eu8, 0xcdu8, 0x1eu8, 0xa9u8, 0xfcu8, 0xa8u8, 0x22u8, 0x91u8, 0x5fu8, 0x50u8, 32 | 0x68u8, 0x6au8, 0x35u8, 0xf7u8, 0xc1u8, 0x1eu8, 0x15u8, 0x37u8, 0xb4u8, 0x30u8, 0x62u8, 33 | 0x56u8, 0x1eu8, 0x2eu8, 0xe0u8, 0x2du8, 0xa4u8, 0x1eu8, 0x75u8, 0x5bu8, 0xc7u8, 0xd0u8, 34 | 0x5bu8, 0x9du8, 0xd0u8, 0x25u8, 0x76u8, 0xdfu8, 0xa7u8, 0x19u8, 0x12u8, 0x93u8, 0xf4u8, 35 | 0xebu8, 0x02u8, 0xf2u8, 0x4au8, 0x13u8, 0xe9u8, 0x1cu8, 0x17u8, 0xccu8, 0x11u8, 0x87u8, 36 | 0x9cu8, 0xa6u8, 0x40u8, 0x27u8, 0xb7u8, 0x2bu8, 0x9bu8, 0x6fu8, 0x23u8, 0x06u8, 0x2cu8, 37 | 0xc6u8, 0x6eu8, 0xc1u8, 0x9au8, 0xbdu8, 0x59u8, 0x37u8, 0xe9u8, 0x9eu8, 0x76u8, 0xf6u8, 38 | 0xc1u8, 0xbcu8, 0x81u8, 0x18u8, 0x60u8, 0xc9u8, 0x64u8, 0x0au8, 0xb3u8, 0x6eu8, 0xf3u8, 39 | 0x6bu8, 0xb9u8, 0xd0u8, 0xf6u8, 0xe0u8, 0x9bu8, 0x91u8, 0xc1u8, 0x0fu8, 0x96u8, 0xefu8, 40 | 0xbcu8, 0x5fu8, 0x8eu8, 0x86u8, 0x56u8, 0x5au8, 0xfcu8, 0x7au8, 0x8bu8, 0xddu8, 0x9au8, 41 | 0x1cu8, 0xf6u8, 0xb4u8, 0x85u8, 0xf4u8, 0xb0u8, 42 | ]; 43 | let mut group = c.benchmark_group("parse"); 44 | group.throughput(Throughput::Bytes(data.len() as u64)); 45 | group.bench_function("parse", move |b| { 46 | b.iter(|| { 47 | let header = RtpReader::new(&data).unwrap(); 48 | assert_eq!(2, header.version()); 49 | assert!(header.padding().is_none()); 50 | assert!(header.extension().is_none()); 51 | assert_eq!(0, header.csrc_count()); 52 | assert!(header.mark()); 53 | assert_eq!(96, header.payload_type()); 54 | assert_eq!(10040, u16::from(header.sequence_number())); 55 | assert_eq!(1_692_665_255, header.timestamp()); 56 | assert_eq!(0xa242_af01, header.ssrc()); 57 | assert_eq!(379, header.payload().len()); 58 | }); 59 | }); 60 | } 61 | 62 | fn rtp_builder(c: &mut Criterion) { 63 | let payload = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; 64 | c.bench_function("builder", move |b| { 65 | b.iter(|| { 66 | let _result = RtpPacketBuilder::new() 67 | .payload_type(12) 68 | .payload(&payload) 69 | .marked(true) 70 | .add_csrc(12) 71 | .build() 72 | .unwrap(); 73 | }); 74 | }); 75 | } 76 | 77 | criterion_group!(benches, rtp_reader, rtp_builder); 78 | criterion_main!(benches); 79 | -------------------------------------------------------------------------------- /fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | target 3 | corpus 4 | artifacts 5 | coverage 6 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rtp-rs-fuzz" 3 | version = "0.0.1" 4 | authors = ["Automatically generated"] 5 | publish = false 6 | edition = "2018" 7 | 8 | [package.metadata] 9 | cargo-fuzz = true 10 | 11 | [dependencies.rtp-rs] 12 | path = ".." 13 | [dependencies.libfuzzer-sys] 14 | git = "https://github.com/rust-fuzz/libfuzzer-sys.git" 15 | 16 | # Prevent this from interfering with workspaces 17 | [workspace] 18 | members = ["."] 19 | 20 | [[bin]] 21 | name = "fuzz_target_1" 22 | path = "fuzz_targets/fuzz_target_1.rs" 23 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/fuzz_target_1.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use libfuzzer_sys::fuzz_target; 3 | 4 | fuzz_target!(|data: &[u8]| { 5 | if let Ok(header) = rtp_rs::RtpReader::new(data) { 6 | format!("{:?}", header); 7 | let b = header.create_builder(); 8 | let len = b.target_length(); 9 | let mut out = vec![0u8; len]; 10 | b.build_into(&mut out[..]).expect("build_into() failed"); 11 | if let Some(padding) = header.padding() { 12 | let padding = padding as usize; 13 | assert_eq!(data[0] & 0b1101_1111, out[0]); 14 | assert_eq!(&data[1..data.len()-padding], &out[1..len]); 15 | } else { 16 | assert_eq!(&data[..len], &out[..]); 17 | } 18 | } 19 | }); 20 | -------------------------------------------------------------------------------- /release.toml: -------------------------------------------------------------------------------- 1 | pre-release-replacements = [ 2 | {file="CHANGELOG.md", search="## Unreleased", replace="## {{version}} - {{date}}", exactly=1}, 3 | ] 4 | post-release-replacements = [ 5 | {file="CHANGELOG.md", search="# ChangeLog", replace="# ChangeLog\n\n## Unreleased", exactly=1}, 6 | ] -------------------------------------------------------------------------------- /src/builder.rs: -------------------------------------------------------------------------------- 1 | use crate::Seq; 2 | 3 | /// Reasons why `RtpPacketBuilder::build_info` fails 4 | #[derive(Debug, Ord, PartialOrd, Eq, PartialEq)] 5 | pub enum RtpPacketBuildError { 6 | /// The target buffer is too small for the RTP packet 7 | BufferTooSmall, 8 | 9 | /// The given payload type is invalid or not set 10 | PayloadTypeInvalid, 11 | 12 | /// The extension payload is too large 13 | ExtensionTooLarge, 14 | 15 | /// The extension payload hasn't been padded to a four byte boundary 16 | ExtensionMissingPadding, 17 | } 18 | 19 | // until we have https://github.com/rust-lang/rust/issues/51999 I think 20 | macro_rules! const_assert { 21 | ($x:expr $(,)?) => { 22 | #[allow(unknown_lints, clippy::eq_op)] 23 | { 24 | const ASSERT: [(); 1] = [()]; 25 | let _ = ASSERT[!($x) as usize]; 26 | } 27 | }; 28 | } 29 | 30 | /// Controls if and how an RTP packet should have padding appended after the payload 31 | /// 32 | /// For example to have the builder add padding if required so that packet lengths are always a 33 | /// multiple of 4 bytes: 34 | /// 35 | /// ``` 36 | /// # use rtp_rs::{RtpPacketBuilder, Pad}; 37 | /// let mut builder = RtpPacketBuilder::new() 38 | /// .padded(Pad::round_to(4)); 39 | /// // configure the rest of the packet fields and then build the packet 40 | /// ``` 41 | pub struct Pad(PadInner); 42 | 43 | impl Pad { 44 | /// No padding should be added, and the `padding` flag in the header should not be set 45 | pub const fn none() -> Self { 46 | Pad(PadInner::None) 47 | } 48 | /// Add padding bytes so that the resulting packet length will be a multiple of the given 49 | /// value, and set the `padding` flag in the packet header 50 | /// 51 | /// Panics if the given value is less than 2. 52 | pub const fn round_to(pad: u8) -> Self { 53 | const_assert!(pad >= 2); 54 | Pad(PadInner::RoundTo(pad)) 55 | } 56 | } 57 | 58 | // we hide the enum so that calling code can't set tuple-variant values directly, bypassing our 59 | // checks for invalid values. 60 | #[derive(Clone)] 61 | enum PadInner { 62 | None, 63 | RoundTo(u8), 64 | } 65 | 66 | impl PadInner { 67 | pub fn adjust_len(&self, initial_len: usize) -> Option { 68 | match self { 69 | PadInner::None => None, 70 | PadInner::RoundTo(n) => { 71 | let remainder = initial_len % *n as usize; 72 | if remainder == 0 { 73 | None 74 | } else { 75 | Some(*n as usize - remainder) 76 | } 77 | } 78 | } 79 | } 80 | } 81 | 82 | /// A new packet build which collects the data which should be written as RTP packet 83 | #[derive(Clone)] 84 | pub struct RtpPacketBuilder<'a> { 85 | padded: PadInner, 86 | marked: bool, 87 | payload_type: u8, 88 | 89 | extension: Option<(u16, &'a [u8])>, 90 | payload: Option<&'a [u8]>, 91 | 92 | sequence: Seq, 93 | timestamp: u32, 94 | 95 | ssrc: u32, 96 | csrcs: [u32; 15], 97 | csrc_count: u8, 98 | } 99 | 100 | impl<'a> Default for RtpPacketBuilder<'a> { 101 | fn default() -> Self { 102 | Self::new() 103 | } 104 | } 105 | 106 | impl<'a> RtpPacketBuilder<'a> { 107 | /// Create a new RTP packet builder 108 | pub fn new() -> Self { 109 | RtpPacketBuilder { 110 | padded: PadInner::None, 111 | marked: false, 112 | /* 113 | * Setting it to an invalid value enforces the user to set the payload type. 114 | * This will cause the build method to fail if it hasn't been updated. 115 | */ 116 | payload_type: 0xFF, 117 | 118 | extension: None, 119 | payload: None, 120 | 121 | sequence: Seq::from(0), 122 | timestamp: 0, 123 | 124 | ssrc: 0, 125 | csrcs: [0u32; 15], 126 | csrc_count: 0, 127 | } 128 | } 129 | 130 | /// Set the payload type. 131 | /// The type must be in range of [0; 127], 132 | /// else `RtpPacketBuilder::build_info` will fail. 133 | pub fn payload_type(mut self, payload_type: u8) -> Self { 134 | self.payload_type = payload_type; 135 | self 136 | } 137 | 138 | /// Control if and how bytes are appended to the packet if the headers and payload together 139 | /// do not have an appropriate length (for instance if the length of the resulting RTP data 140 | /// must be a multiple of 4 bytes). 141 | /// 142 | /// The default is `Pad::none()` - no padding bytes will be added and the padding flag will not 143 | /// be set in the RTP header. 144 | pub fn padded(mut self, pad: Pad) -> Self { 145 | self.padded = pad.0; 146 | self 147 | } 148 | 149 | /// Set the marker bit in the RTP header 150 | pub fn marked(mut self, flag: bool) -> Self { 151 | self.marked = flag; 152 | self 153 | } 154 | 155 | /// Add a contributing source (csrc). 156 | /// If added more than 15 contributing sources the rest will be discarded. 157 | pub fn add_csrc(mut self, csrc: u32) -> Self { 158 | if self.csrc_count == 15 { 159 | /* The limit of contributing sources is 15. Any more should be discarded. */ 160 | self 161 | } else { 162 | self.csrcs[self.csrc_count as usize] = csrc; 163 | self.csrc_count += 1; 164 | self 165 | } 166 | } 167 | 168 | /// Set the contributing sources (csrc). 169 | /// If added more than 15 contributing sources the rest will be discarded. 170 | pub fn set_csrc(mut self, csrcs: &[u32]) -> Self { 171 | if csrcs.len() > 15 { 172 | self.csrc_count = 15; 173 | } else { 174 | self.csrc_count = csrcs.len() as u8; 175 | } 176 | 177 | self.csrcs[0..self.csrc_count as usize].copy_from_slice(csrcs); 178 | self 179 | } 180 | 181 | /// Set the sequence number 182 | pub fn sequence(mut self, seq: Seq) -> Self { 183 | self.sequence = seq; 184 | self 185 | } 186 | 187 | /// Set the source for this packet 188 | pub fn ssrc(mut self, ssrc: u32) -> Self { 189 | self.ssrc = ssrc; 190 | self 191 | } 192 | 193 | /// Set the timestamp 194 | pub fn timestamp(mut self, timestamp: u32) -> Self { 195 | self.timestamp = timestamp; 196 | self 197 | } 198 | 199 | /// Add a custom extension payload. 200 | /// The bytes should be aligned to a a four byte boundary, 201 | /// else `RtpPacketBuilder::build_info` will fail. 202 | pub fn extension(mut self, id: u16, payload: &'a [u8]) -> Self { 203 | self.extension = Some((id, payload)); 204 | self 205 | } 206 | 207 | /// Set the payload of the packet 208 | pub fn payload(mut self, payload: &'a [u8]) -> Self { 209 | self.payload = Some(payload); 210 | self 211 | } 212 | 213 | /// Calculate the target length of the packet. 214 | /// This can be used to allocate a buffer for the `build_into` method. 215 | pub fn target_length(&self) -> usize { 216 | /* 12 is the length of the basic header */ 217 | let mut length = 12usize; 218 | length += self.csrc_count as usize * 4; 219 | length += if let Some((_, ext)) = self.extension { 220 | ext.len() + 4 221 | } else { 222 | 0 223 | }; 224 | length += if let Some(payload) = self.payload { 225 | payload.len() 226 | } else { 227 | 0 228 | }; 229 | if let Some(adj) = self.padded.adjust_len(length) { 230 | length += adj; 231 | } 232 | length 233 | } 234 | 235 | /// Build the packet into the target buffer but ignore all validity checks. 236 | pub fn build_into_unchecked(&self, target: &mut [u8]) -> usize { 237 | let first_byte = &mut target[0]; 238 | *first_byte = 2 << 6; /* The RTP packet version */ 239 | if self.extension.is_some() { 240 | *first_byte |= 1 << 4; /* set the extension flag */ 241 | } 242 | *first_byte |= self.csrc_count; 243 | 244 | target[1] = self.payload_type; 245 | if self.marked { 246 | target[1] |= 0x80; 247 | } 248 | 249 | target[2] = (self.sequence.0 >> 8) as u8; 250 | target[3] = (self.sequence.0 & 0xFF) as u8; 251 | 252 | target[4] = (self.timestamp >> 24) as u8; 253 | target[5] = (self.timestamp >> 16) as u8; 254 | target[6] = (self.timestamp >> 8) as u8; 255 | target[7] = (self.timestamp) as u8; 256 | 257 | target[8] = (self.ssrc >> 24) as u8; 258 | target[9] = (self.ssrc >> 16) as u8; 259 | target[10] = (self.ssrc >> 8) as u8; 260 | target[11] = (self.ssrc) as u8; 261 | 262 | let mut write_index = 12usize; 263 | for index in 0..self.csrc_count as usize { 264 | let csrc = self.csrcs[index]; 265 | target[write_index] = (csrc >> 24) as u8; 266 | target[write_index + 1] = (csrc >> 16) as u8; 267 | target[write_index + 2] = (csrc >> 8) as u8; 268 | target[write_index + 3] = (csrc) as u8; 269 | 270 | write_index += 4; 271 | } 272 | 273 | if let Some((id, payload)) = self.extension { 274 | target[write_index] = (id >> 8) as u8; 275 | target[write_index + 1] = (id & 0xFF) as u8; 276 | 277 | let len = payload.len() / 4; 278 | target[write_index + 2] = (len >> 8) as u8; 279 | target[write_index + 3] = (len & 0xFF) as u8; 280 | 281 | write_index += 4; 282 | 283 | /* the target buffer has been ensured to hold that many bytes */ 284 | target[write_index..(write_index + payload.len())].copy_from_slice(payload); 285 | write_index += payload.len(); 286 | } 287 | 288 | if let Some(payload) = self.payload { 289 | /* the target buffer has been ensured to hold that many bytes */ 290 | target[write_index..(write_index + payload.len())].copy_from_slice(payload); 291 | write_index += payload.len(); 292 | } 293 | 294 | if let Some(padded_bytes) = self.padded.adjust_len(write_index) { 295 | target[0] |= 1 << 5; /* set the padded flag */ 296 | 297 | write_index += padded_bytes; 298 | target[write_index - 1] = padded_bytes as u8; 299 | } 300 | 301 | write_index 302 | } 303 | 304 | /// Build the RTP packet on the target buffer. 305 | /// The length of the final packet will be returned on success. 306 | pub fn build_into(&self, target: &mut [u8]) -> Result { 307 | if target.len() < self.target_length() { 308 | return Err(RtpPacketBuildError::BufferTooSmall); 309 | } 310 | 311 | self.validate_content()?; 312 | Ok(self.build_into_unchecked(target)) 313 | } 314 | 315 | /// Build the RTP packet. 316 | /// On success, it returns a buffer containing the target packet. 317 | pub fn build(&self) -> Result, RtpPacketBuildError> { 318 | self.validate_content()?; 319 | 320 | let mut buffer = vec![0; self.target_length()]; 321 | 322 | let length = self.build_into_unchecked(buffer.as_mut_slice()); 323 | assert_eq!(length, buffer.len()); 324 | 325 | Ok(buffer) 326 | } 327 | 328 | fn validate_content(&self) -> Result<(), RtpPacketBuildError> { 329 | if (self.payload_type & (!0x7F)) != 0 { 330 | return Err(RtpPacketBuildError::PayloadTypeInvalid); 331 | } 332 | 333 | if let Some((_, payload)) = self.extension { 334 | if payload.len() > 0xFFFF { 335 | return Err(RtpPacketBuildError::ExtensionTooLarge); 336 | } 337 | 338 | if (payload.len() & 0x3) != 0 { 339 | return Err(RtpPacketBuildError::ExtensionMissingPadding); 340 | } 341 | } 342 | 343 | Ok(()) 344 | } 345 | } 346 | 347 | impl std::error::Error for RtpPacketBuildError {} 348 | 349 | impl std::fmt::Display for RtpPacketBuildError { 350 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 351 | write!( 352 | f, 353 | "{}", 354 | match self { 355 | RtpPacketBuildError::BufferTooSmall => "buffer too small", 356 | RtpPacketBuildError::PayloadTypeInvalid => "payload type invalid", 357 | RtpPacketBuildError::ExtensionTooLarge => "extensions too large", 358 | RtpPacketBuildError::ExtensionMissingPadding => "extension missing padding", 359 | } 360 | ) 361 | } 362 | } 363 | 364 | #[cfg(test)] 365 | mod test { 366 | use crate::{Pad, RtpPacketBuilder}; 367 | 368 | #[test] 369 | fn test_padded() { 370 | let payload = vec![1u8]; 371 | let packet = RtpPacketBuilder::new() 372 | .payload_type(1) 373 | .payload(&payload) 374 | .padded(Pad::round_to(4)) 375 | .build() 376 | .unwrap(); 377 | 378 | assert_eq!(packet.len() & 0x03, 0); 379 | assert!(crate::reader::RtpReader::new(&packet) 380 | .unwrap() 381 | .padding() 382 | .is_some()); 383 | } 384 | 385 | #[test] 386 | fn test_padding_not_needed() { 387 | let payload = vec![1u8; 4]; 388 | let packet = RtpPacketBuilder::new() 389 | .payload_type(1) 390 | .payload(&payload) 391 | .padded(Pad::round_to(4)) 392 | .build() 393 | .unwrap(); 394 | 395 | // assert the length is not increased beyond the 12 bytes of header + the payload 396 | assert_eq!(packet.len(), 12 + payload.len()); 397 | assert!(crate::reader::RtpReader::new(&packet) 398 | .unwrap() 399 | .padding() 400 | .is_none()); 401 | } 402 | 403 | #[test] 404 | fn test_not_padded() { 405 | let payload = vec![1u8]; 406 | let packet = RtpPacketBuilder::new() 407 | .payload_type(1) 408 | .payload(&payload) 409 | .build() 410 | .unwrap(); 411 | 412 | assert_eq!(packet.len() & 0x03, 1); 413 | } 414 | 415 | #[test] 416 | fn test_would_run() { 417 | let extension = vec![1u8, 2, 3, 4]; 418 | let builder = RtpPacketBuilder::new() 419 | .payload_type(12) 420 | .extension(1, &extension); 421 | 422 | let mut buffer = [0u8; 100]; 423 | builder.build_into(&mut buffer).unwrap(); 424 | } 425 | } 426 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Parser and builder for packets formatted per [RFC 3550](https://tools.ietf.org/html/rfc3550), _A Transport 2 | //! Protocol for Real-Time Applications_. 3 | //! 4 | //! Parse a packet 5 | //! ``` 6 | //! use rtp_rs::*; 7 | //! // let data = ...acquire UDP packet from the network etc... 8 | //! # let data = &[ 9 | //! # 0x80u8, 0xe0u8, 0x27u8, 0x38u8, 0x64u8, 0xe4u8, 10 | //! # 0x05u8, 0xa7u8, 0xa2u8, 0x42u8, 0xafu8, 0x01u8 11 | //! # ]; 12 | //! if let Ok(rtp) = RtpReader::new(data) { 13 | //! println!("Sequence number {:?}", rtp.sequence_number()); 14 | //! println!("Payload length {:?}", rtp.payload().len()); 15 | //! } 16 | //! ``` 17 | //! 18 | //! Build a packet 19 | //! ``` 20 | //! use rtp_rs::*; 21 | //! 22 | //! let payload = vec![0u8, 2, 5, 4, 6]; 23 | //! let result = RtpPacketBuilder::new() 24 | //! .payload_type(111) 25 | //! .ssrc(1337) 26 | //! .sequence(Seq::from(1234)) 27 | //! .timestamp(666657) 28 | //! .padded(Pad::round_to(4)) 29 | //! .marked(true) 30 | //! .payload(&payload) 31 | //! .build(); 32 | //! if let Ok(packet) = result { 33 | //! println!("Packet: {:?}", packet); 34 | //! } 35 | //! ``` 36 | 37 | #![forbid(unsafe_code)] 38 | #![deny(rust_2018_idioms, future_incompatible, missing_docs)] 39 | 40 | /// 16 bit RTP sequence number value, as obtained from the `sequence_number()` method of RtpReader. 41 | /// 42 | /// ``` 43 | /// use rtp_rs::*; 44 | /// let seq = Seq::from(123); 45 | /// ``` 46 | /// 47 | /// This type's behavior attempts to honour the expected wrap-around of sequence number values 48 | /// from `0xffff` back to `0x0000`. 49 | /// 50 | /// You can perform logic over sequences of RTP packets using this type and other helpers from this 51 | /// crate, 52 | /// ``` 53 | /// # use rtp_rs::*; 54 | /// let start = Seq::from(0xfffe); 55 | /// let end = Seq::from(0x0002); 56 | /// // produces the Seq values 0xfffe, 0xffff, 0x0000, 0x0001: 57 | /// for seq in (start..end).seq_iter() { 58 | /// // ...inspect some RTP packet you've stored against this sequence number... 59 | /// } 60 | /// ``` 61 | /// 62 | /// ## Unsoundness 63 | /// **Note** this type has implementations of `Ord` and `PartialOrd`, but those implementations 64 | /// violate the requirement for transitivity which both traits document. 65 | /// 66 | /// ```should_panic 67 | /// # use rtp_rs::*; 68 | /// let a = Seq::from(0); 69 | /// let b = a + 0x7fff; 70 | /// let c = b + 0x7fff; 71 | /// assert!(a < b); 72 | /// assert!(b < c); 73 | /// assert!(a < c); // Assertion fails, in violation of Ord/PartialOrd requirements 74 | /// ``` 75 | /// A future release will potentially deprecate `Ord` / `PartialOrd` implementations for `Seq`, and 76 | /// hopefully provide a mechanism for sequence number processing which is sound. 77 | #[derive(PartialEq, Eq, Debug, Clone, Copy)] 78 | pub struct Seq(u16); 79 | impl Seq { 80 | /// Produce the sequence value which follows this one. 81 | /// 82 | /// Sequence numbers wrap back to `0x0000` after reaching the value `0xffff` 83 | pub fn next(self) -> Seq { 84 | Seq(self.0.wrapping_add(1)) 85 | } 86 | 87 | /// Returns `true` if this sequence number value is immediately before the given one 88 | pub fn precedes(self, other: Seq) -> bool { 89 | self.next() == other 90 | } 91 | } 92 | impl From for u16 { 93 | fn from(v: Seq) -> Self { 94 | v.0 95 | } 96 | } 97 | impl From for Seq { 98 | fn from(v: u16) -> Self { 99 | Seq(v) 100 | } 101 | } 102 | 103 | /// Implements wrapped subtraction such that for instance `Seq(0x0000) - Seq(0xffff)` results in 104 | /// `1` (rather than `-65535`). 105 | /// 106 | /// This is for symmetry with addition, where for example `Seq(0xffff) + 1` gives `Seq(0x0000)` 107 | impl std::ops::Sub for Seq { 108 | type Output = i32; 109 | 110 | fn sub(self, rhs: Seq) -> Self::Output { 111 | let delta = i32::from(self.0) - i32::from(rhs.0); 112 | if delta < std::i16::MIN as i32 { 113 | std::u16::MAX as i32 + 1 + delta 114 | } else if delta > std::i16::MAX as i32 { 115 | delta - std::u16::MAX as i32 - 1 116 | } else { 117 | delta 118 | } 119 | } 120 | } 121 | impl PartialOrd for Seq { 122 | fn partial_cmp(&self, other: &Seq) -> Option { 123 | Some(self.cmp(other)) 124 | } 125 | } 126 | impl Ord for Seq { 127 | fn cmp(&self, other: &Self) -> std::cmp::Ordering { 128 | (*self - *other).cmp(&0) 129 | } 130 | } 131 | 132 | impl std::ops::Add for Seq { 133 | type Output = Seq; 134 | 135 | fn add(self, rhs: u16) -> Self::Output { 136 | Seq(self.0.wrapping_add(rhs)) 137 | } 138 | } 139 | 140 | /// Trait for types that can produce a `SeqIter`, with an implementation provided for `Range`. 141 | pub trait IntoSeqIterator { 142 | /// Produce an `Iterator` over sequence number values 143 | fn seq_iter(self) -> SeqIter; 144 | } 145 | impl IntoSeqIterator for std::ops::Range { 146 | fn seq_iter(self) -> SeqIter { 147 | SeqIter(self.start, self.end) 148 | } 149 | } 150 | 151 | /// An `Iterator` which can produce values from the given start value to the given end value, inclusive. 152 | /// 153 | /// Rather than using this directly, it is convenient to use a range like so, 154 | /// ``` 155 | /// use rtp_rs::*; 156 | /// use rtp_rs::IntoSeqIterator; 157 | /// let here = 12.into(); 158 | /// let there = 22.into(); 159 | /// for seq in (here..there).seq_iter() { 160 | /// println!("{:?}", seq); 161 | /// } 162 | /// ``` 163 | pub struct SeqIter(Seq, Seq); 164 | impl Iterator for SeqIter { 165 | type Item = Seq; 166 | 167 | fn next(&mut self) -> Option { 168 | if self.0 >= self.1 { 169 | None 170 | } else { 171 | let res = self.0; 172 | self.0 = self.0.next(); 173 | Some(res) 174 | } 175 | } 176 | } 177 | 178 | mod reader; 179 | pub use reader::*; 180 | 181 | mod builder; 182 | pub use builder::*; 183 | -------------------------------------------------------------------------------- /src/reader.rs: -------------------------------------------------------------------------------- 1 | use crate::{RtpPacketBuilder, Seq}; 2 | use std::fmt; 3 | 4 | /// Wrapper around a byte-slice of RTP data, providing accessor methods for the RTP header fields. 5 | pub struct RtpReader<'a> { 6 | buf: &'a [u8], 7 | } 8 | 9 | /// Reasons for `RtpHeader::new()` to fail 10 | #[derive(Debug)] 11 | pub enum RtpReaderError { 12 | /// Buffer too short to be valid RTP packet 13 | BufferTooShort(usize), 14 | /// Only RTP version 2 supported 15 | UnsupportedVersion(u8), 16 | /// RTP headers truncated before end of buffer 17 | HeadersTruncated { 18 | /// The amount of data which was expected to be present (which may vary depending on flags 19 | /// in the RTP header) 20 | header_len: usize, 21 | /// The actual amount of data that was available, which was found to be smaller than 22 | /// `header_len` 23 | buffer_len: usize, 24 | }, 25 | /// The padding header at the end of the packet, if present, specifies the number of padding 26 | /// bytes, including itself, and therefore cannot be less than `1`, or greater than the 27 | /// available space. 28 | PaddingLengthInvalid(u8), 29 | } 30 | 31 | impl<'a> RtpReader<'a> { 32 | /// An RTP packet header is no fewer than 12 bytes long 33 | pub const MIN_HEADER_LEN: usize = 12; 34 | const EXTENSION_HEADER_LEN: usize = 4; 35 | 36 | /// Tries to construct a new `RtpHeader` instance, or an `RtpReaderError` if the RTP data is 37 | /// malformed. 38 | /// 39 | /// In particular, if there is too little data in the given buffer, such that some later 40 | /// attempt to access an RTP header field would need to access bytes that are not available, 41 | /// then this method will fail up front, rather than allowing attempts to access any header 42 | /// field to fail later on. 43 | pub fn new(b: &'a [u8]) -> Result, RtpReaderError> { 44 | if b.len() < Self::MIN_HEADER_LEN { 45 | return Err(RtpReaderError::BufferTooShort(b.len())); 46 | } 47 | let r = RtpReader { buf: b }; 48 | if r.version() != 2 { 49 | return Err(RtpReaderError::UnsupportedVersion(r.version())); 50 | } 51 | if r.extension_flag() { 52 | let extension_start = r.csrc_end() + Self::EXTENSION_HEADER_LEN; 53 | if extension_start > b.len() { 54 | return Err(RtpReaderError::HeadersTruncated { 55 | header_len: extension_start, 56 | buffer_len: b.len(), 57 | }); 58 | } 59 | let extension_end = extension_start + r.extension_len(); 60 | if extension_end > b.len() { 61 | return Err(RtpReaderError::HeadersTruncated { 62 | header_len: extension_end, 63 | buffer_len: b.len(), 64 | }); 65 | } 66 | } 67 | if r.payload_offset() > b.len() { 68 | return Err(RtpReaderError::HeadersTruncated { 69 | header_len: r.payload_offset(), 70 | buffer_len: b.len(), 71 | }); 72 | } 73 | if r.padding_flag() { 74 | let post_header_bytes = b.len() - r.payload_offset(); 75 | // with 'padding' flag set, there must be at least a single byte after the headers to 76 | // hold the padding length 77 | if post_header_bytes == 0 { 78 | return Err(RtpReaderError::HeadersTruncated { 79 | header_len: r.payload_offset(), 80 | buffer_len: b.len() - 1, 81 | }); 82 | } 83 | let pad_len = r.padding_len()?; 84 | 85 | if r.payload_offset() + pad_len as usize > b.len() { 86 | return Err(RtpReaderError::PaddingLengthInvalid(pad_len)); 87 | } 88 | } 89 | Ok(r) 90 | } 91 | 92 | /// Version field value (currently only version 2 is supported, so other values will not be 93 | /// seen from this release of `rtp-rs`. 94 | pub fn version(&self) -> u8 { 95 | (self.buf[0] & 0b1100_0000) >> 6 96 | } 97 | 98 | /// Flag indicating if padding is present at the end of the payload data. 99 | fn padding_flag(&self) -> bool { 100 | (self.buf[0] & 0b0010_0000) != 0 101 | } 102 | /// Returns the size of the padding at the end of this packet, or `None` if the padding flag is 103 | /// not set in the packet header 104 | pub fn padding(&self) -> Option { 105 | if self.padding_flag() { 106 | Some(self.padding_len().unwrap()) 107 | } else { 108 | None 109 | } 110 | } 111 | 112 | fn extension_flag(&self) -> bool { 113 | (self.buf[0] & 0b0001_0000) != 0 114 | } 115 | /// A count of the number of CSRC fields present in the RTP headers - may be `0`. 116 | /// 117 | /// See [csrc()](#method.csrc). 118 | pub fn csrc_count(&self) -> u8 { 119 | self.buf[0] & 0b0000_1111 120 | } 121 | /// A 'marker', which may have some definition in the specific RTP profile in use 122 | pub fn mark(&self) -> bool { 123 | (self.buf[1] & 0b1000_0000) != 0 124 | } 125 | /// Indicates the type of content carried in this RTP packet. 126 | /// 127 | /// A few types-values are defined in the standard, but in many applications of RTP the value 128 | /// of this field needs to be agreed between sender and receiver by some mechanism outside of 129 | /// RTP itself. 130 | pub fn payload_type(&self) -> u8 { 131 | self.buf[1] & 0b0111_1111 132 | } 133 | /// The sequence number of this particular packet. 134 | /// 135 | /// Sequence numbers are 16 bits, and will wrap back to `0` after reaching the maximum 16-bit 136 | /// value of `65535`. 137 | /// 138 | /// Receivers can identify packet losses or reordering by inspecting the value of this field 139 | /// across a sequence of received packets. The [`Seq`](struct.Seq.html) wrapper type helps 140 | /// calling code reason about sequence number problems in the face of any wraparound that might 141 | /// have legitimately happened. 142 | pub fn sequence_number(&self) -> Seq { 143 | Seq((self.buf[2] as u16) << 8 | (self.buf[3] as u16)) 144 | } 145 | /// The timestamp of this packet, given in a timebase that relates to the particular 146 | /// `payload_type` in use. 147 | /// 148 | /// It is perfectly possible for successive packets in a sequence to have the same value, or 149 | /// to have values that differ by arbitrarily large amounts. 150 | /// 151 | /// Timestamps are 32 bits, and will wrap back to `0` after reaching the maximum 32 bit value 152 | /// of `4294967295`. 153 | pub fn timestamp(&self) -> u32 { 154 | (self.buf[4] as u32) << 24 155 | | (self.buf[5] as u32) << 16 156 | | (self.buf[6] as u32) << 8 157 | | (self.buf[7] as u32) 158 | } 159 | /// The _synchronisation source_ for this packet. Many applications of RTP do not use this 160 | /// field. 161 | pub fn ssrc(&self) -> u32 { 162 | (self.buf[8] as u32) << 24 163 | | (self.buf[9] as u32) << 16 164 | | (self.buf[10] as u32) << 8 165 | | (self.buf[11] as u32) 166 | } 167 | /// A potentially empty list of _contributing sources_ for this packet. Many applications of 168 | /// RTP do not use this field. 169 | pub fn csrc(&self) -> impl Iterator + '_ { 170 | self.buf[Self::MIN_HEADER_LEN..] 171 | .chunks(4) 172 | .take(self.csrc_count() as usize) 173 | .map(|b| (b[0] as u32) << 24 | (b[1] as u32) << 16 | (b[2] as u32) << 8 | (b[3] as u32)) 174 | } 175 | 176 | /// Returns the offset of the payload for the packet 177 | pub fn payload_offset(&self) -> usize { 178 | let offset = self.csrc_end(); 179 | if self.extension_flag() { 180 | offset + Self::EXTENSION_HEADER_LEN + self.extension_len() 181 | } else { 182 | offset 183 | } 184 | } 185 | 186 | fn csrc_end(&self) -> usize { 187 | Self::MIN_HEADER_LEN + (4 * self.csrc_count()) as usize 188 | } 189 | 190 | /// Returns the payload data of this RTP packet, excluding the packet's headers and any 191 | /// optional trailing padding. 192 | pub fn payload(&self) -> &'a [u8] { 193 | let pad = if self.padding_flag() { 194 | // in Self::new(), we already checked this was Ok, and will not attempt an invalid 195 | // slice below, 196 | self.padding_len().unwrap() as usize 197 | } else { 198 | 0 199 | }; 200 | &self.buf[self.payload_offset()..self.buf.len() - pad] 201 | } 202 | 203 | fn extension_len(&self) -> usize { 204 | let offset = self.csrc_end(); 205 | // The 16 bit extension length header gives a length in 32 bit (4 byte) units; 0 is a 206 | // valid length. 207 | 4 * ((self.buf[offset + 2] as usize) << 8 | (self.buf[offset + 3] as usize)) 208 | } 209 | 210 | // must only be used if padding() returns true 211 | fn padding_len(&self) -> Result { 212 | match self.buf[self.buf.len() - 1] { 213 | 0 => Err(RtpReaderError::PaddingLengthInvalid(0)), 214 | l => Ok(l), 215 | } 216 | } 217 | 218 | /// Returns details of the optional RTP header extension field. If there is an extension, 219 | /// the first component of the resulting tuple is the extension id, and the second is a 220 | /// byte-slice for the extension data value, to be interpreted by the application. 221 | pub fn extension(&self) -> Option<(u16, &'a [u8])> { 222 | if self.extension_flag() { 223 | let offset = self.csrc_end(); 224 | let id = (self.buf[offset] as u16) << 8 | (self.buf[offset + 1] as u16); 225 | let start = offset + 4; 226 | Some((id, &self.buf[start..start + self.extension_len()])) 227 | } else { 228 | None 229 | } 230 | } 231 | 232 | /// Create a `RtpPacketBuilder` from this packet. **Note** that padding from the original 233 | /// packet will not be used by default, and must be defined on the resulting `RtpPacketBuilder` 234 | /// if required. 235 | /// 236 | /// The padding is not copied from the original since, while we do know how many padding bytes 237 | /// were present, we don't know if the intent was to round to 2 bytes, 4 bytes, etc. Blindly 238 | /// copying the padding could result in an incorrect result _if_ the payload is subsequently 239 | /// changed for one with a different length. 240 | /// 241 | /// If you know your output packets don't need padding, there is nothing more to do, since 242 | /// that is the default for the resulting `RtpPacketBulder`. 243 | /// 244 | /// If you know you output packets need padding to 4 bytes, then you _must_ explicitly specify 245 | /// this using `builder.padded(Pad::round_to(4))` even if the source packet was already padded 246 | /// to a 4 byte boundary. 247 | pub fn create_builder(&self) -> RtpPacketBuilder<'a> { 248 | let mut builder = RtpPacketBuilder::new() 249 | .payload_type(self.payload_type()) 250 | .marked(self.mark()) 251 | .sequence(self.sequence_number()) 252 | .ssrc(self.ssrc()) 253 | .timestamp(self.timestamp()) 254 | .payload(self.payload()); 255 | 256 | if let Some(ext) = self.extension() { 257 | builder = builder.extension(ext.0, ext.1); 258 | } 259 | 260 | for csrc in self.csrc() { 261 | builder = builder.add_csrc(csrc); 262 | } 263 | 264 | builder 265 | } 266 | } 267 | impl<'a> fmt::Debug for RtpReader<'a> { 268 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { 269 | f.debug_struct("RtpReader") 270 | .field("version", &self.version()) 271 | .field("padding", &self.padding()) 272 | .field("extension", &self.extension().map(|(id, _)| id)) 273 | .field("csrc_count", &self.csrc_count()) 274 | .field("mark", &self.mark()) 275 | .field("payload_type", &self.payload_type()) 276 | .field("sequence_number", &self.sequence_number()) 277 | .field("timestamp", &self.timestamp()) 278 | .field("ssrc", &self.ssrc()) 279 | .field("payload_length", &self.payload().len()) 280 | .finish() 281 | } 282 | } 283 | 284 | impl std::error::Error for RtpReaderError {} 285 | 286 | impl std::fmt::Display for RtpReaderError { 287 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 288 | write!( 289 | f, 290 | "{}", 291 | match self { 292 | RtpReaderError::BufferTooShort(b) => format!("buffer too short: {b}"), 293 | RtpReaderError::UnsupportedVersion(v) => format!("unsupported version: {v}"), 294 | RtpReaderError::HeadersTruncated { 295 | header_len, 296 | buffer_len, 297 | } => format!( 298 | "headers truncated: header length: {header_len}; buffer length: {buffer_len}" 299 | ), 300 | RtpReaderError::PaddingLengthInvalid(p) => format!("padding length invalid: {p}"), 301 | } 302 | ) 303 | } 304 | } 305 | 306 | #[cfg(test)] 307 | mod tests { 308 | use super::*; 309 | use crate::IntoSeqIterator; 310 | 311 | const TEST_RTP_PACKET: [u8; 391] = [ 312 | 0x80u8, 0xe0u8, 0x27u8, 0x38u8, 0x64u8, 0xe4u8, 0x05u8, 0xa7u8, 0xa2u8, 0x42u8, 0xafu8, 313 | 0x01u8, 0x3cu8, 0x41u8, 0xa4u8, 0xa3u8, 0x5du8, 0x13u8, 0xf9u8, 0xcau8, 0x2cu8, 0x7eu8, 314 | 0xa9u8, 0x77u8, 0xaau8, 0xdeu8, 0xf7u8, 0xcau8, 0xa4u8, 0x28u8, 0xfeu8, 0xdfu8, 0xc8u8, 315 | 0x68u8, 0xf1u8, 0xd9u8, 0x4fu8, 0x69u8, 0x96u8, 0xa0u8, 0x57u8, 0xbau8, 0xfbu8, 0x07u8, 316 | 0xc4u8, 0xc4u8, 0xd4u8, 0xfeu8, 0xf8u8, 0xc7u8, 0xb2u8, 0x0du8, 0x01u8, 0x12u8, 0x14u8, 317 | 0x36u8, 0x69u8, 0x75u8, 0xf2u8, 0xb4u8, 0xb5u8, 0xf2u8, 0x54u8, 0x2eu8, 0xc2u8, 0x66u8, 318 | 0x51u8, 0xebu8, 0x41u8, 0x80u8, 0x96u8, 0xceu8, 0x8eu8, 0x60u8, 0xb2u8, 0x44u8, 0xaeu8, 319 | 0xe5u8, 0x43u8, 0xadu8, 0x7bu8, 0x48u8, 0x89u8, 0x44u8, 0xb0u8, 0x48u8, 0x67u8, 0x6au8, 320 | 0x84u8, 0x7au8, 0x0au8, 0x8fu8, 0x71u8, 0x50u8, 0x69u8, 0xe6u8, 0xb1u8, 0x05u8, 0x40u8, 321 | 0xb9u8, 0x8cu8, 0xafu8, 0x42u8, 0xcbu8, 0x58u8, 0x83u8, 0xcbu8, 0x32u8, 0x64u8, 0xd2u8, 322 | 0x2au8, 0x7du8, 0x4eu8, 0xf5u8, 0xbcu8, 0x33u8, 0xfeu8, 0xb7u8, 0x0cu8, 0xe4u8, 0x8eu8, 323 | 0x38u8, 0xbcu8, 0x3au8, 0x1eu8, 0xd2u8, 0x56u8, 0x13u8, 0x23u8, 0x47u8, 0xcfu8, 0x42u8, 324 | 0xa9u8, 0xbbu8, 0xcfu8, 0x48u8, 0xf3u8, 0x11u8, 0xc7u8, 0xfdu8, 0x73u8, 0x2du8, 0xe1u8, 325 | 0xeau8, 0x47u8, 0x5cu8, 0x5du8, 0x11u8, 0x96u8, 0x1eu8, 0xc4u8, 0x70u8, 0x32u8, 0x77u8, 326 | 0xabu8, 0x31u8, 0x7au8, 0xb1u8, 0x22u8, 0x14u8, 0x8du8, 0x2bu8, 0xecu8, 0x3du8, 0x67u8, 327 | 0x97u8, 0xa4u8, 0x40u8, 0x21u8, 0x1eu8, 0xceu8, 0xb0u8, 0x63u8, 0x01u8, 0x75u8, 0x77u8, 328 | 0x03u8, 0x15u8, 0xcdu8, 0x35u8, 0xa1u8, 0x2fu8, 0x4bu8, 0xa0u8, 0xacu8, 0x8du8, 0xd7u8, 329 | 0x78u8, 0x02u8, 0x23u8, 0xcbu8, 0xfdu8, 0x82u8, 0x4eu8, 0x0bu8, 0x79u8, 0x7fu8, 0x39u8, 330 | 0x70u8, 0x26u8, 0x66u8, 0x37u8, 0xe9u8, 0x93u8, 0x91u8, 0x7bu8, 0xc4u8, 0x80u8, 0xa9u8, 331 | 0x18u8, 0x23u8, 0xb3u8, 0xa1u8, 0x04u8, 0x72u8, 0x53u8, 0xa0u8, 0xb4u8, 0xffu8, 0x79u8, 332 | 0x1fu8, 0x07u8, 0xe2u8, 0x5du8, 0x01u8, 0x7du8, 0x63u8, 0xc1u8, 0x16u8, 0x89u8, 0x23u8, 333 | 0x4au8, 0x17u8, 0xbbu8, 0x6du8, 0x0du8, 0x81u8, 0x1au8, 0xbbu8, 0x94u8, 0x5bu8, 0xcbu8, 334 | 0x2du8, 0xdeu8, 0x98u8, 0x40u8, 0x22u8, 0x62u8, 0x41u8, 0xc2u8, 0x9bu8, 0x95u8, 0x85u8, 335 | 0x60u8, 0xf0u8, 0xdeu8, 0x6fu8, 0xeeu8, 0x93u8, 0xccu8, 0x15u8, 0x76u8, 0xfbu8, 0xf8u8, 336 | 0x8au8, 0x1du8, 0xe1u8, 0x83u8, 0x12u8, 0xabu8, 0x25u8, 0x6au8, 0x7bu8, 0x89u8, 0xedu8, 337 | 0x70u8, 0x4eu8, 0xcdu8, 0x1eu8, 0xa9u8, 0xfcu8, 0xa8u8, 0x22u8, 0x91u8, 0x5fu8, 0x50u8, 338 | 0x68u8, 0x6au8, 0x35u8, 0xf7u8, 0xc1u8, 0x1eu8, 0x15u8, 0x37u8, 0xb4u8, 0x30u8, 0x62u8, 339 | 0x56u8, 0x1eu8, 0x2eu8, 0xe0u8, 0x2du8, 0xa4u8, 0x1eu8, 0x75u8, 0x5bu8, 0xc7u8, 0xd0u8, 340 | 0x5bu8, 0x9du8, 0xd0u8, 0x25u8, 0x76u8, 0xdfu8, 0xa7u8, 0x19u8, 0x12u8, 0x93u8, 0xf4u8, 341 | 0xebu8, 0x02u8, 0xf2u8, 0x4au8, 0x13u8, 0xe9u8, 0x1cu8, 0x17u8, 0xccu8, 0x11u8, 0x87u8, 342 | 0x9cu8, 0xa6u8, 0x40u8, 0x27u8, 0xb7u8, 0x2bu8, 0x9bu8, 0x6fu8, 0x23u8, 0x06u8, 0x2cu8, 343 | 0xc6u8, 0x6eu8, 0xc1u8, 0x9au8, 0xbdu8, 0x59u8, 0x37u8, 0xe9u8, 0x9eu8, 0x76u8, 0xf6u8, 344 | 0xc1u8, 0xbcu8, 0x81u8, 0x18u8, 0x60u8, 0xc9u8, 0x64u8, 0x0au8, 0xb3u8, 0x6eu8, 0xf3u8, 345 | 0x6bu8, 0xb9u8, 0xd0u8, 0xf6u8, 0xe0u8, 0x9bu8, 0x91u8, 0xc1u8, 0x0fu8, 0x96u8, 0xefu8, 346 | 0xbcu8, 0x5fu8, 0x8eu8, 0x86u8, 0x56u8, 0x5au8, 0xfcu8, 0x7au8, 0x8bu8, 0xddu8, 0x9au8, 347 | 0x1cu8, 0xf6u8, 0xb4u8, 0x85u8, 0xf4u8, 0xb0u8, 348 | ]; 349 | 350 | const TEST_RTP_PACKET_WITH_EXTENSION: [u8; 63] = [ 351 | 144u8, 111u8, 79u8, 252u8, 224u8, 94u8, 104u8, 203u8, 30u8, 112u8, 208u8, 191u8, 190u8, 352 | 222u8, 0u8, 3u8, 34u8, 175u8, 185u8, 88u8, 49u8, 0u8, 171u8, 64u8, 48u8, 16u8, 219u8, 0u8, 353 | 104u8, 9u8, 136u8, 90u8, 174u8, 145u8, 68u8, 165u8, 227u8, 178u8, 187u8, 68u8, 166u8, 66u8, 354 | 235u8, 40u8, 171u8, 135u8, 30u8, 174u8, 130u8, 239u8, 205u8, 14u8, 211u8, 232u8, 65u8, 355 | 67u8, 153u8, 120u8, 63u8, 17u8, 101u8, 55u8, 17u8, 356 | ]; 357 | 358 | #[test] 359 | fn version() { 360 | let reader = RtpReader::new(&TEST_RTP_PACKET).unwrap(); 361 | assert_eq!(2, reader.version()); 362 | assert!(reader.padding().is_none()); 363 | assert!(reader.extension().is_none()); 364 | assert_eq!(0, reader.csrc_count()); 365 | assert!(reader.mark()); 366 | assert_eq!(96, reader.payload_type()); 367 | assert_eq!(Seq(10040), reader.sequence_number()); 368 | assert_eq!(1_692_665_255, reader.timestamp()); 369 | assert_eq!(0xa242_af01, reader.ssrc()); 370 | assert_eq!(379, reader.payload().len()); 371 | format!("{:?}", reader); 372 | } 373 | 374 | #[test] 375 | fn padding() { 376 | let reader = RtpReader::new(&TEST_RTP_PACKET_WITH_EXTENSION).unwrap(); 377 | assert_eq!(2, reader.version()); 378 | assert!(reader.padding().is_none()); 379 | assert!(reader.extension().is_some()); 380 | assert_eq!(0, reader.csrc_count()); 381 | assert_eq!(111, reader.payload_type()); 382 | } 383 | 384 | #[test] 385 | fn padding_too_large() { 386 | // 'padding' header-flag is on, and padding length (255) in final byte is larger than the 387 | // buffer length. (Test data created by fuzzing.) 388 | let data = [ 389 | 0xa2, 0xa2, 0xa2, 0xa2, 0xa2, 0x90, 0x0, 0x0, 0x1, 0x0, 0xff, 0xa2, 0xa2, 0xa2, 0xa2, 390 | 0x90, 0x0, 0x0, 0x0, 0x0, 0xff, 391 | ]; 392 | assert!(RtpReader::new(&data).is_err()); 393 | } 394 | 395 | #[test] 396 | fn builder_juggle() { 397 | let reader = RtpReader::new(&TEST_RTP_PACKET).unwrap(); 398 | let buffer = reader.create_builder().build().unwrap(); 399 | 400 | assert_eq!(buffer.as_slice(), &TEST_RTP_PACKET[..]); 401 | } 402 | 403 | #[test] 404 | fn builder_juggle_extension() { 405 | let reader = RtpReader::new(&TEST_RTP_PACKET_WITH_EXTENSION).unwrap(); 406 | let buffer = reader.create_builder().build().unwrap(); 407 | assert_eq!(buffer.as_slice(), &TEST_RTP_PACKET_WITH_EXTENSION[..]); 408 | } 409 | 410 | #[test] 411 | fn builder_juggle_clear_payload() { 412 | let new_payload = vec![]; 413 | let reader = RtpReader::new(&TEST_RTP_PACKET_WITH_EXTENSION).unwrap(); 414 | let buffer = reader 415 | .create_builder() 416 | .payload(&new_payload) 417 | .build() 418 | .unwrap(); 419 | 420 | let expected = &TEST_RTP_PACKET_WITH_EXTENSION[0..(3 + 4) * 4]; 421 | assert_eq!(buffer.as_slice(), expected); 422 | } 423 | 424 | #[test] 425 | fn seq() { 426 | assert!(Seq(0).precedes(Seq(1))); 427 | assert!(Seq(0xffff).precedes(Seq(0))); 428 | assert!(Seq(0) < Seq(1)); 429 | assert!(Seq(0xffff) < Seq(0)); 430 | assert_eq!(-1, Seq(0) - Seq(1)); 431 | assert_eq!(1, Seq(1) - Seq(0)); 432 | assert_eq!(0, Seq(1) - Seq(1)); 433 | assert_eq!(1, Seq(0) - Seq(0xffff)); 434 | assert_eq!(-1, Seq(0xffff) - Seq(0)); 435 | let mut it = (Seq(0xfffe)..Seq(1)).seq_iter(); 436 | assert_eq!(Seq(0xfffe), it.next().unwrap()); 437 | assert_eq!(Seq(0xffff), it.next().unwrap()); 438 | assert_eq!(Seq(0x0000), it.next().unwrap()); 439 | assert_eq!(None, it.next()); 440 | } 441 | } 442 | --------------------------------------------------------------------------------