src/rtcp.rs

use anyhow::Result;

use crate::transport_crypto::{TRANSPORT_NONCE_LEN, TRANSPORT_TAG_LEN, TransportCrypto};

// --------------------------------------------------------------------------- // RTCP header (RFC 3550) // ---------------------------------------------------------------------------

pub(crate) const RTCP_HEADER_LEN: usize = 4;

/// Build a 4-byte RTCP fixed header. /// /// text /// 0 1 2 3 /// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// |V=2|P| FMT/RC | PT | length | /// +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ /// /// /// packet_len_bytes is the total on-wire packet length in bytes (must be a /// multiple of 4). The RTCP length field is set to (packet_len_bytes / 4) - 1. pub(crate) fn build_rtcp_header( fmt_or_count: u8, packet_type: u8, packet_len_bytes: usize, ) -> [u8; RTCP_HEADER_LEN] { let mut header = [0u8; RTCP_HEADER_LEN]; let word_count = packet_len_bytes / 4; let length_field = word_count .checked_sub(1) .expect("rtcp packet length must include at least one 32-bit word"); header[0] = 0x80 | (fmt_or_count & 0x1f); // V=2, P=0 header[1] = packet_type; header[2..4].copy_from_slice(&(length_field as u16).to_be_bytes()); header }

/// Build a transport-protected RTCP packet. /// /// This is the free-function equivalent of the former /// TransportCrypto::build_protected_rtcp_packet method. It constructs the /// RTCP header, encrypts the body under the transport AEAD using the header as /// AAD, and returns the complete on-wire packet. pub(crate) fn build_protected_rtcp_packet( crypto: &TransportCrypto, fmt_or_count: u8, packet_type: u8, body: &[u8], ) -> Result<Vec> { let packet_len = RTCP_HEADER_LEN + body.len() + TRANSPORT_TAG_LEN + TRANSPORT_NONCE_LEN; let header = build_rtcp_header(fmt_or_count, packet_type, packet_len); let encrypted = crypto.encrypt(&header, body)?; let mut packet = Vec::with_capacity(header.len() + encrypted.len()); packet.extend_from_slice(&header); packet.extend_from_slice(&encrypted); Ok(packet) }

#[cfg(test)] mod tests { use super::*;

#[test]
fn protected_rtcp_feedback_packets_round_trip() {
    let crypto = TransportCrypto::new(&[5u8; 32], "aead_aes256_gcm_rtpsize")
        .expect("crypto should initialize");
    let sender_ssrc = 0x1122_3344u32;
    let media_ssrc = 0x5566_7788u32;

    let rr_packet = build_protected_rtcp_packet(&crypto, 0, 201, &sender_ssrc.to_be_bytes())
        .expect("rr packet");

    let mut pli_body = [0u8; 8];
    pli_body[0..4].copy_from_slice(&sender_ssrc.to_be_bytes());
    pli_body[4..8].copy_from_slice(&media_ssrc.to_be_bytes());
    let pli_packet =
        build_protected_rtcp_packet(&crypto, 1, 206, &pli_body).expect("pli packet");

    let mut fir_body = [0u8; 16];
    fir_body[0..4].copy_from_slice(&sender_ssrc.to_be_bytes());
    fir_body[8..12].copy_from_slice(&media_ssrc.to_be_bytes());
    let fir_packet =
        build_protected_rtcp_packet(&crypto, 4, 206, &fir_body).expect("fir packet");

    // RR: 4 (header) + 4 (body) + 16 (tag) + 4 (nonce) = 28
    assert_eq!(rr_packet.len(), 28);
    assert_eq!(rr_packet[0], 0x80);
    assert_eq!(rr_packet[1], 201);
    assert_eq!(u16::from_be_bytes([rr_packet[2], rr_packet[3]]), 6);
    let rr_body = crypto
        .decrypt_with_aad(&rr_packet, RTCP_HEADER_LEN)
        .expect("rr decrypt");
    assert_eq!(rr_body, sender_ssrc.to_be_bytes());

    // PLI: 4 + 8 + 16 + 4 = 32
    assert_eq!(pli_packet.len(), 32);
    assert_eq!(pli_packet[0], 0x81);
    assert_eq!(pli_packet[1], 206);
    assert_eq!(u16::from_be_bytes([pli_packet[2], pli_packet[3]]), 7);
    let pli_body = crypto
        .decrypt_with_aad(&pli_packet, RTCP_HEADER_LEN)
        .expect("pli decrypt");
    assert_eq!(&pli_body[0..4], &sender_ssrc.to_be_bytes());
    assert_eq!(&pli_body[4..8], &media_ssrc.to_be_bytes());

    // FIR: 4 + 16 + 16 + 4 = 40
    assert_eq!(fir_packet.len(), 40);
    assert_eq!(fir_packet[0], 0x84);
    assert_eq!(fir_packet[1], 206);
    assert_eq!(u16::from_be_bytes([fir_packet[2], fir_packet[3]]), 9);
    let fir_body = crypto
        .decrypt_with_aad(&fir_packet, RTCP_HEADER_LEN)
        .expect("fir decrypt");
    assert_eq!(&fir_body[0..4], &sender_ssrc.to_be_bytes());
    assert_eq!(&fir_body[4..8], &0u32.to_be_bytes());
    assert_eq!(&fir_body[8..12], &media_ssrc.to_be_bytes());
    assert_eq!(fir_body[12], 0);
    assert_eq!(&fir_body[13..16], &[0, 0, 0]);
}

}