use bytes::{Buf, BufMut};
use super::types::ObjectStatus;
use crate::error::CodecError;
use crate::varint::VarInt;
fn skip(buf: &mut impl Buf, len: u64) -> Result<(), CodecError> {
let len = usize::try_from(len).map_err(|_| CodecError::UnexpectedEnd)?;
if buf.remaining() < len {
return Err(CodecError::UnexpectedEnd);
}
buf.advance(len);
Ok(())
}
fn check_extensions_against_status(
status: Option<u64>,
extension_headers_len: u64,
) -> Result<(), CodecError> {
if status == Some(ObjectStatus::ObjectDoesNotExist.as_u64()) && extension_headers_len != 0 {
return Err(CodecError::ExtensionsOnNonExistentObject(
usize::try_from(extension_headers_len).unwrap_or(usize::MAX),
));
}
Ok(())
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SubgroupStreamType(u8);
impl SubgroupStreamType {
pub fn as_u8(self) -> u8 {
self.0
}
pub fn from_u8(v: u8) -> Option<Self> {
if (0x10..=0x15).contains(&v) || (0x18..=0x1D).contains(&v) {
Some(SubgroupStreamType(v))
} else {
None
}
}
pub fn from_flags(
subgroup_id_field_present: bool,
subgroup_id_is_first_object: bool,
extensions_present: bool,
end_of_group: bool,
) -> Self {
let mut v: u8 = 0x10;
if extensions_present {
v |= 0x01;
}
if subgroup_id_field_present {
v |= 0x04;
} else if subgroup_id_is_first_object {
v |= 0x02;
}
if end_of_group {
v |= 0x08;
}
SubgroupStreamType(v)
}
pub fn has_subgroup_id_field(self) -> bool {
self.0 & 0x04 != 0
}
pub fn subgroup_id_is_first_object(self) -> bool {
!self.has_subgroup_id_field() && (self.0 & 0x02 != 0)
}
pub fn extensions_present(self) -> bool {
self.0 & 0x01 != 0
}
pub fn contains_end_of_group(self) -> bool {
self.0 & 0x08 != 0
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SubgroupHeader {
pub stream_type: SubgroupStreamType,
pub track_alias: VarInt,
pub group_id: VarInt,
pub subgroup_id: Option<VarInt>,
pub publisher_priority: u8,
}
impl SubgroupHeader {
pub fn encode(&self, buf: &mut impl BufMut) {
VarInt::from_u64(self.stream_type.as_u8() as u64).unwrap().encode(buf);
self.track_alias.encode(buf);
self.group_id.encode(buf);
if self.stream_type.has_subgroup_id_field() {
let sg = self.subgroup_id.unwrap_or_else(|| VarInt::from_u64(0).unwrap());
sg.encode(buf);
}
buf.put_u8(self.publisher_priority);
}
pub fn encode_checked(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
if self.stream_type.has_subgroup_id_field() != self.subgroup_id.is_some() {
return Err(CodecError::InvalidField);
}
self.encode(buf);
Ok(())
}
pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
let type_val = VarInt::decode(buf)?.into_inner();
if type_val > 0xFF {
return Err(stream_type_error(type_val));
}
let stream_type = SubgroupStreamType::from_u8(type_val as u8)
.ok_or_else(|| stream_type_error(type_val))?;
let track_alias = VarInt::decode(buf)?;
let group_id = VarInt::decode(buf)?;
let subgroup_id =
if stream_type.has_subgroup_id_field() { Some(VarInt::decode(buf)?) } else { None };
if buf.remaining() < 1 {
return Err(CodecError::UnexpectedEnd);
}
let publisher_priority = buf.get_u8();
Ok(SubgroupHeader { stream_type, track_alias, group_id, subgroup_id, publisher_priority })
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SubgroupObject {
pub object_id: VarInt,
pub extension_headers: Vec<u8>,
pub status: Option<ObjectStatus>,
pub payload: Vec<u8>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SubgroupObjectMeta {
pub object_id: u64,
pub extension_headers_len: u64,
pub payload_length: u64,
pub status: Option<u64>,
pub wire_len: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PayloadPermission {
Permitted,
Forbidden,
}
impl PayloadPermission {
pub fn for_status(status: ObjectStatus) -> Self {
match status {
ObjectStatus::Normal => PayloadPermission::Permitted,
ObjectStatus::ObjectDoesNotExist => PayloadPermission::Forbidden,
ObjectStatus::EndOfGroup => PayloadPermission::Forbidden,
ObjectStatus::EndOfTrack => PayloadPermission::Forbidden,
}
}
pub fn permits(self) -> bool {
matches!(self, PayloadPermission::Permitted)
}
}
impl SubgroupObject {
pub fn status(&self) -> ObjectStatus {
if self.payload.is_empty() {
self.status.unwrap_or(ObjectStatus::Normal)
} else {
ObjectStatus::Normal
}
}
pub fn permits_payload(&self) -> bool {
PayloadPermission::for_status(self.status()).permits()
}
}
impl SubgroupObjectMeta {
pub fn payload_permission(&self) -> Option<PayloadPermission> {
match self.status {
None => Some(PayloadPermission::Permitted),
Some(code) => ObjectStatus::from_u64(code).map(PayloadPermission::for_status),
}
}
}
#[derive(Debug, Clone)]
pub struct SubgroupObjectReader {
extensions_present: bool,
prev_object_id: Option<u64>,
}
impl SubgroupObjectReader {
pub fn new(header: &SubgroupHeader) -> Self {
Self { extensions_present: header.stream_type.extensions_present(), prev_object_id: None }
}
pub fn read_object(&mut self, buf: &mut impl Buf) -> Result<SubgroupObject, CodecError> {
let delta = VarInt::decode(buf)?.into_inner();
let object_id_val = match self.prev_object_id {
None => delta,
Some(prev) => prev
.checked_add(1)
.and_then(|v| v.checked_add(delta))
.ok_or(CodecError::InvalidField)?,
};
self.prev_object_id = Some(object_id_val);
let object_id = VarInt::from_u64(object_id_val).map_err(|_| CodecError::InvalidField)?;
let extension_headers = if self.extensions_present {
let ext_len = VarInt::decode(buf)?.into_inner() as usize;
crate::types::read_bytes(buf, ext_len)?
} else {
Vec::new()
};
let payload_length = VarInt::decode(buf)?.into_inner() as usize;
let (status, payload) = if payload_length == 0 {
let status_val = VarInt::decode(buf)?.into_inner();
let status = ObjectStatus::from_u64(status_val).ok_or(CodecError::InvalidField)?;
(Some(status), Vec::new())
} else {
let payload = crate::types::read_bytes(buf, payload_length)?;
(None, payload)
};
check_extensions_against_status(
status.map(|s| s.as_u64()),
extension_headers.len() as u64,
)?;
Ok(SubgroupObject { object_id, extension_headers, status, payload })
}
pub fn read_object_meta(
&mut self,
buf: &mut impl Buf,
) -> Result<SubgroupObjectMeta, CodecError> {
let start = buf.remaining();
let delta = VarInt::decode(buf)?.into_inner();
let object_id_val = match self.prev_object_id {
None => delta,
Some(prev) => prev
.checked_add(1)
.and_then(|v| v.checked_add(delta))
.ok_or(CodecError::InvalidField)?,
};
self.prev_object_id = Some(object_id_val);
let object_id =
VarInt::from_u64(object_id_val).map_err(|_| CodecError::InvalidField)?.into_inner();
let extension_headers_len = if self.extensions_present {
let ext_len = VarInt::decode(buf)?.into_inner();
skip(buf, ext_len)?;
ext_len
} else {
0
};
let payload_length = VarInt::decode(buf)?.into_inner();
let status = if payload_length == 0 {
let status_val = VarInt::decode(buf)?.into_inner();
Some(ObjectStatus::from_u64(status_val).ok_or(CodecError::InvalidField)?.as_u64())
} else {
skip(buf, payload_length)?;
None
};
check_extensions_against_status(status, extension_headers_len)?;
Ok(SubgroupObjectMeta {
object_id,
extension_headers_len,
payload_length,
status,
wire_len: (start - buf.remaining()) as u64,
})
}
pub fn write_object(
&mut self,
object: &SubgroupObject,
buf: &mut impl BufMut,
) -> Result<(), CodecError> {
let explicit_status = match object.status {
Some(status) if status != ObjectStatus::Normal => {
if !object.payload.is_empty() {
return Err(CodecError::InvalidField);
}
Some(status)
}
Some(ObjectStatus::Normal) if object.payload.is_empty() => Some(ObjectStatus::Normal),
_ => None,
};
if !self.extensions_present && !object.extension_headers.is_empty() {
return Err(CodecError::InvalidField);
}
check_extensions_against_status(
explicit_status.map(|s| s.as_u64()),
object.extension_headers.len() as u64,
)?;
let oid = object.object_id.into_inner();
let delta = match self.prev_object_id {
None => oid,
Some(prev) => oid
.checked_sub(prev)
.and_then(|v| v.checked_sub(1))
.ok_or(CodecError::InvalidField)?,
};
VarInt::from_u64(delta).map_err(|_| CodecError::InvalidField)?.encode(buf);
if self.extensions_present {
VarInt::from_u64(object.extension_headers.len() as u64)
.map_err(|_| CodecError::InvalidField)?
.encode(buf);
buf.put_slice(&object.extension_headers);
}
if let Some(status) = explicit_status {
VarInt::from_u64(0).unwrap().encode(buf);
VarInt::from_u64(status.as_u64()).unwrap().encode(buf);
} else {
VarInt::from_u64(object.payload.len() as u64)
.map_err(|_| CodecError::InvalidField)?
.encode(buf);
buf.put_slice(&object.payload);
}
self.prev_object_id = Some(oid);
Ok(())
}
}
fn stream_type_error(raw: u64) -> CodecError {
let assigned = raw == FETCH_STREAM_TYPE as u64
|| (raw <= 0xFF && SubgroupStreamType::from_u8(raw as u8).is_some());
if assigned {
CodecError::InvalidField
} else {
CodecError::UnknownStreamType(raw)
}
}
pub const FETCH_STREAM_TYPE: u8 = 0x05;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FetchHeader {
pub request_id: VarInt,
}
impl FetchHeader {
pub fn encode(&self, buf: &mut impl BufMut) {
VarInt::from_u64(FETCH_STREAM_TYPE as u64).unwrap().encode(buf);
self.request_id.encode(buf);
}
pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
let type_val = VarInt::decode(buf)?.into_inner();
if type_val != FETCH_STREAM_TYPE as u64 {
return Err(stream_type_error(type_val));
}
let request_id = VarInt::decode(buf)?;
Ok(FetchHeader { request_id })
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FetchObject {
pub group_id: VarInt,
pub subgroup_id: VarInt,
pub object_id: VarInt,
pub publisher_priority: u8,
pub extension_headers: Vec<u8>,
pub status: Option<ObjectStatus>,
pub payload: Vec<u8>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct FetchObjectMeta {
pub group_id: u64,
pub subgroup_id: u64,
pub object_id: u64,
pub publisher_priority: u8,
pub extension_headers_len: u64,
pub payload_length: u64,
pub status: Option<u64>,
pub wire_len: u64,
}
impl FetchObjectMeta {
pub fn payload_permission(&self) -> Option<PayloadPermission> {
match self.status {
None => Some(PayloadPermission::Permitted),
Some(code) => ObjectStatus::from_u64(code).map(PayloadPermission::for_status),
}
}
}
impl FetchObject {
pub fn status(&self) -> ObjectStatus {
if self.payload.is_empty() {
self.status.unwrap_or(ObjectStatus::Normal)
} else {
ObjectStatus::Normal
}
}
pub fn permits_payload(&self) -> bool {
PayloadPermission::for_status(self.status()).permits()
}
pub fn encode_checked(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
if matches!(self.status, Some(status) if status != ObjectStatus::Normal)
&& !self.payload.is_empty()
{
return Err(CodecError::InvalidField);
}
check_extensions_against_status(
self.status.map(|s| s.as_u64()),
self.extension_headers.len() as u64,
)?;
self.encode(buf);
Ok(())
}
pub fn encode(&self, buf: &mut impl BufMut) {
self.group_id.encode(buf);
self.subgroup_id.encode(buf);
self.object_id.encode(buf);
buf.put_u8(self.publisher_priority);
VarInt::from_u64(self.extension_headers.len() as u64).unwrap().encode(buf);
buf.put_slice(&self.extension_headers);
if let Some(status) = self.status {
VarInt::from_u64(0).unwrap().encode(buf);
VarInt::from_u64(status.as_u64()).unwrap().encode(buf);
} else {
VarInt::from_u64(self.payload.len() as u64).unwrap().encode(buf);
buf.put_slice(&self.payload);
}
}
pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
let group_id = VarInt::decode(buf)?;
let subgroup_id = VarInt::decode(buf)?;
let object_id = VarInt::decode(buf)?;
if buf.remaining() < 1 {
return Err(CodecError::UnexpectedEnd);
}
let publisher_priority = buf.get_u8();
let ext_len = VarInt::decode(buf)?.into_inner() as usize;
let extension_headers = crate::types::read_bytes(buf, ext_len)?;
let payload_length = VarInt::decode(buf)?.into_inner() as usize;
let (status, payload) = if payload_length == 0 {
let status_val = VarInt::decode(buf)?.into_inner();
let status = ObjectStatus::from_u64(status_val).ok_or(CodecError::InvalidField)?;
(Some(status), Vec::new())
} else {
(None, crate::types::read_bytes(buf, payload_length)?)
};
check_extensions_against_status(
status.map(|s| s.as_u64()),
extension_headers.len() as u64,
)?;
Ok(FetchObject {
group_id,
subgroup_id,
object_id,
publisher_priority,
extension_headers,
status,
payload,
})
}
pub fn decode_meta(buf: &mut impl Buf) -> Result<FetchObjectMeta, CodecError> {
let start = buf.remaining();
let group_id = VarInt::decode(buf)?.into_inner();
let subgroup_id = VarInt::decode(buf)?.into_inner();
let object_id = VarInt::decode(buf)?.into_inner();
if buf.remaining() < 1 {
return Err(CodecError::UnexpectedEnd);
}
let publisher_priority = buf.get_u8();
let extension_headers_len = VarInt::decode(buf)?.into_inner();
skip(buf, extension_headers_len)?;
let payload_length = VarInt::decode(buf)?.into_inner();
let status = if payload_length == 0 {
let status_val = VarInt::decode(buf)?.into_inner();
Some(ObjectStatus::from_u64(status_val).ok_or(CodecError::InvalidField)?.as_u64())
} else {
skip(buf, payload_length)?;
None
};
check_extensions_against_status(status, extension_headers_len)?;
Ok(FetchObjectMeta {
group_id,
subgroup_id,
object_id,
publisher_priority,
extension_headers_len,
payload_length,
status,
wire_len: (start - buf.remaining()) as u64,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DatagramType(u8);
impl DatagramType {
pub fn as_u8(self) -> u8 {
self.0
}
pub fn from_u8(v: u8) -> Option<Self> {
if (0x00..=0x07).contains(&v) || v == 0x20 || v == 0x21 {
Some(DatagramType(v))
} else {
None
}
}
pub fn payload(object_id_present: bool, extensions_present: bool, end_of_group: bool) -> Self {
let mut v: u8 = 0x00;
if extensions_present {
v |= 0x01;
}
if end_of_group {
v |= 0x02;
}
if !object_id_present {
v |= 0x04;
}
DatagramType(v)
}
pub fn status(extensions_present: bool) -> Self {
if extensions_present {
DatagramType(0x21)
} else {
DatagramType(0x20)
}
}
pub fn is_status(self) -> bool {
self.0 >= 0x20
}
pub fn object_id_present(self) -> bool {
if self.is_status() {
true
} else {
self.0 & 0x04 == 0
}
}
pub fn end_of_group(self) -> bool {
!self.is_status() && (self.0 & 0x02 != 0)
}
pub fn extensions_present(self) -> bool {
self.0 & 0x01 != 0
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DatagramObject {
pub datagram_type: DatagramType,
pub track_alias: VarInt,
pub group_id: VarInt,
pub object_id: VarInt,
pub publisher_priority: u8,
pub extension_headers: Vec<u8>,
pub status: Option<ObjectStatus>,
pub payload: Vec<u8>,
}
impl DatagramObject {
pub fn encode_checked(&self, buf: &mut impl BufMut) -> Result<(), CodecError> {
if self.datagram_type.is_status() {
if !self.payload.is_empty() {
return Err(CodecError::InvalidField);
}
} else if matches!(self.status, Some(status) if status != ObjectStatus::Normal) {
return Err(CodecError::InvalidField);
}
if self.datagram_type.extensions_present() {
if self.extension_headers.is_empty() {
return Err(CodecError::InvalidField);
}
} else if !self.extension_headers.is_empty() {
return Err(CodecError::InvalidField);
}
check_extensions_against_status(
self.status.map(|s| s.as_u64()),
self.extension_headers.len() as u64,
)?;
self.encode(buf);
Ok(())
}
pub fn encode(&self, buf: &mut impl BufMut) {
VarInt::from_u64(self.datagram_type.as_u8() as u64).unwrap().encode(buf);
self.track_alias.encode(buf);
self.group_id.encode(buf);
if self.datagram_type.object_id_present() {
self.object_id.encode(buf);
}
buf.put_u8(self.publisher_priority);
if self.datagram_type.extensions_present() {
VarInt::from_u64(self.extension_headers.len() as u64).unwrap().encode(buf);
buf.put_slice(&self.extension_headers);
}
if self.datagram_type.is_status() {
let status = self.status.unwrap_or(ObjectStatus::Normal);
VarInt::from_u64(status.as_u64()).unwrap().encode(buf);
} else {
buf.put_slice(&self.payload);
}
}
pub fn decode(buf: &mut impl Buf) -> Result<Self, CodecError> {
let type_val = VarInt::decode(buf)?.into_inner();
if type_val > 0xFF {
return Err(CodecError::UnknownDatagramType(type_val));
}
let datagram_type = DatagramType::from_u8(type_val as u8)
.ok_or(CodecError::UnknownDatagramType(type_val))?;
let track_alias = VarInt::decode(buf)?;
let group_id = VarInt::decode(buf)?;
let object_id = if datagram_type.object_id_present() {
VarInt::decode(buf)?
} else {
VarInt::from_u64(0).unwrap()
};
if buf.remaining() < 1 {
return Err(CodecError::UnexpectedEnd);
}
let publisher_priority = buf.get_u8();
let extension_headers = if datagram_type.extensions_present() {
let ext_len = VarInt::decode(buf)?.into_inner() as usize;
if ext_len == 0 {
return Err(CodecError::InvalidField);
}
crate::types::read_bytes(buf, ext_len)?
} else {
Vec::new()
};
let (status, payload) = if datagram_type.is_status() {
let status_val = VarInt::decode(buf)?.into_inner();
let status = ObjectStatus::from_u64(status_val).ok_or(CodecError::InvalidField)?;
(Some(status), Vec::new())
} else {
let remaining = buf.remaining();
(None, crate::types::read_bytes(buf, remaining)?)
};
check_extensions_against_status(
status.map(|s| s.as_u64()),
extension_headers.len() as u64,
)?;
Ok(DatagramObject {
datagram_type,
track_alias,
group_id,
object_id,
publisher_priority,
extension_headers,
status,
payload,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
fn vi(v: u64) -> VarInt {
VarInt::from_u64(v).unwrap()
}
#[test]
fn subgroup_type_0x10_all_off() {
let t = SubgroupStreamType::from_u8(0x10).unwrap();
assert!(!t.has_subgroup_id_field());
assert!(!t.subgroup_id_is_first_object());
assert!(!t.extensions_present());
assert!(!t.contains_end_of_group());
}
#[test]
fn subgroup_type_0x15_explicit_with_ext() {
let t = SubgroupStreamType::from_u8(0x15).unwrap();
assert!(t.has_subgroup_id_field());
assert!(!t.subgroup_id_is_first_object());
assert!(t.extensions_present());
assert!(!t.contains_end_of_group());
}
#[test]
fn subgroup_type_0x1d_all_on() {
let t = SubgroupStreamType::from_u8(0x1D).unwrap();
assert!(t.has_subgroup_id_field());
assert!(t.extensions_present());
assert!(t.contains_end_of_group());
}
#[test]
fn subgroup_type_0x12_first_object() {
let t = SubgroupStreamType::from_u8(0x12).unwrap();
assert!(!t.has_subgroup_id_field());
assert!(t.subgroup_id_is_first_object());
assert!(!t.extensions_present());
}
#[test]
fn subgroup_type_rejects_undefined() {
for bad in [0x00u8, 0x0F, 0x16, 0x17, 0x1E, 0x1F, 0x20] {
assert!(SubgroupStreamType::from_u8(bad).is_none(), "0x{bad:02x} should be rejected");
}
}
#[test]
fn subgroup_type_from_flags_roundtrip() {
for &f_sg in &[false, true] {
for &f_first in &[false, true] {
for &f_ext in &[false, true] {
for &f_eog in &[false, true] {
let t = SubgroupStreamType::from_flags(f_sg, f_first, f_ext, f_eog);
assert_eq!(t.has_subgroup_id_field(), f_sg);
if !f_sg {
assert_eq!(t.subgroup_id_is_first_object(), f_first);
}
assert_eq!(t.extensions_present(), f_ext);
assert_eq!(t.contains_end_of_group(), f_eog);
}
}
}
}
}
#[test]
fn subgroup_header_roundtrip_0x10() {
let h = SubgroupHeader {
stream_type: SubgroupStreamType::from_u8(0x10).unwrap(),
track_alias: vi(1),
group_id: vi(0),
subgroup_id: None,
publisher_priority: 128,
};
let mut buf = Vec::new();
h.encode(&mut buf);
assert_eq!(buf[0], 0x10);
let decoded = SubgroupHeader::decode(&mut &buf[..]).unwrap();
assert_eq!(decoded, h);
}
#[test]
fn subgroup_header_roundtrip_explicit_subgroup() {
let h = SubgroupHeader {
stream_type: SubgroupStreamType::from_u8(0x14).unwrap(),
track_alias: vi(5),
group_id: vi(10),
subgroup_id: Some(vi(2)),
publisher_priority: 64,
};
let mut buf = Vec::new();
h.encode(&mut buf);
let decoded = SubgroupHeader::decode(&mut &buf[..]).unwrap();
assert_eq!(decoded, h);
}
#[test]
fn subgroup_header_decode_rejects_bad_type() {
let buf = [0x16u8, 0x01, 0x00, 0x80];
let err = SubgroupHeader::decode(&mut &buf[..]).unwrap_err();
assert!(
matches!(err, CodecError::UnknownStreamType(0x16)),
"an unassigned Type must be named as one, got {err:?}"
);
}
#[test]
fn subgroup_header_decode_does_not_call_the_fetch_type_unknown() {
let buf = [0x05u8, 0x01, 0x00, 0x80];
let err = SubgroupHeader::decode(&mut &buf[..]).unwrap_err();
assert!(
matches!(err, CodecError::InvalidField),
"a fetch stream at the subgroup reader must be refused without naming the \
unknown-stream-type rule, got {err:?}"
);
}
#[test]
fn subgroup_reader_delta_sequential_ids() {
let header = SubgroupHeader {
stream_type: SubgroupStreamType::from_u8(0x10).unwrap(),
track_alias: vi(1),
group_id: vi(0),
subgroup_id: None,
publisher_priority: 0,
};
let mut write = SubgroupObjectReader::new(&header);
let mut buf = Vec::new();
for i in 0..3u64 {
let obj = SubgroupObject {
object_id: vi(i),
extension_headers: vec![],
status: None,
payload: vec![0xAA + i as u8; 4],
};
write.write_object(&obj, &mut buf).unwrap();
}
let mut read = SubgroupObjectReader::new(&header);
let mut cursor = &buf[..];
let o0 = read.read_object(&mut cursor).unwrap();
assert_eq!(o0.object_id.into_inner(), 0);
assert_eq!(o0.payload, vec![0xAA; 4]);
let o1 = read.read_object(&mut cursor).unwrap();
assert_eq!(o1.object_id.into_inner(), 1);
let o2 = read.read_object(&mut cursor).unwrap();
assert_eq!(o2.object_id.into_inner(), 2);
}
#[test]
fn subgroup_reader_delta_sparse_ids() {
let header = SubgroupHeader {
stream_type: SubgroupStreamType::from_u8(0x10).unwrap(),
track_alias: vi(1),
group_id: vi(0),
subgroup_id: None,
publisher_priority: 0,
};
let mut write = SubgroupObjectReader::new(&header);
let mut buf = Vec::new();
for &id in &[5u64, 10, 11] {
write
.write_object(
&SubgroupObject {
object_id: vi(id),
extension_headers: vec![],
status: None,
payload: vec![1, 2, 3],
},
&mut buf,
)
.unwrap();
}
let mut read = SubgroupObjectReader::new(&header);
let mut cursor = &buf[..];
assert_eq!(read.read_object(&mut cursor).unwrap().object_id.into_inner(), 5);
assert_eq!(read.read_object(&mut cursor).unwrap().object_id.into_inner(), 10);
assert_eq!(read.read_object(&mut cursor).unwrap().object_id.into_inner(), 11);
}
#[test]
fn subgroup_reader_with_extensions() {
let header = SubgroupHeader {
stream_type: SubgroupStreamType::from_u8(0x11).unwrap(),
track_alias: vi(1),
group_id: vi(0),
subgroup_id: None,
publisher_priority: 0,
};
let mut write = SubgroupObjectReader::new(&header);
let mut buf = Vec::new();
write
.write_object(
&SubgroupObject {
object_id: vi(0),
extension_headers: vec![0x01, 0x02, 0x03],
status: None,
payload: vec![0xFF],
},
&mut buf,
)
.unwrap();
let mut read = SubgroupObjectReader::new(&header);
let o = read.read_object(&mut &buf[..]).unwrap();
assert_eq!(o.extension_headers, vec![0x01, 0x02, 0x03]);
assert_eq!(o.payload, vec![0xFF]);
}
#[test]
fn subgroup_reader_status_object() {
let header = SubgroupHeader {
stream_type: SubgroupStreamType::from_u8(0x10).unwrap(),
track_alias: vi(1),
group_id: vi(0),
subgroup_id: None,
publisher_priority: 0,
};
let mut write = SubgroupObjectReader::new(&header);
let mut buf = Vec::new();
write
.write_object(
&SubgroupObject {
object_id: vi(7),
extension_headers: vec![],
status: Some(ObjectStatus::EndOfGroup),
payload: vec![],
},
&mut buf,
)
.unwrap();
let mut read = SubgroupObjectReader::new(&header);
let o = read.read_object(&mut &buf[..]).unwrap();
assert_eq!(o.object_id.into_inner(), 7);
assert_eq!(o.status, Some(ObjectStatus::EndOfGroup));
assert!(o.payload.is_empty());
}
#[test]
fn subgroup_reader_meta_matches_read_object() {
let header = SubgroupHeader {
stream_type: SubgroupStreamType::from_u8(0x11).unwrap(),
track_alias: vi(1),
group_id: vi(0),
subgroup_id: None,
publisher_priority: 0,
};
let mut write = SubgroupObjectReader::new(&header);
let mut buf = Vec::new();
for (id, status) in
[(0u64, None), (4, Some(ObjectStatus::EndOfGroup)), (9, None)].into_iter()
{
write
.write_object(
&SubgroupObject {
object_id: vi(id),
extension_headers: vec![0x0A, 0x0B],
status,
payload: if status.is_some() { vec![] } else { vec![0xEE; 3] },
},
&mut buf,
)
.unwrap();
}
let mut full = SubgroupObjectReader::new(&header);
let mut meta = SubgroupObjectReader::new(&header);
let mut full_cursor = &buf[..];
let mut meta_cursor = &buf[..];
for _ in 0..3 {
let before = meta_cursor.remaining();
let o = full.read_object(&mut full_cursor).unwrap();
let m = meta.read_object_meta(&mut meta_cursor).unwrap();
assert_eq!(m.object_id, o.object_id.into_inner());
assert_eq!(m.extension_headers_len, o.extension_headers.len() as u64);
assert_eq!(m.payload_length, o.payload.len() as u64);
assert_eq!(m.status, o.status.map(|s| s.as_u64()));
assert_eq!(m.wire_len, (before - meta_cursor.remaining()) as u64);
assert_eq!(full_cursor.remaining(), meta_cursor.remaining());
}
assert!(meta_cursor.is_empty());
}
#[test]
fn subgroup_reader_meta_short_buffer_is_unexpected_end() {
let header = SubgroupHeader {
stream_type: SubgroupStreamType::from_u8(0x10).unwrap(),
track_alias: vi(1),
group_id: vi(0),
subgroup_id: None,
publisher_priority: 0,
};
let mut write = SubgroupObjectReader::new(&header);
let mut buf = Vec::new();
write
.write_object(
&SubgroupObject {
object_id: vi(0),
extension_headers: vec![],
status: None,
payload: vec![1, 2, 3, 4],
},
&mut buf,
)
.unwrap();
for cut in 1..buf.len() {
let mut read = SubgroupObjectReader::new(&header);
let err = read.read_object_meta(&mut &buf[..cut]).unwrap_err();
assert!(
matches!(err, CodecError::UnexpectedEnd | CodecError::VarInt(_)),
"cut {cut} gave {err:?}"
);
}
}
#[test]
fn fetch_header_roundtrip() {
let h = FetchHeader { request_id: vi(99) };
let mut buf = Vec::new();
h.encode(&mut buf);
assert_eq!(buf[0], 0x05);
assert_eq!(FetchHeader::decode(&mut &buf[..]).unwrap(), h);
}
#[test]
fn fetch_header_rejects_wrong_type() {
let buf = [0x10u8, 0x05];
assert!(FetchHeader::decode(&mut &buf[..]).is_err());
}
#[test]
fn fetch_object_roundtrip_with_payload() {
let obj = FetchObject {
group_id: vi(3),
subgroup_id: vi(1),
object_id: vi(7),
publisher_priority: 200,
extension_headers: vec![0xAA, 0xBB],
status: None,
payload: vec![1, 2, 3, 4],
};
let mut buf = Vec::new();
obj.encode(&mut buf);
assert_eq!(FetchObject::decode(&mut &buf[..]).unwrap(), obj);
}
#[test]
fn fetch_object_roundtrip_status() {
let obj = FetchObject {
group_id: vi(3),
subgroup_id: vi(1),
object_id: vi(8),
publisher_priority: 200,
extension_headers: vec![],
status: Some(ObjectStatus::ObjectDoesNotExist),
payload: vec![],
};
let mut buf = Vec::new();
obj.encode(&mut buf);
assert_eq!(FetchObject::decode(&mut &buf[..]).unwrap(), obj);
}
#[test]
fn fetch_object_meta_matches_decode() {
for obj in [
FetchObject {
group_id: vi(3),
subgroup_id: vi(1),
object_id: vi(7),
publisher_priority: 200,
extension_headers: vec![0xAA, 0xBB],
status: None,
payload: vec![1, 2, 3, 4],
},
FetchObject {
group_id: vi(4),
subgroup_id: vi(0),
object_id: vi(8),
publisher_priority: 1,
extension_headers: vec![],
status: Some(ObjectStatus::EndOfTrack),
payload: vec![],
},
] {
let mut buf = Vec::new();
obj.encode(&mut buf);
let mut decode_cursor = &buf[..];
let mut meta_cursor = &buf[..];
let decoded = FetchObject::decode(&mut decode_cursor).unwrap();
let meta = FetchObject::decode_meta(&mut meta_cursor).unwrap();
assert_eq!(meta.group_id, decoded.group_id.into_inner());
assert_eq!(meta.subgroup_id, decoded.subgroup_id.into_inner());
assert_eq!(meta.object_id, decoded.object_id.into_inner());
assert_eq!(meta.publisher_priority, decoded.publisher_priority);
assert_eq!(meta.extension_headers_len, decoded.extension_headers.len() as u64);
assert_eq!(meta.payload_length, decoded.payload.len() as u64);
assert_eq!(meta.status, decoded.status.map(|s| s.as_u64()));
assert_eq!(meta.wire_len, buf.len() as u64);
assert!(meta_cursor.is_empty());
assert_eq!(decode_cursor.remaining(), meta_cursor.remaining());
}
}
#[test]
fn datagram_type_variants() {
let t0 = DatagramType::from_u8(0x00).unwrap();
assert!(t0.object_id_present());
assert!(!t0.extensions_present());
assert!(!t0.end_of_group());
assert!(!t0.is_status());
let t7 = DatagramType::from_u8(0x07).unwrap();
assert!(!t7.object_id_present()); assert!(t7.extensions_present());
assert!(t7.end_of_group());
assert!(!t7.is_status());
let t20 = DatagramType::from_u8(0x20).unwrap();
assert!(t20.is_status());
assert!(!t20.extensions_present());
assert!(t20.object_id_present());
let t21 = DatagramType::from_u8(0x21).unwrap();
assert!(t21.is_status());
assert!(t21.extensions_present());
}
#[test]
fn datagram_type_rejects_undefined() {
for bad in [0x08u8, 0x10, 0x1F, 0x22, 0x80] {
assert!(DatagramType::from_u8(bad).is_none(), "0x{bad:02x}");
}
}
#[test]
fn datagram_object_0x00_roundtrip() {
let d = DatagramObject {
datagram_type: DatagramType::from_u8(0x00).unwrap(),
track_alias: vi(1),
group_id: vi(2),
object_id: vi(3),
publisher_priority: 100,
extension_headers: vec![],
status: None,
payload: vec![0xDE, 0xAD, 0xBE, 0xEF],
};
let mut buf = Vec::new();
d.encode(&mut buf);
assert_eq!(DatagramObject::decode(&mut &buf[..]).unwrap(), d);
}
#[test]
fn datagram_object_0x04_no_object_id() {
let d = DatagramObject {
datagram_type: DatagramType::from_u8(0x04).unwrap(),
track_alias: vi(1),
group_id: vi(2),
object_id: vi(0),
publisher_priority: 100,
extension_headers: vec![],
status: None,
payload: vec![0xAA],
};
let mut buf = Vec::new();
d.encode(&mut buf);
let decoded = DatagramObject::decode(&mut &buf[..]).unwrap();
assert_eq!(decoded, d);
}
#[test]
fn datagram_object_0x21_status_with_extensions() {
let d = DatagramObject {
datagram_type: DatagramType::from_u8(0x21).unwrap(),
track_alias: vi(9),
group_id: vi(4),
object_id: vi(11),
publisher_priority: 50,
extension_headers: vec![0xCA, 0xFE],
status: Some(ObjectStatus::EndOfTrack),
payload: vec![],
};
let mut buf = Vec::new();
d.encode(&mut buf);
assert_eq!(DatagramObject::decode(&mut &buf[..]).unwrap(), d);
}
fn datagram(type_byte: u8, status: Option<ObjectStatus>, payload: Vec<u8>) -> DatagramObject {
DatagramObject {
datagram_type: DatagramType::from_u8(type_byte).unwrap(),
track_alias: vi(1),
group_id: vi(0),
object_id: vi(0),
publisher_priority: 128,
extension_headers: vec![],
status,
payload,
}
}
#[test]
fn encode_checked_refuses_the_field_the_type_byte_cannot_carry() {
for &status in ObjectStatus::ALL {
if status == ObjectStatus::Normal {
continue;
}
let object = datagram(0x00, Some(status), vec![0xDE, 0xAD]);
let mut refused = Vec::new();
let result = object.encode_checked(&mut refused);
assert!(
matches!(result, Err(CodecError::InvalidField)),
"encode_checked must refuse {status:?} under a payload type; got {result:?}"
);
assert!(refused.is_empty(), "a refused {status:?} datagram still wrote {refused:?}");
let mut dropped = Vec::new();
object.encode(&mut dropped);
let decoded = DatagramObject::decode(&mut &dropped[..])
.unwrap_or_else(|e| panic!("the lossy encoding of {status:?} must parse: {e:?}"));
assert_eq!(decoded.status, None, "{status:?} is exactly what `encode` loses here");
assert_eq!(decoded.payload, vec![0xDE, 0xAD]);
let mut carried = Vec::new();
datagram(0x20, Some(status), vec![]).encode_checked(&mut carried).unwrap_or_else(|e| {
panic!("encode_checked refused a status-type {status:?}: {e:?}")
});
let decoded = DatagramObject::decode(&mut &carried[..]).unwrap();
assert_eq!(decoded.status, Some(status), "{status:?} lost its status");
}
let object = datagram(0x20, Some(ObjectStatus::EndOfGroup), vec![0xDE, 0xAD]);
let mut refused = Vec::new();
let result = object.encode_checked(&mut refused);
assert!(
matches!(result, Err(CodecError::InvalidField)),
"encode_checked must refuse a payload under a status type; got {result:?}"
);
assert!(refused.is_empty(), "a refused datagram still wrote {refused:?}");
let mut dropped = Vec::new();
object.encode(&mut dropped);
let decoded = DatagramObject::decode(&mut &dropped[..]).unwrap();
assert!(decoded.payload.is_empty(), "the payload is exactly what `encode` loses here");
assert_eq!(decoded.status, Some(ObjectStatus::EndOfGroup));
let mut named = Vec::new();
datagram(0x00, Some(ObjectStatus::Normal), vec![0xDE, 0xAD])
.encode_checked(&mut named)
.unwrap_or_else(|e| {
panic!("encode_checked refused a Normal status under a payload type: {e:?}")
});
let mut unnamed = Vec::new();
datagram(0x00, None, vec![0xDE, 0xAD]).encode_checked(&mut unnamed).unwrap();
assert_eq!(named, unnamed, "naming Normal must ask for the bytes leaving it out asks for");
}
}