use crate::format::{FormatError, FormatResult};
const VERSION: u8 = 3;
const FLAG_MASK_ALLOC: u8 = 0x03;
const FLAG_MASK_FILL: u8 = 0x03;
const FLAG_SHIFT_FILL: u8 = 2;
const FLAG_UNDEFINED: u8 = 0x10;
const FLAG_HAVE_VALUE: u8 = 0x20;
const FLAGS_ALL: u8 =
FLAG_MASK_ALLOC | (FLAG_MASK_FILL << FLAG_SHIFT_FILL) | FLAG_UNDEFINED | FLAG_HAVE_VALUE;
#[derive(Debug, Clone, PartialEq)]
pub struct FillValueMessage {
pub alloc_time: u8,
pub fill_write_time: u8,
pub fill_defined: u8,
pub fill_value: Option<Vec<u8>>,
}
impl Default for FillValueMessage {
fn default() -> Self {
Self {
alloc_time: 2, fill_write_time: 0, fill_defined: 1, fill_value: None,
}
}
}
impl FillValueMessage {
pub fn with_value(data: Vec<u8>) -> Self {
Self {
alloc_time: 2,
fill_write_time: 0,
fill_defined: 2,
fill_value: Some(data),
}
}
pub fn undefined() -> Self {
Self {
alloc_time: 2,
fill_write_time: 1, fill_defined: 0,
fill_value: None,
}
}
pub fn encode(&self) -> Vec<u8> {
let mut buf = Vec::with_capacity(10);
buf.push(VERSION);
let flags = (self.alloc_time & FLAG_MASK_ALLOC)
| ((self.fill_write_time & FLAG_MASK_FILL) << FLAG_SHIFT_FILL);
if self.fill_defined == 0 {
buf.push(flags | FLAG_UNDEFINED);
} else if let Some(data) = self.fill_value.as_ref().filter(|d| !d.is_empty()) {
buf.push(flags | FLAG_HAVE_VALUE);
buf.extend_from_slice(&(data.len() as u32).to_le_bytes());
buf.extend_from_slice(data);
} else {
buf.push(flags);
}
buf
}
pub fn decode(buf: &[u8]) -> FormatResult<(Self, usize)> {
if buf.is_empty() {
return Err(FormatError::BufferTooShort {
needed: 1,
available: 0,
});
}
match buf[0] {
1 | 2 => Self::decode_v1v2(buf),
3 => Self::decode_v3(buf),
other => Err(FormatError::InvalidVersion(other)),
}
}
fn decode_v1v2(buf: &[u8]) -> FormatResult<(Self, usize)> {
if buf.len() < 4 {
return Err(FormatError::BufferTooShort {
needed: 4,
available: buf.len(),
});
}
let alloc_time = buf[1];
let fill_write_time = buf[2];
let defined_byte = buf[3];
let mut pos = 4;
let mut fill_value = None;
if defined_byte != 0 {
if buf.len() < pos + 4 {
return Err(FormatError::BufferTooShort {
needed: pos + 4,
available: buf.len(),
});
}
let size =
u32::from_le_bytes([buf[pos], buf[pos + 1], buf[pos + 2], buf[pos + 3]]) as usize;
pos += 4;
if size > 0 {
if buf.len() < pos + size {
return Err(FormatError::BufferTooShort {
needed: pos + size,
available: buf.len(),
});
}
fill_value = Some(buf[pos..pos + size].to_vec());
pos += size;
}
}
let fill_defined = if fill_value.is_some() {
2
} else if defined_byte != 0 {
1
} else {
0
};
Ok((
Self {
alloc_time,
fill_write_time,
fill_defined,
fill_value,
},
pos,
))
}
fn decode_v3(buf: &[u8]) -> FormatResult<(Self, usize)> {
if buf.len() < 2 {
return Err(FormatError::BufferTooShort {
needed: 2,
available: buf.len(),
});
}
let flags = buf[1];
if flags & !FLAGS_ALL != 0 {
return Err(FormatError::InvalidData(format!(
"unknown flags 0x{flags:02x} in version-3 fill-value message"
)));
}
let alloc_time = flags & FLAG_MASK_ALLOC;
let fill_write_time = (flags >> FLAG_SHIFT_FILL) & FLAG_MASK_FILL;
let mut pos = 2;
let (fill_defined, fill_value) = if flags & FLAG_UNDEFINED != 0 {
if flags & FLAG_HAVE_VALUE != 0 {
return Err(FormatError::InvalidData(
"fill-value message sets both the undefined and have-value flags".into(),
));
}
(0, None)
} else if flags & FLAG_HAVE_VALUE != 0 {
if buf.len() < pos + 4 {
return Err(FormatError::BufferTooShort {
needed: pos + 4,
available: buf.len(),
});
}
let size =
u32::from_le_bytes([buf[pos], buf[pos + 1], buf[pos + 2], buf[pos + 3]]) as usize;
pos += 4;
if buf.len() < pos + size {
return Err(FormatError::BufferTooShort {
needed: pos + size,
available: buf.len(),
});
}
let data = buf[pos..pos + size].to_vec();
pos += size;
(2, Some(data))
} else {
(1, None)
};
Ok((
Self {
alloc_time,
fill_write_time,
fill_defined,
fill_value,
},
pos,
))
}
}
pub(crate) fn tiled_fill(total: usize, fill_value: Option<&[u8]>) -> Vec<u8> {
match fill_value {
Some(fv) if !fv.is_empty() && total > 0 => {
let mut buf = vec![0u8; total];
for slot in buf.chunks_mut(fv.len()) {
let n = slot.len().min(fv.len());
slot[..n].copy_from_slice(&fv[..n]);
}
buf
}
_ => vec![0u8; total],
}
}
pub(crate) fn try_tiled_fill(
total: usize,
fill_value: Option<&[u8]>,
) -> Result<Vec<u8>, std::collections::TryReserveError> {
let mut buf: Vec<u8> = Vec::new();
buf.try_reserve_exact(total)?;
buf.resize(total, 0);
if let Some(fv) = fill_value {
if !fv.is_empty() && total > 0 {
for slot in buf.chunks_mut(fv.len()) {
let n = slot.len().min(fv.len());
slot[..n].copy_from_slice(&fv[..n]);
}
}
}
Ok(buf)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn roundtrip_default() {
let msg = FillValueMessage::default();
let encoded = msg.encode();
assert_eq!(encoded.len(), 2);
let (decoded, consumed) = FillValueMessage::decode(&encoded).unwrap();
assert_eq!(consumed, 2);
assert_eq!(decoded, msg);
}
#[test]
fn roundtrip_user_defined() {
let msg = FillValueMessage::with_value(vec![0xDE, 0xAD, 0xBE, 0xEF]);
let encoded = msg.encode();
assert_eq!(encoded.len(), 10);
let (decoded, consumed) = FillValueMessage::decode(&encoded).unwrap();
assert_eq!(consumed, 10);
assert_eq!(decoded, msg);
assert_eq!(
decoded.fill_value.as_ref().unwrap(),
&vec![0xDE, 0xAD, 0xBE, 0xEF]
);
}
#[test]
fn roundtrip_undefined() {
let msg = FillValueMessage::undefined();
let encoded = msg.encode();
assert_eq!(encoded.len(), 2);
let (decoded, consumed) = FillValueMessage::decode(&encoded).unwrap();
assert_eq!(consumed, 2);
assert_eq!(decoded, msg);
}
#[test]
fn version_3_flags_byte_layout() {
let msg = FillValueMessage {
alloc_time: 3,
fill_write_time: 2,
fill_defined: 2,
fill_value: Some(vec![0x01, 0x02]),
};
let encoded = msg.encode();
assert_eq!(encoded[0], 3);
assert_eq!(encoded[1], 0x03 | (0x02 << 2) | FLAG_HAVE_VALUE);
assert_eq!(&encoded[2..6], &2u32.to_le_bytes());
assert_eq!(&encoded[6..8], &[0x01, 0x02]);
assert_eq!(encoded.len(), 8);
}
#[test]
fn version_3_undefined_flag() {
let encoded = FillValueMessage::undefined().encode();
assert_eq!(encoded[1] & FLAG_UNDEFINED, FLAG_UNDEFINED);
assert_eq!(encoded[1] & FLAG_HAVE_VALUE, 0);
}
#[test]
fn empty_user_data_normalizes_to_default() {
let msg = FillValueMessage {
alloc_time: 1,
fill_write_time: 2,
fill_defined: 2,
fill_value: Some(vec![]),
};
let encoded = msg.encode();
assert_eq!(encoded.len(), 2);
let (decoded, consumed) = FillValueMessage::decode(&encoded).unwrap();
assert_eq!(consumed, 2);
assert_eq!(decoded.alloc_time, 1);
assert_eq!(decoded.fill_write_time, 2);
assert_eq!(decoded.fill_defined, 1);
assert_eq!(decoded.fill_value, None);
}
#[test]
fn decode_version_1_message() {
let buf = [1u8, 2, 0, 1, 4, 0, 0, 0, 0xAA, 0xBB, 0xCC, 0xDD];
let (decoded, consumed) = FillValueMessage::decode(&buf).unwrap();
assert_eq!(consumed, 12);
assert_eq!(decoded.alloc_time, 2);
assert_eq!(decoded.fill_defined, 2);
assert_eq!(
decoded.fill_value.as_ref().unwrap(),
&vec![0xAA, 0xBB, 0xCC, 0xDD]
);
}
#[test]
fn decode_version_2_message_libhdf5_default() {
let buf = [2u8, 2, 2, 1, 4, 0, 0, 0, 0x00, 0x00, 0x80, 0xBF];
let (decoded, consumed) = FillValueMessage::decode(&buf).unwrap();
assert_eq!(consumed, 12);
assert_eq!(decoded.alloc_time, 2);
assert_eq!(decoded.fill_write_time, 2);
assert_eq!(decoded.fill_defined, 2);
assert_eq!(
decoded.fill_value.as_ref().unwrap(),
&vec![0x00, 0x00, 0x80, 0xBF]
);
}
#[test]
fn decode_version_2_defined_without_value() {
let buf = [2u8, 2, 0, 1, 0, 0, 0, 0];
let (decoded, consumed) = FillValueMessage::decode(&buf).unwrap();
assert_eq!(consumed, 8);
assert_eq!(decoded.fill_defined, 1);
assert_eq!(decoded.fill_value, None);
}
#[test]
fn decode_bad_version() {
for bad in [0u8, 4, 9] {
let buf = [bad, 0, 0, 0];
match FillValueMessage::decode(&buf).unwrap_err() {
FormatError::InvalidVersion(v) if v == bad => {}
other => panic!("unexpected error for version {bad}: {other:?}"),
}
}
}
#[test]
fn decode_buffer_too_short() {
let buf = [3u8];
match FillValueMessage::decode(&buf).unwrap_err() {
FormatError::BufferTooShort { .. } => {}
other => panic!("unexpected error: {other:?}"),
}
}
#[test]
fn decode_v3_unknown_flag_rejected() {
let buf = [3u8, 0x40];
match FillValueMessage::decode(&buf).unwrap_err() {
FormatError::InvalidData(_) => {}
other => panic!("unexpected error: {other:?}"),
}
}
#[test]
fn decode_v3_truncated_size() {
let buf = [3u8, FLAG_HAVE_VALUE, 0xFF];
match FillValueMessage::decode(&buf).unwrap_err() {
FormatError::BufferTooShort { .. } => {}
other => panic!("unexpected error: {other:?}"),
}
}
#[test]
fn decode_v3_truncated_data() {
let buf = [3u8, FLAG_HAVE_VALUE, 4, 0, 0, 0, 0xAA, 0xBB];
match FillValueMessage::decode(&buf).unwrap_err() {
FormatError::BufferTooShort {
needed: 10,
available: 8,
} => {}
other => panic!("unexpected error: {other:?}"),
}
}
#[test]
fn version_byte() {
let encoded = FillValueMessage::default().encode();
assert_eq!(encoded[0], 3);
}
#[test]
fn tiled_fill_repeats_pattern() {
assert_eq!(tiled_fill(0, Some(&[1, 2])), Vec::<u8>::new());
assert_eq!(tiled_fill(6, None), vec![0u8; 6]);
assert_eq!(tiled_fill(6, Some(&[])), vec![0u8; 6]);
assert_eq!(
tiled_fill(6, Some(&[0xAB, 0xCD])),
vec![0xAB, 0xCD, 0xAB, 0xCD, 0xAB, 0xCD]
);
assert_eq!(tiled_fill(5, Some(&[1, 2])), vec![1, 2, 1, 2, 1]);
}
}