use crate::error::{Error, Result};
pub const FILL_TIME_NEVER: u8 = 1;
#[derive(Debug, Clone)]
pub struct FillValueMessage {
pub version: u8,
pub alloc_time: u8,
pub fill_time: u8,
pub defined: bool,
pub value: Option<Vec<u8>>,
}
impl FillValueMessage {
pub fn decode(data: &[u8]) -> Result<Self> {
Self::decode_impl(data)
}
fn decode_impl(data: &[u8]) -> Result<Self> {
if data.is_empty() {
return Err(Error::InvalidFormat("empty fill value message".into()));
}
let version = data[0];
match version {
1 | 2 => Self::decode_v2(data),
3 => Self::decode_v3(data),
_ => Err(Error::InvalidFormat(format!(
"fill value message version {version}"
))),
}
}
fn decode_v2(data: &[u8]) -> Result<Self> {
if data.len() < 4 {
return Err(Error::InvalidFormat("fill value v2 too short".into()));
}
let alloc_time = data[1];
let fill_time = data[2];
let defined = data[3] != 0;
let value = if defined {
if data.len() < 8 {
return Err(Error::InvalidFormat(
"fill value v2 missing value size".into(),
));
}
let size = read_u32_le_at(data, 4, "fill value v2 value size")? as usize;
if size > 0 {
Some(checked_window(data, 8, size, "fill value v2 value")?.to_vec())
} else {
None
}
} else {
None
};
let message = Self {
version: data[0],
alloc_time,
fill_time,
defined,
value,
};
trace_fill_value(
data,
message.version,
alloc_time,
fill_time,
defined,
&message.value,
);
Ok(message)
}
fn decode_v3(data: &[u8]) -> Result<Self> {
if data.len() < 2 {
return Err(Error::InvalidFormat("fill value v3 too short".into()));
}
let flags = data[1];
let alloc_time = flags & 0x03;
let fill_time = (flags >> 2) & 0x03;
let undefined = flags & 0x10 != 0;
let have_value = flags & 0x20 != 0;
if undefined && have_value {
return Err(Error::InvalidFormat(
"fill value v3 has both undefined and value-present flags".into(),
));
}
let defined = !undefined;
let value = if have_value {
if data.len() < 6 {
return Err(Error::InvalidFormat(
"fill value v3 missing value size".into(),
));
}
let size = read_u32_le_at(data, 2, "fill value v3 value size")? as usize;
if size > 0 {
Some(checked_window(data, 6, size, "fill value v3 value")?.to_vec())
} else {
None
}
} else {
None
};
let message = Self {
version: 3,
alloc_time,
fill_time,
defined,
value,
};
trace_fill_value(
data,
message.version,
alloc_time,
fill_time,
defined,
&message.value,
);
Ok(message)
}
pub fn decode_old(data: &[u8]) -> Result<Self> {
Self::decode_old_with_datatype_size(data, None)
}
pub(crate) fn decode_old_with_datatype_size(
data: &[u8],
datatype_size: Option<usize>,
) -> Result<Self> {
if data.len() < 4 {
return Err(Error::InvalidFormat("old fill value too short".into()));
}
let size = read_u32_le_at(data, 0, "old fill value size")? as usize;
let value = if size > 0 {
Some(checked_window(data, 4, size, "old fill value")?.to_vec())
} else {
None
};
if let Some(datatype_size) = datatype_size {
if size > 0 && size != datatype_size {
return Err(Error::InvalidFormat(format!(
"old fill value size {size} does not match datatype size {datatype_size}"
)));
}
}
let message = Self {
version: 0,
alloc_time: 2,
fill_time: 2,
defined: size > 0,
value,
};
trace_fill_value(data, 0, 2, 2, message.defined, &message.value);
Ok(message)
}
}
fn checked_end(pos: usize, len: usize, context: &str) -> Result<usize> {
pos.checked_add(len)
.ok_or_else(|| Error::InvalidFormat(format!("{context} offset overflow")))
}
fn checked_window<'a>(data: &'a [u8], pos: usize, len: usize, context: &str) -> Result<&'a [u8]> {
let end = checked_end(pos, len, context)?;
data.get(pos..end)
.ok_or_else(|| Error::InvalidFormat(format!("{context} is truncated")))
}
fn read_u32_le_at(data: &[u8], pos: usize, context: &str) -> Result<u32> {
let bytes = checked_window(data, pos, 4, context)?;
let bytes: [u8; 4] = bytes
.try_into()
.map_err(|_| Error::InvalidFormat(format!("{context} is truncated")))?;
Ok(u32::from_le_bytes(bytes))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn checked_window_rejects_offset_overflow() {
let err = checked_window(&[], usize::MAX, 1, "fill value test window").unwrap_err();
assert!(
err.to_string()
.contains("fill value test window offset overflow"),
"unexpected error: {err}"
);
}
}
#[cfg(feature = "tracehash")]
fn trace_fill_value(
data: &[u8],
version: u8,
alloc_time: u8,
fill_time: u8,
defined: bool,
value: &Option<Vec<u8>>,
) {
let mut th = tracehash::th_call!("hdf5.fill_value.decode");
th.input_bytes(data);
th.output_value(&(true));
th.output_u64(version as u64);
th.output_u64(alloc_time as u64);
th.output_u64(fill_time as u64);
th.output_value(&(defined));
if let Some(value) = value {
th.output_value(&(true));
th.output_u64(value.len() as u64);
th.output_value(value);
} else {
th.output_value(&(false));
th.output_u64(0);
}
th.finish();
}
#[cfg(not(feature = "tracehash"))]
fn trace_fill_value(
_data: &[u8],
_version: u8,
_alloc_time: u8,
_fill_time: u8,
_defined: bool,
_value: &Option<Vec<u8>>,
) {
}