gap-protocol 1.2.3

Group Audio Protocol — the Opus-based audio sub-protocol of the Group Protocol Stack. Provides per-source replay protection on top of the GBP base layer.
Documentation
//! GAP audio payload codec. Five CBOR keys.

use gbp::CodecError;
use serde::{Deserialize, Serialize};
use serde_bytes::ByteBuf;

/// Audio frame payload.
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct GapPayload {
    /// Audio source identifier (microphone or device).
    #[serde(rename = "msid")]
    pub media_source_id: u32,
    /// 16-bit `rtp_sequence` widened to `u32` for CBOR uint compatibility.
    #[serde(rename = "seq")]
    pub rtp_sequence: u32,
    /// 48 kHz timestamp.
    #[serde(rename = "ts")]
    pub rtp_timestamp: u64,
    /// Key phase (binds the payload to a specific MLS epoch).
    #[serde(rename = "kp")]
    pub key_phase: u32,
    /// Opus frame bytes.
    #[serde(rename = "opus")]
    pub opus_frame: ByteBuf,
}

impl GapPayload {
    /// Builds a 20 ms Opus frame at 48 kHz (960 samples).
    pub fn opus_20ms(media_source_id: u32, rtp_sequence: u16, key_phase: u32, opus: Vec<u8>) -> Self {
        Self {
            media_source_id,
            rtp_sequence: rtp_sequence as u32,
            rtp_timestamp: 960,
            key_phase,
            opus_frame: ByteBuf::from(opus),
        }
    }

    /// Builds an Opus frame with an explicit `rtp_timestamp`.
    /// Prefer [`GapPayload::opus_20ms`] for the common 48 kHz / 20 ms case.
    pub fn with_timestamp(
        media_source_id: u32,
        rtp_sequence: u16,
        rtp_timestamp: u64,
        key_phase: u32,
        opus: Vec<u8>,
    ) -> Self {
        Self {
            media_source_id,
            rtp_sequence: rtp_sequence as u32,
            rtp_timestamp,
            key_phase,
            opus_frame: ByteBuf::from(opus),
        }
    }

    /// CBOR-encodes the payload.
    pub fn to_cbor(&self) -> Vec<u8> {
        let mut buf = Vec::new();
        ciborium::into_writer(self, &mut buf).expect("cbor encode");
        buf
    }

    /// Decodes a CBOR-encoded payload.
    pub fn from_cbor(data: &[u8]) -> Result<Self, CodecError> {
        ciborium::from_reader(data).map_err(|e| CodecError::Decode(e.to_string()))
    }
}