use crate::pgs::segment::CompositionState;
fn u16_be(data: &[u8], offset: usize) -> u16 {
u16::from_be_bytes([data[offset], data[offset + 1]])
}
fn u24_be(data: &[u8], offset: usize) -> u32 {
((data[offset] as u32) << 16) | ((data[offset + 1] as u32) << 8) | (data[offset + 2] as u32)
}
fn push_u16_be(buf: &mut Vec<u8>, val: u16) {
buf.extend_from_slice(&val.to_be_bytes());
}
fn push_u24_be(buf: &mut Vec<u8>, val: u32) {
buf.push((val >> 16) as u8);
buf.push((val >> 8) as u8);
buf.push(val as u8);
}
#[derive(Debug, Clone)]
pub struct PcsData {
pub video_width: u16,
pub video_height: u16,
pub composition_number: u16,
pub composition_state: CompositionState,
pub palette_only: bool,
pub palette_id: u8,
pub objects: Vec<CompositionObject>,
}
#[derive(Debug, Clone)]
pub struct CompositionObject {
pub object_id: u16,
pub window_id: u8,
pub x: u16,
pub y: u16,
pub crop: Option<CropInfo>,
}
#[derive(Debug, Clone)]
pub struct CropInfo {
pub x: u16,
pub y: u16,
pub width: u16,
pub height: u16,
}
impl PcsData {
pub fn parse(payload: &[u8]) -> Option<Self> {
if payload.len() < 11 {
return None;
}
let video_width = u16_be(payload, 0);
let video_height = u16_be(payload, 2);
let composition_number = u16_be(payload, 5);
let composition_state = CompositionState::from_byte(payload[7])?;
let palette_only = payload[8] == 0x80;
let palette_id = payload[9];
let num_objects = payload[10] as usize;
let mut objects = Vec::with_capacity(num_objects);
let mut offset = 11;
for _ in 0..num_objects {
if offset + 8 > payload.len() {
return None;
}
let object_id = u16_be(payload, offset);
let window_id = payload[offset + 2];
let cropped = payload[offset + 3] & 0x80 != 0;
let x = u16_be(payload, offset + 4);
let y = u16_be(payload, offset + 6);
offset += 8;
let crop = if cropped {
if offset + 8 > payload.len() {
return None;
}
let crop = CropInfo {
x: u16_be(payload, offset),
y: u16_be(payload, offset + 2),
width: u16_be(payload, offset + 4),
height: u16_be(payload, offset + 6),
};
offset += 8;
Some(crop)
} else {
None
};
objects.push(CompositionObject {
object_id,
window_id,
x,
y,
crop,
});
}
Some(PcsData {
video_width,
video_height,
composition_number,
composition_state,
palette_only,
palette_id,
objects,
})
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut buf = Vec::new();
push_u16_be(&mut buf, self.video_width);
push_u16_be(&mut buf, self.video_height);
buf.push(0x10); push_u16_be(&mut buf, self.composition_number);
buf.push(self.composition_state.to_byte());
buf.push(if self.palette_only { 0x80 } else { 0x00 });
buf.push(self.palette_id);
buf.push(self.objects.len() as u8);
for obj in &self.objects {
push_u16_be(&mut buf, obj.object_id);
buf.push(obj.window_id);
buf.push(if obj.crop.is_some() { 0x80 } else { 0x00 });
push_u16_be(&mut buf, obj.x);
push_u16_be(&mut buf, obj.y);
if let Some(crop) = &obj.crop {
push_u16_be(&mut buf, crop.x);
push_u16_be(&mut buf, crop.y);
push_u16_be(&mut buf, crop.width);
push_u16_be(&mut buf, crop.height);
}
}
buf
}
}
#[derive(Debug, Clone)]
pub struct WdsData {
pub windows: Vec<WindowDefinition>,
}
#[derive(Debug, Clone)]
pub struct WindowDefinition {
pub id: u8,
pub x: u16,
pub y: u16,
pub width: u16,
pub height: u16,
}
impl WdsData {
pub fn parse(payload: &[u8]) -> Option<Self> {
if payload.is_empty() {
return None;
}
let num_windows = payload[0] as usize;
let mut windows = Vec::with_capacity(num_windows);
let mut offset = 1;
for _ in 0..num_windows {
if offset + 9 > payload.len() {
return None;
}
windows.push(WindowDefinition {
id: payload[offset],
x: u16_be(payload, offset + 1),
y: u16_be(payload, offset + 3),
width: u16_be(payload, offset + 5),
height: u16_be(payload, offset + 7),
});
offset += 9;
}
Some(WdsData { windows })
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut buf = Vec::new();
buf.push(self.windows.len() as u8);
for w in &self.windows {
buf.push(w.id);
push_u16_be(&mut buf, w.x);
push_u16_be(&mut buf, w.y);
push_u16_be(&mut buf, w.width);
push_u16_be(&mut buf, w.height);
}
buf
}
}
#[derive(Debug, Clone)]
pub struct PdsData {
pub id: u8,
pub version: u8,
pub entries: Vec<PaletteEntry>,
}
#[derive(Debug, Clone)]
pub struct PaletteEntry {
pub id: u8,
pub luminance: u8,
pub cr: u8,
pub cb: u8,
pub alpha: u8,
}
impl PdsData {
pub fn parse(payload: &[u8]) -> Option<Self> {
if payload.len() < 2 {
return None;
}
let id = payload[0];
let version = payload[1];
let remaining = &payload[2..];
if !remaining.len().is_multiple_of(5) {
return None;
}
let num_entries = remaining.len() / 5;
let mut entries = Vec::with_capacity(num_entries);
for i in 0..num_entries {
let base = i * 5;
entries.push(PaletteEntry {
id: remaining[base],
luminance: remaining[base + 1],
cr: remaining[base + 2],
cb: remaining[base + 3],
alpha: remaining[base + 4],
});
}
Some(PdsData {
id,
version,
entries,
})
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut buf = Vec::with_capacity(2 + self.entries.len() * 5);
buf.push(self.id);
buf.push(self.version);
for e in &self.entries {
buf.push(e.id);
buf.push(e.luminance);
buf.push(e.cr);
buf.push(e.cb);
buf.push(e.alpha);
}
buf
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SequenceFlag {
Complete,
First,
Last,
Continuation,
}
impl SequenceFlag {
pub fn from_byte(b: u8) -> Option<Self> {
match b {
0xC0 => Some(SequenceFlag::Complete),
0x80 => Some(SequenceFlag::First),
0x40 => Some(SequenceFlag::Last),
0x00 => Some(SequenceFlag::Continuation),
_ => None,
}
}
pub fn to_byte(self) -> u8 {
match self {
SequenceFlag::Complete => 0xC0,
SequenceFlag::First => 0x80,
SequenceFlag::Last => 0x40,
SequenceFlag::Continuation => 0x00,
}
}
pub fn as_str(&self) -> &'static str {
match self {
SequenceFlag::Complete => "complete",
SequenceFlag::First => "first",
SequenceFlag::Last => "last",
SequenceFlag::Continuation => "continuation",
}
}
}
#[derive(Debug, Clone)]
pub struct OdsData {
pub id: u16,
pub version: u8,
pub sequence: SequenceFlag,
pub data_length: u32,
pub width: Option<u16>,
pub height: Option<u16>,
pub rle_data: Vec<u8>,
}
impl OdsData {
pub fn parse(payload: &[u8]) -> Option<Self> {
if payload.len() < 4 {
return None;
}
let id = u16_be(payload, 0);
let version = payload[2];
let sequence = SequenceFlag::from_byte(payload[3])?;
let is_first = matches!(sequence, SequenceFlag::Complete | SequenceFlag::First);
let (data_length, width, height, rle_offset) = if is_first {
if payload.len() < 11 {
return None;
}
(
u24_be(payload, 4),
Some(u16_be(payload, 7)),
Some(u16_be(payload, 9)),
11,
)
} else {
(0, None, None, 4)
};
let rle_data = payload[rle_offset..].to_vec();
Some(OdsData {
id,
version,
sequence,
data_length,
width,
height,
rle_data,
})
}
pub fn to_bytes(&self) -> Vec<u8> {
let is_first = matches!(self.sequence, SequenceFlag::Complete | SequenceFlag::First);
let mut buf = Vec::new();
push_u16_be(&mut buf, self.id);
buf.push(self.version);
buf.push(self.sequence.to_byte());
if is_first {
let data_length = self.rle_data.len() as u32 + 4; push_u24_be(&mut buf, data_length);
push_u16_be(&mut buf, self.width.unwrap_or(0));
push_u16_be(&mut buf, self.height.unwrap_or(0));
}
buf.extend_from_slice(&self.rle_data);
buf
}
}
pub fn ods_rle_data(payload: &[u8], sequence: SequenceFlag) -> Option<&[u8]> {
let offset = match sequence {
SequenceFlag::Complete | SequenceFlag::First => 11,
SequenceFlag::Continuation | SequenceFlag::Last => 4,
};
if payload.len() < offset {
return None;
}
Some(&payload[offset..])
}
#[derive(Debug, Clone)]
pub enum ParsedPayload {
Pcs(PcsData),
Wds(WdsData),
Pds(PdsData),
Ods(OdsData),
End,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pcs_no_objects() {
let payload = vec![
0x07, 0x80, 0x04, 0x38, 0x10, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, ];
let pcs = PcsData::parse(&payload).unwrap();
assert_eq!(pcs.video_width, 1920);
assert_eq!(pcs.video_height, 1080);
assert_eq!(pcs.composition_number, 1);
assert_eq!(pcs.composition_state, CompositionState::EpochStart);
assert!(!pcs.palette_only);
assert_eq!(pcs.palette_id, 0);
assert!(pcs.objects.is_empty());
}
#[test]
fn test_pcs_one_uncropped_object() {
let payload = vec![
0x07, 0x80, 0x04, 0x38, 0x10, 0x01, 0xAE, 0x80, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x03, 0x05, 0x00, 0x6C, ];
let pcs = PcsData::parse(&payload).unwrap();
assert_eq!(pcs.objects.len(), 1);
let obj = &pcs.objects[0];
assert_eq!(obj.object_id, 0);
assert_eq!(obj.window_id, 0);
assert_eq!(obj.x, 773);
assert_eq!(obj.y, 108);
assert!(obj.crop.is_none());
}
#[test]
fn test_pcs_cropped_object() {
let payload = vec![
0x07, 0x80, 0x04, 0x38, 0x10, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x80, 0x00, 0x64, 0x00, 0xC8, 0x00, 0x0A, 0x00, 0x14, 0x00, 0x50, 0x00, 0x28, ];
let pcs = PcsData::parse(&payload).unwrap();
let obj = &pcs.objects[0];
assert!(obj.crop.is_some());
let crop = obj.crop.as_ref().unwrap();
assert_eq!(crop.x, 10);
assert_eq!(crop.y, 20);
assert_eq!(crop.width, 80);
assert_eq!(crop.height, 40);
}
#[test]
fn test_pcs_palette_only_update() {
let payload = vec![
0x07, 0x80, 0x04, 0x38, 0x10, 0x00, 0x02, 0x00, 0x80, 0x03, 0x00, ];
let pcs = PcsData::parse(&payload).unwrap();
assert!(pcs.palette_only);
assert_eq!(pcs.palette_id, 3);
}
#[test]
fn test_pcs_truncated() {
assert!(PcsData::parse(&[0x07, 0x80]).is_none());
let payload = vec![
0x07, 0x80, 0x04, 0x38, 0x10, 0x00, 0x01, 0x80, 0x00, 0x00, 0x01,
];
assert!(PcsData::parse(&payload).is_none());
}
#[test]
fn test_wds_two_windows() {
let payload = vec![
0x02, 0x00, 0x03, 0x05, 0x00, 0x6C, 0x01, 0x79, 0x00, 0x2B, 0x01, 0x02, 0xE3, 0x03, 0xA0, 0x01, 0xD8, 0x00, 0x2B, ];
let wds = WdsData::parse(&payload).unwrap();
assert_eq!(wds.windows.len(), 2);
assert_eq!(wds.windows[0].id, 0);
assert_eq!(wds.windows[0].x, 773);
assert_eq!(wds.windows[0].width, 377);
assert_eq!(wds.windows[1].id, 1);
assert_eq!(wds.windows[1].x, 739);
assert_eq!(wds.windows[1].width, 472);
}
#[test]
fn test_wds_truncated() {
assert!(WdsData::parse(&[]).is_none());
assert!(WdsData::parse(&[0x01, 0x00, 0x00, 0x00, 0x00, 0x00]).is_none());
}
#[test]
fn test_pds_multiple_entries() {
let payload = vec![
0x00, 0x00, 0x00, 0x10, 0x80, 0x80, 0x00, 0x01, 0x10, 0x80, 0x80, 0xFF, 0xFF, 0xEB, 0x80, 0x80, 0xFF, ];
let pds = PdsData::parse(&payload).unwrap();
assert_eq!(pds.id, 0);
assert_eq!(pds.version, 0);
assert_eq!(pds.entries.len(), 3);
assert_eq!(pds.entries[0].id, 0);
assert_eq!(pds.entries[0].luminance, 16);
assert_eq!(pds.entries[0].alpha, 0);
assert_eq!(pds.entries[2].id, 255);
assert_eq!(pds.entries[2].luminance, 235);
}
#[test]
fn test_pds_empty_palette() {
let payload = vec![0x00, 0x00]; let pds = PdsData::parse(&payload).unwrap();
assert!(pds.entries.is_empty());
}
#[test]
fn test_pds_truncated() {
assert!(PdsData::parse(&[0x00]).is_none());
assert!(PdsData::parse(&[0x00, 0x00, 0x01, 0x10, 0x80]).is_none());
}
#[test]
fn test_ods_complete() {
let payload = vec![
0x00, 0x00, 0x00, 0xC0, 0x00, 0x21, 0xBB, 0x01, 0x79, 0x00, 0x2B, 0x00, 0x01, 0x02,
];
let ods = OdsData::parse(&payload).unwrap();
assert_eq!(ods.id, 0);
assert_eq!(ods.version, 0);
assert_eq!(ods.sequence, SequenceFlag::Complete);
assert_eq!(ods.data_length, 8635);
assert_eq!(ods.width, Some(377));
assert_eq!(ods.height, Some(43));
}
#[test]
fn test_ods_continuation_fragment() {
let payload = vec![
0x00, 0x00, 0x01, 0x00, 0xAA, 0xBB,
];
let ods = OdsData::parse(&payload).unwrap();
assert_eq!(ods.sequence, SequenceFlag::Continuation);
assert_eq!(ods.data_length, 0);
assert!(ods.width.is_none());
assert!(ods.height.is_none());
}
#[test]
fn test_ods_last_fragment() {
let payload = vec![
0x00, 0x01, 0x00, 0x40, 0xCC, 0xDD,
];
let ods = OdsData::parse(&payload).unwrap();
assert_eq!(ods.sequence, SequenceFlag::Last);
assert_eq!(ods.data_length, 0);
assert!(ods.width.is_none());
}
#[test]
fn test_ods_first_fragment() {
let payload = vec![
0x00, 0x00, 0x00, 0x80, 0x00, 0x40, 0x00, 0x01, 0x79, 0x00, 0x2B, ];
let ods = OdsData::parse(&payload).unwrap();
assert_eq!(ods.sequence, SequenceFlag::First);
assert_eq!(ods.width, Some(377));
assert_eq!(ods.height, Some(43));
}
#[test]
fn test_ods_truncated() {
assert!(OdsData::parse(&[0x00, 0x00, 0x00]).is_none());
let payload = vec![0x00, 0x00, 0x00, 0xC0, 0x00, 0x10, 0x00];
assert!(OdsData::parse(&payload).is_none());
}
#[test]
fn test_ods_invalid_sequence_flag() {
let payload = vec![
0x00, 0x00, 0x00, 0x20, 0x00, 0x10, 0x00,
];
assert!(OdsData::parse(&payload).is_none());
}
#[test]
fn test_sequence_flag_as_str() {
assert_eq!(SequenceFlag::Complete.as_str(), "complete");
assert_eq!(SequenceFlag::First.as_str(), "first");
assert_eq!(SequenceFlag::Last.as_str(), "last");
assert_eq!(SequenceFlag::Continuation.as_str(), "continuation");
}
#[test]
fn test_pcs_roundtrip_no_objects() {
let payload = vec![
0x07, 0x80, 0x04, 0x38, 0x10,
0x00, 0x01, 0x80, 0x00, 0x00, 0x00,
];
let pcs = PcsData::parse(&payload).unwrap();
assert_eq!(pcs.to_bytes(), payload);
}
#[test]
fn test_pcs_roundtrip_with_object() {
let payload = vec![
0x07, 0x80, 0x04, 0x38, 0x10,
0x01, 0xAE, 0x80, 0x00, 0x00, 0x01,
0x00, 0x00, 0x00, 0x00, 0x03, 0x05, 0x00, 0x6C,
];
let pcs = PcsData::parse(&payload).unwrap();
assert_eq!(pcs.to_bytes(), payload);
}
#[test]
fn test_pcs_roundtrip_cropped() {
let payload = vec![
0x07, 0x80, 0x04, 0x38, 0x10,
0x00, 0x01, 0x00, 0x00, 0x00, 0x01,
0x00, 0x01, 0x00, 0x80, 0x00, 0x64, 0x00, 0xC8,
0x00, 0x0A, 0x00, 0x14, 0x00, 0x50, 0x00, 0x28,
];
let pcs = PcsData::parse(&payload).unwrap();
assert_eq!(pcs.to_bytes(), payload);
}
#[test]
fn test_wds_roundtrip() {
let payload = vec![
0x02,
0x00, 0x03, 0x05, 0x00, 0x6C, 0x01, 0x79, 0x00, 0x2B,
0x01, 0x02, 0xE3, 0x03, 0xA0, 0x01, 0xD8, 0x00, 0x2B,
];
let wds = WdsData::parse(&payload).unwrap();
assert_eq!(wds.to_bytes(), payload);
}
#[test]
fn test_pds_roundtrip() {
let payload = vec![
0x00, 0x00,
0x00, 0x10, 0x80, 0x80, 0x00,
0x01, 0x10, 0x80, 0x80, 0xFF,
0xFF, 0xEB, 0x80, 0x80, 0xFF,
];
let pds = PdsData::parse(&payload).unwrap();
assert_eq!(pds.to_bytes(), payload);
}
#[test]
fn test_ods_roundtrip_complete() {
let payload = vec![
0x00, 0x00, 0x00, 0xC0,
0x00, 0x00, 0x07, 0x01, 0x79, 0x00, 0x2B,
0x00, 0x01, 0x02,
];
let ods = OdsData::parse(&payload).unwrap();
assert_eq!(ods.rle_data, vec![0x00, 0x01, 0x02]);
assert_eq!(ods.to_bytes(), payload);
}
#[test]
fn test_ods_roundtrip_continuation() {
let payload = vec![
0x00, 0x00, 0x01, 0x00,
0xAA, 0xBB,
];
let ods = OdsData::parse(&payload).unwrap();
assert_eq!(ods.rle_data, vec![0xAA, 0xBB]);
assert_eq!(ods.to_bytes(), payload);
}
#[test]
fn test_ods_roundtrip_last() {
let payload = vec![
0x00, 0x01, 0x00, 0x40,
0xCC, 0xDD,
];
let ods = OdsData::parse(&payload).unwrap();
assert_eq!(ods.to_bytes(), payload);
}
#[test]
fn test_sequence_flag_roundtrip() {
for flag in [SequenceFlag::Complete, SequenceFlag::First, SequenceFlag::Last, SequenceFlag::Continuation] {
assert_eq!(SequenceFlag::from_byte(flag.to_byte()), Some(flag));
}
}
#[test]
fn test_composition_state_roundtrip() {
for state in [CompositionState::Normal, CompositionState::AcquisitionPoint, CompositionState::EpochStart] {
assert_eq!(CompositionState::from_byte(state.to_byte()), Some(state));
}
}
}