///|
/// A parsed RTP packet with the `rtpsize` cleartext header separated.
pub(all) struct RtpPacket {
sequence : UInt16
timestamp : UInt
ssrc : UInt
header_len : Int
header : Bytes
payload : Bytes
} derive(Debug, Eq)
///|
/// Errors raised while validating an RTP packet.
pub(all) suberror RtpError {
InvalidRtpPacket(reason~ : String)
} derive(Debug, Eq)
///|
/// Build Discord's RTP header for a 20 ms Opus frame.
pub fn build_rtp_header(
sequence~ : UInt16,
timestamp~ : UInt,
ssrc~ : UInt,
) -> 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.
pub fn rtpsize_header_len(bytes : Bytes) -> Int {
if bytes.length() < 12 {
return 0
}
let csrc_count = (bytes[0].to_int() & 0x0f) * 4
let extension_header = if (bytes[0].to_int() & 0x10) != 0 { 4 } else { 0 }
12 + csrc_count + extension_header
}
///|
/// Parse an RTP v2 packet and retain extension data in `payload`.
pub fn parse_rtp_packet(bytes : Bytes) -> RtpPacket raise RtpError {
if bytes.length() < 12 {
raise InvalidRtpPacket(reason="RTP packet is shorter than 12 bytes")
}
if bytes[0].to_int() >> 6 != 2 {
raise InvalidRtpPacket(reason="RTP version is not 2")
}
let header_len = rtpsize_header_len(bytes)
if header_len == 0 || header_len > bytes.length() {
raise InvalidRtpPacket(reason="RTP CSRC or extension header is truncated")
}
let sequence = (bytes[2].to_uint16() << 8) | bytes[3].to_uint16()
let timestamp = read_u32_be(bytes, 4)
let ssrc = read_u32_be(bytes, 8)
{
sequence,
timestamp,
ssrc,
header_len,
header: bytes[:header_len].to_owned(),
payload: bytes[header_len:].to_owned(),
}
}