use str0m::rtp::{Extension, ExtensionSerializer, ExtensionValues};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct KeyEpoch(pub u64);
impl KeyEpoch {
#[must_use]
pub fn new(kid: u64) -> Self {
Self(kid)
}
#[must_use]
pub fn as_u64(self) -> u64 {
self.0
}
}
pub const SFRAME_KID_WIDTH: usize = 4;
#[derive(Debug, Clone, Copy, Default)]
pub struct KeyEpochSerializer;
impl ExtensionSerializer for KeyEpochSerializer {
fn write_to(&self, buf: &mut [u8], ev: &ExtensionValues) -> usize {
let Some(ke) = ev.user_values.get::<KeyEpoch>() else {
return 0;
};
let Ok(kid32) = u32::try_from(ke.as_u64()) else {
return 0;
};
if buf.len() < SFRAME_KID_WIDTH {
return 0;
}
buf[..SFRAME_KID_WIDTH].copy_from_slice(&kid32.to_be_bytes());
SFRAME_KID_WIDTH
}
fn parse_value(&self, buf: &[u8], ev: &mut ExtensionValues) -> bool {
let Ok(bytes) = <[u8; SFRAME_KID_WIDTH]>::try_from(buf) else {
return false;
};
ev.user_values
.set(KeyEpoch::new(u64::from(u32::from_be_bytes(bytes))));
true
}
fn is_video(&self) -> bool {
true
}
fn is_audio(&self) -> bool {
true
}
}
#[must_use]
pub fn sframe_key_id_extension(uri: &str) -> Extension {
Extension::with_serializer(uri, KeyEpochSerializer)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn key_epoch_newtype_roundtrip() {
let k = KeyEpoch::new(42);
assert_eq!(k.as_u64(), 42);
assert_eq!(KeyEpoch::new(0).as_u64(), 0);
}
#[test]
fn key_epoch_serializer_wire_roundtrip() {
let ser = KeyEpochSerializer;
for kid in [0u64, 1, 7, 255, 256, 65_535, u64::from(u32::MAX)] {
let mut out = ExtensionValues::default();
out.user_values.set(KeyEpoch::new(kid));
let mut buf = [0u8; 16];
let n = ser.write_to(&mut buf, &out);
assert_eq!(n, SFRAME_KID_WIDTH, "kid={kid} must write a fixed 4 bytes");
let mut parsed = ExtensionValues::default();
assert!(ser.parse_value(&buf[..n], &mut parsed), "kid={kid} parse");
assert_eq!(
parsed.user_values.get::<KeyEpoch>().copied(),
Some(KeyEpoch::new(kid)),
"kid={kid} did not round-trip on the wire"
);
}
}
#[test]
fn key_epoch_serializer_omits_kid_above_u32() {
let ser = KeyEpochSerializer;
let mut out = ExtensionValues::default();
out.user_values.set(KeyEpoch::new(u64::from(u32::MAX) + 1));
let mut buf = [0u8; 16];
assert_eq!(ser.write_to(&mut buf, &out), 0);
}
#[test]
fn key_epoch_serializer_refuses_short_buffer() {
let ser = KeyEpochSerializer;
let mut out = ExtensionValues::default();
out.user_values.set(KeyEpoch::new(7));
let mut buf = [0u8; SFRAME_KID_WIDTH - 1];
assert_eq!(ser.write_to(&mut buf, &out), 0);
}
#[test]
fn key_epoch_serializer_absent_writes_nothing() {
let ser = KeyEpochSerializer;
let ev = ExtensionValues::default();
let mut buf = [0u8; 16];
assert_eq!(ser.write_to(&mut buf, &ev), 0);
}
#[test]
fn key_epoch_serializer_rejects_wrong_width() {
let ser = KeyEpochSerializer;
let mut parsed = ExtensionValues::default();
assert!(!ser.parse_value(&[], &mut parsed));
assert!(!ser.parse_value(&[0u8; 3], &mut parsed));
assert!(!ser.parse_value(&[0u8; 5], &mut parsed));
assert!(!ser.parse_value(&[0u8; 9], &mut parsed));
assert!(ser.parse_value(&[0u8; SFRAME_KID_WIDTH], &mut parsed));
}
}