///|
/// A parsed RTP packet with the `rtpsize` cleartext header separated.
#doc(hidden)
pub(all) struct RtpPacket {
sequence : UInt16
timestamp : UInt32
ssrc : UInt32
header_len : Int32
header : Bytes
payload : Bytes
} derive(Debug, Eq)
///|
/// Errors raised while validating an RTP packet.
#doc(hidden)
pub(all) suberror RtpError {
InvalidRtpPacket(reason~ : String)
} derive(Debug, Eq)
///|
/// Build Discord's RTP header for a 20 ms Opus frame.
#doc(hidden)
pub fn build_rtp_header(
sequence~ : UInt16,
timestamp~ : UInt32,
ssrc~ : UInt32,
) -> Bytes {
Bytes::from_array([
0x80,
0x78,
(sequence >> 8).to_byte(),
sequence.to_byte(),
(timestamp >> 24).to_byte(),
(timestamp >> 16).to_byte(),
(timestamp >> 8).to_byte(),
timestamp.to_byte(),
(ssrc >> 24).to_byte(),
(ssrc >> 16).to_byte(),
(ssrc >> 8).to_byte(),
ssrc.to_byte(),
])
}
///|
/// Return the cleartext RTP header length used as AEAD associated data.
#doc(hidden)
pub fn rtpsize_header_len(bytes : Bytes) -> Int32 {
// version, padding, extension, CSRC count | marker + payload type,
// sequence, timestamp, SSRC
match bytes {
[
u2be(_),
u1be(_),
u1be(extension),
u4be(csrc_count),
u8be(_),
u16be(_),
u32be(_),
u32be(_),
..,
] =>
12 +
csrc_count.reinterpret_as_int() * 4 +
extension.reinterpret_as_int() * 4
_ => 0
}
}
///|
/// Parse an RTP v2 packet and retain extension data in `payload`.
#doc(hidden)
pub fn parse_rtp_packet(bytes : Bytes) -> RtpPacket raise RtpError {
if bytes.length() < 12 {
raise InvalidRtpPacket(reason="RTP packet is shorter than 12 bytes")
}
guard bytes
is [u2be(2), u14be(_), u16be(sequence), u32be(timestamp), u32be(ssrc), ..] else {
raise InvalidRtpPacket(reason="RTP version is not 2")
}
let header_len = rtpsize_header_len(bytes)
if header_len > bytes.length() {
raise InvalidRtpPacket(reason="RTP CSRC or extension header is truncated")
}
{
sequence: sequence.to_uint16(),
timestamp,
ssrc,
header_len,
header: bytes[:header_len].to_owned(),
payload: bytes[header_len:].to_owned(),
}
}