use crate::model::common::varint::{BufMutVarIntExt, BufVarIntExt};
use crate::model::error::ParseError;
use bytes::{Buf, BufMut, Bytes, BytesMut};
use super::constant::SubgroupHeaderType;
#[derive(Debug, PartialEq, Clone, Copy)]
pub struct SubgroupHeader {
pub header_type: SubgroupHeaderType,
pub track_alias: u64,
pub group_id: u64,
pub subgroup_id: Option<u64>,
pub publisher_priority: Option<u8>,
}
impl SubgroupHeader {
pub fn new_fixed_zero_id(
track_alias: u64,
group_id: u64,
publisher_priority: Option<u8>,
has_extensions: bool,
contains_end_of_group: bool,
) -> Self {
let has_default_priority = publisher_priority.is_none();
let header_type = SubgroupHeaderType::from_properties(
has_extensions,
0,
contains_end_of_group,
has_default_priority,
);
Self {
header_type,
track_alias,
group_id,
subgroup_id: Some(0),
publisher_priority,
}
}
pub fn new_first_object_id(
track_alias: u64,
group_id: u64,
publisher_priority: Option<u8>,
has_extensions: bool,
contains_end_of_group: bool,
) -> Self {
let has_default_priority = publisher_priority.is_none();
let header_type = SubgroupHeaderType::from_properties(
has_extensions,
1,
contains_end_of_group,
has_default_priority,
);
Self {
header_type,
track_alias,
group_id,
subgroup_id: None, publisher_priority,
}
}
pub fn new_with_explicit_id(
track_alias: u64,
group_id: u64,
subgroup_id: u64,
publisher_priority: Option<u8>,
has_extensions: bool,
contains_end_of_group: bool,
) -> Self {
let has_default_priority = publisher_priority.is_none();
let header_type = SubgroupHeaderType::from_properties(
has_extensions,
2,
contains_end_of_group,
has_default_priority,
);
Self {
header_type,
track_alias,
group_id,
subgroup_id: Some(subgroup_id),
publisher_priority,
}
}
pub fn serialize(&self, track_alias: Option<u64>) -> Result<Bytes, ParseError> {
let mut buf = BytesMut::new();
buf.put_vi(u64::from(self.header_type))?;
let alias = track_alias.unwrap_or(self.track_alias);
buf.put_vi(alias)?;
buf.put_vi(self.group_id)?;
if self.header_type.has_explicit_subgroup_id() {
if let Some(id) = self.subgroup_id {
buf.put_vi(id)?;
} else {
return Err(ParseError::ProtocolViolation {
context: "SubgroupHeader::serialize(header_type)",
details: "Subgroup_id field is required for this header type".to_string(),
});
}
}
if !self.header_type.has_default_priority() {
if let Some(priority) = self.publisher_priority {
buf.put_u8(priority);
} else {
return Err(ParseError::ProtocolViolation {
context: "SubgroupHeader::serialize(publisher_priority)",
details: "Publisher_priority field is required when DEFAULT_PRIORITY bit is not set"
.to_string(),
});
}
}
Ok(buf.freeze())
}
pub fn deserialize(bytes: &mut Bytes) -> Result<Self, ParseError> {
let type_value = bytes.get_vi()?;
let header_type = SubgroupHeaderType::try_from(type_value)?;
let track_alias = bytes.get_vi()?;
let group_id = bytes.get_vi()?;
let subgroup_id = if header_type.has_explicit_subgroup_id() {
Some(bytes.get_vi()?)
} else if header_type.subgroup_id_is_zero() {
Some(0) } else {
None };
let publisher_priority = if !header_type.has_default_priority() {
if bytes.remaining() < 1 {
return Err(ParseError::NotEnoughBytes {
context: "SubgroupHeader::deserialize(publisher_priority)",
needed: 1,
available: bytes.remaining(),
});
}
Some(bytes.get_u8())
} else {
None
};
Ok(Self {
header_type,
track_alias,
group_id,
subgroup_id,
publisher_priority,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use bytes::Buf;
#[test]
fn test_roundtrip() {
let header_type = SubgroupHeaderType::try_new(0x14).unwrap();
let track_alias = 87;
let group_id = 9;
let subgroup_id = Some(11);
let publisher_priority = Some(255);
let subgroup_header = SubgroupHeader {
header_type,
track_alias,
group_id,
subgroup_id,
publisher_priority,
};
let mut buf = subgroup_header.serialize(None).unwrap();
let deserialized = SubgroupHeader::deserialize(&mut buf).unwrap();
assert_eq!(deserialized, subgroup_header);
assert!(!buf.has_remaining());
}
#[test]
fn test_roundtrip_default_priority() {
let header_type = SubgroupHeaderType::try_new(0x30).unwrap(); let track_alias = 87;
let group_id = 9;
let subgroup_id = Some(0);
let publisher_priority = None; let subgroup_header = SubgroupHeader {
header_type,
track_alias,
group_id,
subgroup_id,
publisher_priority,
};
let mut buf = subgroup_header.serialize(None).unwrap();
let deserialized = SubgroupHeader::deserialize(&mut buf).unwrap();
assert_eq!(deserialized, subgroup_header);
assert!(!buf.has_remaining());
}
#[test]
fn test_excess_roundtrip() {
let header_type = SubgroupHeaderType::try_new(0x14).unwrap();
let track_alias = 87;
let group_id = 9;
let subgroup_id = Some(11);
let publisher_priority = Some(255);
let subgroup_header = SubgroupHeader {
header_type,
track_alias,
group_id,
subgroup_id,
publisher_priority,
};
let serialized = subgroup_header.serialize(None).unwrap();
let mut excess = BytesMut::new();
excess.extend_from_slice(&serialized);
excess.extend_from_slice(&[9u8, 1u8, 1u8]);
let mut buf = excess.freeze();
let deserialized = SubgroupHeader::deserialize(&mut buf).unwrap();
assert_eq!(deserialized, subgroup_header);
assert_eq!(buf.chunk(), &[9u8, 1u8, 1u8]);
}
#[test]
fn test_partial_message() {
let header_type = SubgroupHeaderType::try_new(0x14).unwrap();
let track_alias = 87;
let group_id = 9;
let subgroup_id = Some(11);
let publisher_priority = Some(255);
let subgroup_header = SubgroupHeader {
header_type,
track_alias,
group_id,
subgroup_id,
publisher_priority,
};
let buf = subgroup_header.serialize(None).unwrap();
let upper = buf.remaining() / 2;
let mut partial = buf.slice(..upper);
let deserialized = SubgroupHeader::deserialize(&mut partial);
assert!(deserialized.is_err());
}
#[test]
fn test_new_header_types_roundtrip() {
let test_cases = vec![
(SubgroupHeaderType::try_new(0x10).unwrap(), false, Some(0)),
(SubgroupHeaderType::try_new(0x11).unwrap(), false, Some(0)),
(SubgroupHeaderType::try_new(0x12).unwrap(), false, None),
(SubgroupHeaderType::try_new(0x13).unwrap(), false, None),
(SubgroupHeaderType::try_new(0x14).unwrap(), true, Some(42)),
(SubgroupHeaderType::try_new(0x15).unwrap(), true, Some(42)),
(SubgroupHeaderType::try_new(0x18).unwrap(), false, Some(0)),
(SubgroupHeaderType::try_new(0x19).unwrap(), false, Some(0)),
(SubgroupHeaderType::try_new(0x1A).unwrap(), false, None),
(SubgroupHeaderType::try_new(0x1B).unwrap(), false, None),
(SubgroupHeaderType::try_new(0x1C).unwrap(), true, Some(42)),
(SubgroupHeaderType::try_new(0x1D).unwrap(), true, Some(42)),
];
for (header_type, has_explicit_id, expected_subgroup_id) in test_cases {
let track_alias = 87;
let group_id = 9;
let publisher_priority = Some(255);
let subgroup_header = SubgroupHeader {
header_type,
track_alias,
group_id,
subgroup_id: if has_explicit_id {
Some(42)
} else {
expected_subgroup_id
},
publisher_priority,
};
let mut buf = subgroup_header.serialize(Some(track_alias)).unwrap();
let deserialized = SubgroupHeader::deserialize(&mut buf).unwrap();
assert_eq!(deserialized.header_type, header_type);
assert_eq!(deserialized.track_alias, track_alias);
assert_eq!(deserialized.group_id, group_id);
assert_eq!(deserialized.subgroup_id, expected_subgroup_id);
assert_eq!(deserialized.publisher_priority, publisher_priority);
assert!(!buf.has_remaining());
}
}
#[test]
fn test_header_type_classification() {
assert!(
SubgroupHeaderType::try_new(0x10)
.unwrap()
.subgroup_id_is_zero()
);
assert!(
SubgroupHeaderType::try_new(0x11)
.unwrap()
.subgroup_id_is_zero()
);
assert!(
SubgroupHeaderType::try_new(0x18)
.unwrap()
.subgroup_id_is_zero()
);
assert!(
SubgroupHeaderType::try_new(0x19)
.unwrap()
.subgroup_id_is_zero()
);
assert!(
SubgroupHeaderType::try_new(0x12)
.unwrap()
.subgroup_id_is_first_object_id()
);
assert!(
SubgroupHeaderType::try_new(0x13)
.unwrap()
.subgroup_id_is_first_object_id()
);
assert!(
SubgroupHeaderType::try_new(0x1A)
.unwrap()
.subgroup_id_is_first_object_id()
);
assert!(
SubgroupHeaderType::try_new(0x1B)
.unwrap()
.subgroup_id_is_first_object_id()
);
assert!(
SubgroupHeaderType::try_new(0x14)
.unwrap()
.has_explicit_subgroup_id()
);
assert!(
SubgroupHeaderType::try_new(0x15)
.unwrap()
.has_explicit_subgroup_id()
);
assert!(
SubgroupHeaderType::try_new(0x1C)
.unwrap()
.has_explicit_subgroup_id()
);
assert!(
SubgroupHeaderType::try_new(0x1D)
.unwrap()
.has_explicit_subgroup_id()
);
assert!(
SubgroupHeaderType::try_new(0x18)
.unwrap()
.contains_end_of_group()
);
assert!(
SubgroupHeaderType::try_new(0x19)
.unwrap()
.contains_end_of_group()
);
assert!(
SubgroupHeaderType::try_new(0x1A)
.unwrap()
.contains_end_of_group()
);
assert!(
SubgroupHeaderType::try_new(0x1B)
.unwrap()
.contains_end_of_group()
);
assert!(
SubgroupHeaderType::try_new(0x1C)
.unwrap()
.contains_end_of_group()
);
assert!(
SubgroupHeaderType::try_new(0x1D)
.unwrap()
.contains_end_of_group()
);
assert!(SubgroupHeaderType::try_new(0x11).unwrap().has_extensions());
assert!(SubgroupHeaderType::try_new(0x13).unwrap().has_extensions());
assert!(SubgroupHeaderType::try_new(0x15).unwrap().has_extensions());
assert!(SubgroupHeaderType::try_new(0x19).unwrap().has_extensions());
assert!(SubgroupHeaderType::try_new(0x1B).unwrap().has_extensions());
assert!(SubgroupHeaderType::try_new(0x1D).unwrap().has_extensions());
}
#[test]
fn test_constructor_methods() {
let track_alias = 87;
let group_id = 9;
let publisher_priority = Some(255);
let header =
SubgroupHeader::new_fixed_zero_id(track_alias, group_id, publisher_priority, false, false);
assert_eq!(
header.header_type,
SubgroupHeaderType::try_new(0x10).unwrap()
);
assert_eq!(header.subgroup_id, Some(0));
let header =
SubgroupHeader::new_fixed_zero_id(track_alias, group_id, publisher_priority, true, false);
assert_eq!(
header.header_type,
SubgroupHeaderType::try_new(0x11).unwrap()
);
assert_eq!(header.subgroup_id, Some(0));
let header =
SubgroupHeader::new_fixed_zero_id(track_alias, group_id, publisher_priority, false, true);
assert_eq!(
header.header_type,
SubgroupHeaderType::try_new(0x18).unwrap()
);
assert_eq!(header.subgroup_id, Some(0));
let header =
SubgroupHeader::new_fixed_zero_id(track_alias, group_id, publisher_priority, true, true);
assert_eq!(
header.header_type,
SubgroupHeaderType::try_new(0x19).unwrap()
);
assert_eq!(header.subgroup_id, Some(0));
let header =
SubgroupHeader::new_first_object_id(track_alias, group_id, publisher_priority, false, false);
assert_eq!(
header.header_type,
SubgroupHeaderType::try_new(0x12).unwrap()
);
assert_eq!(header.subgroup_id, None);
let header =
SubgroupHeader::new_first_object_id(track_alias, group_id, publisher_priority, true, false);
assert_eq!(
header.header_type,
SubgroupHeaderType::try_new(0x13).unwrap()
);
assert_eq!(header.subgroup_id, None);
let header =
SubgroupHeader::new_first_object_id(track_alias, group_id, publisher_priority, false, true);
assert_eq!(
header.header_type,
SubgroupHeaderType::try_new(0x1A).unwrap()
);
assert_eq!(header.subgroup_id, None);
let header =
SubgroupHeader::new_first_object_id(track_alias, group_id, publisher_priority, true, true);
assert_eq!(
header.header_type,
SubgroupHeaderType::try_new(0x1B).unwrap()
);
assert_eq!(header.subgroup_id, None);
let subgroup_id = 42;
let header = SubgroupHeader::new_with_explicit_id(
track_alias,
group_id,
subgroup_id,
publisher_priority,
false,
false,
);
assert_eq!(
header.header_type,
SubgroupHeaderType::try_new(0x14).unwrap()
);
assert_eq!(header.subgroup_id, Some(subgroup_id));
let header = SubgroupHeader::new_with_explicit_id(
track_alias,
group_id,
subgroup_id,
publisher_priority,
true,
false,
);
assert_eq!(
header.header_type,
SubgroupHeaderType::try_new(0x15).unwrap()
);
assert_eq!(header.subgroup_id, Some(subgroup_id));
let header = SubgroupHeader::new_with_explicit_id(
track_alias,
group_id,
subgroup_id,
publisher_priority,
false,
true,
);
assert_eq!(
header.header_type,
SubgroupHeaderType::try_new(0x1C).unwrap()
);
assert_eq!(header.subgroup_id, Some(subgroup_id));
let header = SubgroupHeader::new_with_explicit_id(
track_alias,
group_id,
subgroup_id,
publisher_priority,
true,
true,
);
assert_eq!(
header.header_type,
SubgroupHeaderType::try_new(0x1D).unwrap()
);
assert_eq!(header.subgroup_id, Some(subgroup_id));
}
#[test]
fn test_object_parsing_integration() {
use crate::model::data::object::Object;
use crate::model::data::subgroup_object::SubgroupObject;
use bytes::Bytes;
let track_alias = 87;
let group_id = 9;
let publisher_priority = Some(255);
let header =
SubgroupHeader::new_fixed_zero_id(track_alias, group_id, publisher_priority, false, false);
assert_eq!(
header.header_type,
SubgroupHeaderType::try_new(0x10).unwrap()
);
assert_eq!(header.subgroup_id, Some(0));
let subgroup_obj = SubgroupObject {
object_id: 42,
extension_headers: None,
payload: Some(Bytes::from_static(b"test payload")),
object_status: None,
};
let object = Object::try_from_subgroup(
subgroup_obj,
header.track_alias,
header.group_id,
header.subgroup_id,
header.publisher_priority,
)
.unwrap();
assert_eq!(object.subgroup_id, Some(0));
assert_eq!(object.track_alias, track_alias);
assert_eq!(object.location.group, group_id);
assert_eq!(object.location.object, 42);
let header = SubgroupHeader::new_with_explicit_id(
track_alias,
group_id,
123,
publisher_priority,
true,
false,
);
assert_eq!(
header.header_type,
SubgroupHeaderType::try_new(0x15).unwrap()
);
assert_eq!(header.subgroup_id, Some(123));
let object = Object::try_from_subgroup(
SubgroupObject {
object_id: 43,
extension_headers: None,
payload: Some(Bytes::from_static(b"test payload 2")),
object_status: None,
},
header.track_alias,
header.group_id,
header.subgroup_id,
header.publisher_priority,
)
.unwrap();
assert_eq!(object.subgroup_id, Some(123));
let header =
SubgroupHeader::new_first_object_id(track_alias, group_id, publisher_priority, false, true);
assert_eq!(
header.header_type,
SubgroupHeaderType::try_new(0x1A).unwrap()
);
assert_eq!(header.subgroup_id, None);
let object = Object::try_from_subgroup(
SubgroupObject {
object_id: 55,
extension_headers: None,
payload: Some(Bytes::from_static(b"test payload 3")),
object_status: None,
},
header.track_alias,
header.group_id,
header.subgroup_id, header.publisher_priority,
)
.unwrap();
assert_eq!(object.subgroup_id, None);
}
}