use crate::check::Check;
use crate::error::{Error, Result};
use crate::io::{Reader, Writer};
pub const HEAD_FIXED_LEN: usize = 44;
pub const ENCODER_NAME_MAX: usize = 256;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Head {
pub head_version: u32,
pub encoder_name: String,
pub created_unix_sec: u64,
pub display_width_px: u32,
pub display_height_px: u32,
pub build_width_um: u32,
pub build_depth_um: u32,
pub build_height_um: u32,
pub layer_height_um: u32,
pub total_layers: u32,
}
impl Head {
pub fn parse(payload: &[u8]) -> Result<Head> {
let mut r = Reader::checked(payload, Check::HeadFrame);
let head_version = r.u32()?;
if head_version != 1 {
return Err(Error::new(
Check::HeadVersion,
format!("unsupported head_version {head_version}"),
));
}
let encoder_name_len = r.u32()?;
if encoder_name_len as usize > ENCODER_NAME_MAX {
return Err(Error::new(
Check::HeadFrame,
format!(
"encoder_name_len {encoder_name_len} exceeds the {ENCODER_NAME_MAX} byte limit"
),
));
}
let encoder_name = r.utf8(encoder_name_len as usize)?.to_owned();
let created_unix_sec = r.u64()?;
let display_width_px = r.u32()?;
let display_height_px = r.u32()?;
let build_width_um = r.u32()?;
let build_depth_um = r.u32()?;
let build_height_um = r.u32()?;
let layer_height_um = r.u32()?;
let total_layers = r.u32()?;
let expected = HEAD_FIXED_LEN + encoder_name_len as usize;
if payload.len() != expected {
return Err(Error::new(
Check::HeadFrame,
format!(
"HEAD is {} bytes, not the {expected} its fields and name total",
payload.len()
),
));
}
Ok(Head {
head_version,
encoder_name,
created_unix_sec,
display_width_px,
display_height_px,
build_width_um,
build_depth_um,
build_height_um,
layer_height_um,
total_layers,
})
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut w = Writer::with_capacity(HEAD_FIXED_LEN + self.encoder_name.len());
w.u32(self.head_version);
w.u32(self.encoder_name.len() as u32);
w.bytes(self.encoder_name.as_bytes());
w.u64(self.created_unix_sec);
w.u32(self.display_width_px);
w.u32(self.display_height_px);
w.u32(self.build_width_um);
w.u32(self.build_depth_um);
w.u32(self.build_height_um);
w.u32(self.layer_height_um);
w.u32(self.total_layers);
w.into_vec()
}
pub fn total_pixels(&self) -> u32 {
self.display_width_px.saturating_mul(self.display_height_px)
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample(name: &str) -> Head {
Head {
head_version: 1,
encoder_name: name.to_owned(),
created_unix_sec: 1_710_000_000,
display_width_px: 64,
display_height_px: 48,
build_width_um: 218_000,
build_depth_um: 123_000,
build_height_um: 250_000,
layer_height_um: 50,
total_layers: 4,
}
}
#[test]
fn hdr_round_trips_with_a_multibyte_name() {
let head = sample("DragonFruit 1.0 \u{2713}");
let bytes = head.to_bytes();
assert_eq!(bytes.len(), HEAD_FIXED_LEN + head.encoder_name.len());
assert_eq!(
u32::from_le_bytes(bytes[4..8].try_into().unwrap()) as usize,
head.encoder_name.len()
);
let parsed = Head::parse(&bytes).unwrap();
assert_eq!(parsed, head);
}
#[test]
fn hdr_rejects_a_length_measured_in_characters() {
let head = sample("DragonFruit \u{2713}");
let mut bytes = head.to_bytes();
let chars = head.encoder_name.chars().count() as u32;
bytes[4..8].copy_from_slice(&chars.to_le_bytes());
let err = Head::parse(&bytes).unwrap_err();
assert_eq!(err.check(), Check::HeadFrame);
}
#[test]
fn hdr_rejects_an_oversized_or_truncated_frame() {
let mut bytes = sample("DragonFruit 1.0").to_bytes();
bytes[4..8].copy_from_slice(&(ENCODER_NAME_MAX as u32 + 1).to_le_bytes());
assert_eq!(Head::parse(&bytes).unwrap_err().check(), Check::HeadFrame);
let bytes = sample("DragonFruit 1.0").to_bytes();
assert_eq!(
Head::parse(&bytes[..bytes.len() - 1]).unwrap_err().check(),
Check::HeadFrame
);
assert_eq!(
Head::parse(&bytes[..4]).unwrap_err().check(),
Check::HeadFrame
);
let mut longer = bytes.clone();
longer.extend_from_slice(&[0u8; 8]);
assert_eq!(Head::parse(&longer).unwrap_err().check(), Check::HeadFrame);
}
#[test]
fn hdr_rejects_unknown_versions_and_any_extra_bytes() {
let mut bytes = sample("X").to_bytes();
bytes[0..4].copy_from_slice(&2u32.to_le_bytes());
assert_eq!(Head::parse(&bytes).unwrap_err().check(), Check::HeadVersion);
let mut bytes = sample("X").to_bytes();
bytes.push(0);
assert_eq!(Head::parse(&bytes).unwrap_err().check(), Check::HeadFrame);
}
}