use bytes::{Buf, Bytes, BytesMut};
use moq_net::{BoundsExceeded, DecodeError, EncodeError, VarInt};
const PROP_TIMESTAMP: u64 = 0x06;
const PROP_TIMESCALE: u64 = 0x08;
#[derive(Clone, Debug)]
pub struct Frame {
pub timestamp: u64,
pub timescale: Option<u64>,
pub payload: Bytes,
}
#[derive(Debug, Clone, thiserror::Error)]
#[non_exhaustive]
pub enum Error {
#[error("loc frame missing required timestamp property")]
MissingTimestamp,
#[error("malformed loc properties")]
MalformedProperties,
#[error("short buffer")]
ShortBuffer,
#[error("value out of range: {0}")]
OutOfRange(#[from] BoundsExceeded),
}
impl From<DecodeError> for Error {
fn from(err: DecodeError) -> Self {
match err {
DecodeError::Short => Self::ShortBuffer,
_ => Self::MalformedProperties,
}
}
}
impl From<EncodeError> for Error {
fn from(err: EncodeError) -> Self {
match err {
EncodeError::Short => Self::ShortBuffer,
_ => Self::OutOfRange(BoundsExceeded),
}
}
}
pub fn decode(mut buf: Bytes) -> Result<Frame, Error> {
let properties_length: u64 = VarInt::decode_quic(&mut buf)?.into();
let properties_length: usize = properties_length.try_into().map_err(|_| Error::MalformedProperties)?;
if properties_length > buf.remaining() {
return Err(Error::MalformedProperties);
}
let mut props = buf.split_to(properties_length);
let mut timestamp: Option<u64> = None;
let mut timescale: Option<u64> = None;
let mut prev_type: u64 = 0;
let mut first = true;
while props.has_remaining() {
let delta: u64 = VarInt::decode_quic(&mut props)?.into();
let abs = if first {
first = false;
delta
} else {
prev_type.checked_add(delta).ok_or(Error::MalformedProperties)?
};
prev_type = abs;
if abs % 2 == 0 {
let value: u64 = VarInt::decode_quic(&mut props)?.into();
match abs {
PROP_TIMESTAMP => timestamp = Some(value),
PROP_TIMESCALE => {
if value == 0 {
return Err(Error::MalformedProperties);
}
timescale = Some(value);
}
_ => {}
}
} else {
let len: u64 = VarInt::decode_quic(&mut props)?.into();
let len: usize = len.try_into().map_err(|_| Error::MalformedProperties)?;
if len > props.remaining() {
return Err(Error::MalformedProperties);
}
props.advance(len);
}
}
let timestamp = timestamp.ok_or(Error::MissingTimestamp)?;
Ok(Frame {
timestamp,
timescale,
payload: buf,
})
}
pub fn encode(timestamp: u64, payload: &[u8]) -> Result<Bytes, Error> {
let mut props = BytesMut::with_capacity(16);
VarInt::try_from(PROP_TIMESTAMP)?.encode_quic(&mut props)?;
VarInt::try_from(timestamp)?.encode_quic(&mut props)?;
let mut out = BytesMut::with_capacity(props.len() + payload.len() + 8);
VarInt::try_from(props.len() as u64)?.encode_quic(&mut out)?;
out.extend_from_slice(&props);
out.extend_from_slice(payload);
Ok(out.freeze())
}
#[cfg(test)]
mod tests {
use super::*;
fn write_varint(buf: &mut BytesMut, value: u64) {
VarInt::try_from(value).unwrap().encode_quic(buf).unwrap();
}
#[test]
fn roundtrip() {
let payload = Bytes::from_static(b"hello world");
let encoded = encode(12345, &payload).unwrap();
let frame = decode(encoded).unwrap();
assert_eq!(frame.timestamp, 12345);
assert_eq!(frame.timescale, None);
assert_eq!(frame.payload, payload);
}
#[test]
fn decode_per_frame_timescale() {
let mut props = BytesMut::new();
write_varint(&mut props, PROP_TIMESTAMP);
write_varint(&mut props, 96_000);
write_varint(&mut props, PROP_TIMESCALE - PROP_TIMESTAMP); write_varint(&mut props, 48_000);
let mut frame = BytesMut::new();
write_varint(&mut frame, props.len() as u64);
frame.extend_from_slice(&props);
frame.extend_from_slice(b"payload");
let decoded = decode(frame.freeze()).unwrap();
assert_eq!(decoded.timestamp, 96_000);
assert_eq!(decoded.timescale, Some(48_000));
assert_eq!(decoded.payload, Bytes::from_static(b"payload"));
}
#[test]
fn decode_skips_video_config() {
let mut props = BytesMut::new();
write_varint(&mut props, PROP_TIMESTAMP);
write_varint(&mut props, 10);
write_varint(&mut props, 0x0d - PROP_TIMESTAMP); write_varint(&mut props, 3); props.extend_from_slice(&[0x01, 0x02, 0x03]);
let mut frame = BytesMut::new();
write_varint(&mut frame, props.len() as u64);
frame.extend_from_slice(&props);
frame.extend_from_slice(b"data");
let decoded = decode(frame.freeze()).unwrap();
assert_eq!(decoded.timestamp, 10);
assert_eq!(decoded.timescale, None);
assert_eq!(decoded.payload, Bytes::from_static(b"data"));
}
#[test]
fn decode_missing_timestamp_errors() {
let mut props = BytesMut::new();
write_varint(&mut props, PROP_TIMESCALE);
write_varint(&mut props, 1000);
let mut frame = BytesMut::new();
write_varint(&mut frame, props.len() as u64);
frame.extend_from_slice(&props);
frame.extend_from_slice(b"x");
assert!(matches!(decode(frame.freeze()), Err(Error::MissingTimestamp)));
}
#[test]
fn decode_empty_properties_errors() {
let mut frame = BytesMut::new();
write_varint(&mut frame, 0);
frame.extend_from_slice(b"payload");
assert!(matches!(decode(frame.freeze()), Err(Error::MissingTimestamp)));
}
#[test]
fn decode_rejects_zero_timescale() {
let mut props = BytesMut::new();
write_varint(&mut props, PROP_TIMESTAMP);
write_varint(&mut props, 10);
write_varint(&mut props, PROP_TIMESCALE - PROP_TIMESTAMP);
write_varint(&mut props, 0);
let mut frame = BytesMut::new();
write_varint(&mut frame, props.len() as u64);
frame.extend_from_slice(&props);
frame.extend_from_slice(b"x");
assert!(matches!(decode(frame.freeze()), Err(Error::MalformedProperties)));
}
#[test]
fn decode_overflowing_properties_length_errors() {
let mut frame = BytesMut::new();
write_varint(&mut frame, 100); frame.extend_from_slice(&[0x06]);
assert!(matches!(decode(frame.freeze()), Err(Error::MalformedProperties)));
}
}