use crate::blocks::common::{read_link, BlockHeader, ParseBlock, BLOCK_HEADER_SIZE};
use crate::error::{Mf4Error, Result};
const SR_DATA_SIZE: usize = 8 + 8 + 1 + 1 + 6;
use byteorder::{LittleEndian, ReadBytesExt};
use std::io::Cursor;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SrSyncType {
Time,
Angle,
Distance,
Index,
Unknown(u8),
}
impl SrSyncType {
fn from_u8(value: u8) -> Self {
match value {
1 => SrSyncType::Time,
2 => SrSyncType::Angle,
3 => SrSyncType::Distance,
4 => SrSyncType::Index,
v => SrSyncType::Unknown(v),
}
}
}
#[derive(Debug, Clone)]
pub struct SrBlock {
pub header: BlockHeader,
pub sr_next: u64,
pub sr_data: u64,
pub sr_cycle_count: u64,
pub sr_interval: f64,
pub sr_sync_type: SrSyncType,
pub sr_flags: u8,
}
impl SrBlock {
pub const MIN_SIZE: u64 = BLOCK_HEADER_SIZE as u64 + 2 * 8 + SR_DATA_SIZE as u64;
}
impl ParseBlock for SrBlock {
fn parse(data: &[u8], offset: u64) -> Result<Self> {
let header = BlockHeader::parse(data, offset)?;
header.validate_type(b"##SR", offset)?;
if header.length < Self::MIN_SIZE {
return Err(Mf4Error::invalid_block_size(
"SR",
header.length,
Self::MIN_SIZE,
));
}
let links_start = BLOCK_HEADER_SIZE;
let sr_next = read_link(data, links_start)?;
let sr_data = read_link(data, links_start + 8)?;
let data_start = header.data_offset();
let data_section = data
.get(data_start..)
.ok_or_else(|| Mf4Error::truncated(offset, data_start, data.len()))?;
if data_section.len() < SR_DATA_SIZE {
return Err(Mf4Error::truncated(
offset,
SR_DATA_SIZE,
data_section.len(),
));
}
let mut cursor = Cursor::new(data_section);
let sr_cycle_count = cursor.read_u64::<LittleEndian>()?;
let sr_interval = cursor.read_f64::<LittleEndian>()?;
let sr_sync_type = SrSyncType::from_u8(cursor.read_u8()?);
let sr_flags = cursor.read_u8()?;
Ok(SrBlock {
header,
sr_next,
sr_data,
sr_cycle_count,
sr_interval,
sr_sync_type,
sr_flags,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
fn create_test_sr_block() -> Vec<u8> {
let links = 2usize;
let total_len = BLOCK_HEADER_SIZE + links * 8 + SR_DATA_SIZE;
let mut data = vec![0u8; total_len];
data[0..4].copy_from_slice(b"##SR");
data[8..16].copy_from_slice(&(total_len as u64).to_le_bytes());
data[16..24].copy_from_slice(&(links as u64).to_le_bytes());
data[24..32].copy_from_slice(&500u64.to_le_bytes()); data[32..40].copy_from_slice(&600u64.to_le_bytes());
let d = BLOCK_HEADER_SIZE + links * 8;
data[d..d + 8].copy_from_slice(&1234u64.to_le_bytes()); data[d + 8..d + 16].copy_from_slice(&0.25f64.to_le_bytes()); data[d + 16] = 1; data[d + 17] = 3;
data
}
#[test]
fn parses_every_field_at_its_specified_offset() {
let sr = SrBlock::parse(&create_test_sr_block(), 0).unwrap();
assert_eq!(sr.sr_next, 500);
assert_eq!(sr.sr_data, 600);
assert_eq!(sr.sr_cycle_count, 1234);
assert_eq!(sr.sr_interval, 0.25);
assert_eq!(sr.sr_sync_type, SrSyncType::Time);
assert_eq!(sr.sr_flags, 3);
}
#[test]
fn sync_type_is_numbered_from_one() {
let spec = [
(1u8, SrSyncType::Time),
(2, SrSyncType::Angle),
(3, SrSyncType::Distance),
(4, SrSyncType::Index),
];
for (raw, expected) in spec {
let mut data = create_test_sr_block();
data[BLOCK_HEADER_SIZE + 2 * 8 + 16] = raw;
let sr = SrBlock::parse(&data, 0).unwrap();
assert_eq!(
sr.sr_sync_type, expected,
"sr_sync_type {raw} decoded wrongly"
);
}
let mut data = create_test_sr_block();
data[BLOCK_HEADER_SIZE + 2 * 8 + 16] = 0;
assert_eq!(
SrBlock::parse(&data, 0).unwrap().sr_sync_type,
SrSyncType::Unknown(0),
"0 is undefined for sr_sync_type; reading it as seconds shifted \
every domain by one"
);
}
#[test]
fn a_third_link_would_consume_the_cycle_count() {
let sr = SrBlock::parse(&create_test_sr_block(), 0).unwrap();
assert_eq!(
sr.sr_cycle_count, 1234,
"cycle count is wrong, so the link section is mis-sized"
);
assert_eq!(sr.sr_interval, 0.25, "the fields after it are shifted too");
}
#[test]
fn rejects_a_block_of_the_wrong_type() {
let mut data = create_test_sr_block();
data[0..4].copy_from_slice(b"##DG");
assert!(SrBlock::parse(&data, 0).is_err());
}
#[test]
fn rejects_a_block_too_short_for_its_data_section() {
let mut data = create_test_sr_block();
let short = (data.len() - 1) as u64;
data[8..16].copy_from_slice(&short.to_le_bytes());
data.truncate(data.len() - 1);
assert!(SrBlock::parse(&data, 0).is_err());
}
}