arcly-stream 0.1.3

An open-extensible live-media streaming kernel: lock-free zero-copy frame fan-out, instant-start GOP cache, a pluggable multi-protocol ingestion layer (RTMP, RTSP, SRT, WHIP/WHEP shipped), and a feature-gated pure-Rust media plane (MPEG-TS/HLS/fMP4) — runtime, config, and metrics free.
Documentation
//! RTCP feedback packet builders (RFC 4585 / RFC 5104).
//!
//! Only the two keyframe-request messages an ingest needs are built here: PLI
//! (Picture Loss Indication) and FIR (Full Intra Request). Both are sent back to
//! a WHIP publisher when the server needs a fresh IDR — e.g. a new subscriber
//! joined and the GOP cache has no keyframe yet.

/// RTCP payload type for Payload-Specific Feedback (PSFB), RFC 4585.
const PT_PSFB: u8 = 206;

/// Build a PLI (Picture Loss Indication) RTCP packet.
///
/// PLI is a PSFB message with feedback message type (FMT) 1 and no parameters
/// (RFC 4585 §6.3.1). `sender_ssrc` is this server's SSRC; `media_ssrc`
/// identifies the publisher's video stream to refresh.
pub fn build_pli(sender_ssrc: u32, media_ssrc: u32) -> Vec<u8> {
    let mut p = Vec::with_capacity(12);
    // V=2, P=0, FMT=1 (PLI).
    p.push(0x80 | 1);
    p.push(PT_PSFB);
    // Length in 32-bit words minus one: header(1) + 2 SSRC words = 3 → 2.
    p.extend_from_slice(&2u16.to_be_bytes());
    p.extend_from_slice(&sender_ssrc.to_be_bytes());
    p.extend_from_slice(&media_ssrc.to_be_bytes());
    p
}

/// Build a FIR (Full Intra Request) RTCP packet, RFC 5104 §4.3.1.
///
/// FIR is a PSFB message with FMT 4, carrying one FCI entry: the target SSRC, a
/// monotonically increasing `seq_nr` (so retransmitted FIRs are deduplicated),
/// and three reserved bytes.
pub fn build_fir(sender_ssrc: u32, media_ssrc: u32, seq_nr: u8) -> Vec<u8> {
    let mut p = Vec::with_capacity(20);
    // V=2, P=0, FMT=4 (FIR).
    p.push(0x80 | 4);
    p.push(PT_PSFB);
    // header(1) + sender(1) + media(1) + FCI(2) = 5 words → length 4.
    p.extend_from_slice(&4u16.to_be_bytes());
    p.extend_from_slice(&sender_ssrc.to_be_bytes());
    p.extend_from_slice(&0u32.to_be_bytes()); // media SSRC field is 0 for FIR
    p.extend_from_slice(&media_ssrc.to_be_bytes()); // FCI: target SSRC
    p.push(seq_nr);
    p.extend_from_slice(&[0, 0, 0]); // reserved
    p
}

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

    #[test]
    fn pli_has_correct_header_and_ssrcs() {
        let p = build_pli(0x1111_1111, 0x2222_2222);
        assert_eq!(p.len(), 12);
        assert_eq!(p[0], 0x81); // V=2, FMT=1
        assert_eq!(p[1], 206); // PSFB
        assert_eq!(u16::from_be_bytes([p[2], p[3]]), 2); // length words-1
        assert_eq!(&p[4..8], &0x1111_1111u32.to_be_bytes());
        assert_eq!(&p[8..12], &0x2222_2222u32.to_be_bytes());
    }

    #[test]
    fn fir_carries_seq_and_target_ssrc() {
        let p = build_fir(1, 0xDEAD_BEEF, 7);
        assert_eq!(p.len(), 20);
        assert_eq!(p[0], 0x84); // FMT=4
        assert_eq!(&p[12..16], &0xDEAD_BEEFu32.to_be_bytes());
        assert_eq!(p[16], 7); // seq_nr
    }
}