#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use crate::bytes::{ensure_len, read_length, read_optional_offset};
use crate::error::FormatError;
#[derive(Debug, Clone, PartialEq)]
pub enum DataLayout {
Compact {
data: Vec<u8>,
},
Contiguous {
address: Option<u64>,
size: u64,
},
Chunked {
chunk_dimensions: Vec<u32>,
index: ChunkIndexLayout,
},
Virtual,
}
impl DataLayout {
pub fn parse(data: &[u8], offset_size: u8, length_size: u8) -> Result<DataLayout, FormatError> {
ensure_len(data, 0, 2)?;
let version = data[0];
let layout_class = data[1];
match version {
LAYOUT_VERSION_3 => Self::parse_v3(data, layout_class, offset_size, length_size),
LAYOUT_VERSION_4 => Self::parse_v4(data, layout_class, offset_size, length_size),
_ => Err(FormatError::InvalidLayoutVersion(version)),
}
}
fn parse_v3(
data: &[u8],
layout_class: u8,
offset_size: u8,
length_size: u8,
) -> Result<DataLayout, FormatError> {
let pos = LAYOUT_PROPERTIES_OFFSET;
match layout_class {
LAYOUT_CLASS_COMPACT => Self::parse_compact(data),
LAYOUT_CLASS_CONTIGUOUS => Self::parse_contiguous(data, offset_size, length_size),
LAYOUT_CLASS_CHUNKED => {
ensure_len(data, pos, 1)?;
let dimensionality = data[pos] as usize;
let mut p = pos + 1;
let os = offset_size as usize;
ensure_len(data, p, os)?;
let address = read_optional_offset(data, p, offset_size)?;
p += os;
ensure_len(data, p, dimensionality * 4)?;
let mut chunk_dimensions = Vec::with_capacity(dimensionality);
for _ in 0..dimensionality {
let dim = u32::from_le_bytes([data[p], data[p + 1], data[p + 2], data[p + 3]]);
chunk_dimensions.push(dim);
p += 4;
}
Ok(DataLayout::Chunked {
chunk_dimensions,
index: ChunkIndexLayout::BTreeV1 { address },
})
}
_ => Err(FormatError::InvalidLayoutClass(layout_class)),
}
}
fn parse_v4(
data: &[u8],
layout_class: u8,
offset_size: u8,
length_size: u8,
) -> Result<DataLayout, FormatError> {
let pos = LAYOUT_PROPERTIES_OFFSET;
match layout_class {
LAYOUT_CLASS_COMPACT => Self::parse_compact(data),
LAYOUT_CLASS_CONTIGUOUS => Self::parse_contiguous(data, offset_size, length_size),
LAYOUT_CLASS_CHUNKED => {
ensure_len(data, pos, 3)?;
let flags = data[pos];
let dimensionality = data[pos + 1] as usize;
let dim_size_encoded_length = data[pos + 2] as usize;
let mut p = pos + 3;
ensure_len(data, p, dimensionality * dim_size_encoded_length)?;
let mut chunk_dimensions = Vec::with_capacity(dimensionality);
for _ in 0..dimensionality {
let val = match dim_size_encoded_length {
1 => data[p] as u32,
2 => u16::from_le_bytes([data[p], data[p + 1]]) as u32,
4 => u32::from_le_bytes([data[p], data[p + 1], data[p + 2], data[p + 3]]),
8 => {
u32::from_le_bytes([data[p], data[p + 1], data[p + 2], data[p + 3]])
}
_ => {
return Err(FormatError::UnexpectedEof {
expected: p + dim_size_encoded_length,
available: data.len(),
});
}
};
chunk_dimensions.push(val);
p += dim_size_encoded_length;
}
Ok(DataLayout::Chunked {
chunk_dimensions,
index: parse_chunk_index(data, p, flags, offset_size, length_size)?,
})
}
LAYOUT_CLASS_VIRTUAL => Ok(DataLayout::Virtual),
_ => Err(FormatError::InvalidLayoutClass(layout_class)),
}
}
fn parse_compact(data: &[u8]) -> Result<DataLayout, FormatError> {
let pos = LAYOUT_PROPERTIES_OFFSET;
ensure_len(data, pos, 2)?;
let data_size = u16::from_le_bytes([data[pos], data[pos + 1]]) as usize;
ensure_len(data, COMPACT_DATA_OFFSET, data_size)?;
Ok(DataLayout::Compact {
data: data[COMPACT_DATA_OFFSET..COMPACT_DATA_OFFSET + data_size].to_vec(),
})
}
fn parse_contiguous(
data: &[u8],
offset_size: u8,
length_size: u8,
) -> Result<DataLayout, FormatError> {
let pos = LAYOUT_PROPERTIES_OFFSET;
let os = offset_size as usize;
ensure_len(data, pos, os + length_size as usize)?;
Ok(DataLayout::Contiguous {
address: read_optional_offset(data, pos, offset_size)?,
size: read_length(data, pos + os, length_size)?,
})
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum ChunkIndexLayout {
BTreeV1 {
address: Option<u64>,
},
SingleChunk {
filtered: Option<FilteredSingleChunk>,
address: Option<u64>,
},
Implicit {
address: Option<u64>,
},
FixedArray {
address: Option<u64>,
},
ExtensibleArray {
address: Option<u64>,
},
BTreeV2 {
address: Option<u64>,
},
}
impl ChunkIndexLayout {
pub(crate) fn address(self) -> Option<u64> {
match self {
ChunkIndexLayout::BTreeV1 { address }
| ChunkIndexLayout::SingleChunk { address, .. }
| ChunkIndexLayout::Implicit { address }
| ChunkIndexLayout::FixedArray { address }
| ChunkIndexLayout::ExtensibleArray { address }
| ChunkIndexLayout::BTreeV2 { address } => address,
}
}
pub(crate) fn enumerable(self) -> bool {
!matches!(self, ChunkIndexLayout::BTreeV2 { .. })
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) struct FilteredSingleChunk {
pub(crate) filtered_size: u64,
pub(crate) filter_mask: u32,
}
fn parse_chunk_index(
data: &[u8],
pos: usize,
flags: u8,
offset_size: u8,
length_size: u8,
) -> Result<ChunkIndexLayout, FormatError> {
ensure_len(data, pos, 1)?;
let index_type = data[pos];
let info = pos + 1;
let address = |info_len: usize| read_optional_offset(data, info + info_len, offset_size);
Ok(match index_type {
CHUNK_INDEX_SINGLE_CHUNK => {
let filtered = read_filtered_single_chunk(data, info, flags, length_size)?;
let info_len = filtered.map_or(0, |_| length_size as usize + FILTER_MASK_LEN);
ChunkIndexLayout::SingleChunk {
filtered,
address: address(info_len)?,
}
}
CHUNK_INDEX_IMPLICIT => ChunkIndexLayout::Implicit {
address: address(0)?,
},
CHUNK_INDEX_FIXED_ARRAY => ChunkIndexLayout::FixedArray {
address: address(FIXED_ARRAY_INFO_LEN)?,
},
CHUNK_INDEX_EXTENSIBLE_ARRAY => ChunkIndexLayout::ExtensibleArray {
address: address(EXTENSIBLE_ARRAY_INFO_LEN)?,
},
CHUNK_INDEX_BTREE_V2 => ChunkIndexLayout::BTreeV2 {
address: address(BTREE_V2_INFO_LEN)?,
},
other => return Err(FormatError::InvalidChunkIndexType(other)),
})
}
fn read_filtered_single_chunk(
data: &[u8],
pos: usize,
flags: u8,
length_size: u8,
) -> Result<Option<FilteredSingleChunk>, FormatError> {
if flags & SINGLE_INDEX_WITH_FILTER == 0 {
return Ok(None);
}
let mask_at = pos + length_size as usize;
ensure_len(data, mask_at, FILTER_MASK_LEN)?;
Ok(Some(FilteredSingleChunk {
filtered_size: read_length(data, pos, length_size)?,
filter_mask: u32::from_le_bytes([
data[mask_at],
data[mask_at + 1],
data[mask_at + 2],
data[mask_at + 3],
]),
}))
}
const LAYOUT_PROPERTIES_OFFSET: usize = 2;
pub(crate) const COMPACT_DATA_OFFSET: usize = LAYOUT_PROPERTIES_OFFSET + 2;
const LAYOUT_VERSION_3: u8 = 3;
const LAYOUT_VERSION_4: u8 = 4;
const LAYOUT_CLASS_COMPACT: u8 = 0;
const LAYOUT_CLASS_CONTIGUOUS: u8 = 1;
const LAYOUT_CLASS_CHUNKED: u8 = 2;
const LAYOUT_CLASS_VIRTUAL: u8 = 3;
const CHUNK_INDEX_SINGLE_CHUNK: u8 = 1;
const CHUNK_INDEX_IMPLICIT: u8 = 2;
const CHUNK_INDEX_FIXED_ARRAY: u8 = 3;
const CHUNK_INDEX_EXTENSIBLE_ARRAY: u8 = 4;
const CHUNK_INDEX_BTREE_V2: u8 = 5;
const SINGLE_INDEX_WITH_FILTER: u8 = 0x02;
const FILTER_MASK_LEN: usize = 4;
const FIXED_ARRAY_INFO_LEN: usize = 1;
const EXTENSIBLE_ARRAY_INFO_LEN: usize = 5;
const BTREE_V2_INFO_LEN: usize = 6;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn v3_compact() {
let mut buf = vec![3u8, 0]; buf.extend_from_slice(&5u16.to_le_bytes()); buf.extend_from_slice(&[0xAA, 0xBB, 0xCC, 0xDD, 0xEE]); let layout = DataLayout::parse(&buf, 8, 8).unwrap();
assert_eq!(
layout,
DataLayout::Compact {
data: vec![0xAA, 0xBB, 0xCC, 0xDD, 0xEE]
}
);
}
#[test]
fn v3_contiguous() {
let mut buf = vec![3u8, 1]; buf.extend_from_slice(&0x1000u64.to_le_bytes()); buf.extend_from_slice(&256u64.to_le_bytes()); let layout = DataLayout::parse(&buf, 8, 8).unwrap();
assert_eq!(
layout,
DataLayout::Contiguous {
address: Some(0x1000),
size: 256,
}
);
}
#[test]
fn v3_contiguous_undefined_address() {
let mut buf = vec![3u8, 1];
buf.extend_from_slice(&[0xFF; 8]); buf.extend_from_slice(&0u64.to_le_bytes()); let layout = DataLayout::parse(&buf, 8, 8).unwrap();
assert_eq!(
layout,
DataLayout::Contiguous {
address: None,
size: 0,
}
);
}
#[test]
fn v3_chunked() {
let mut buf = vec![3u8, 2]; buf.push(3); buf.extend_from_slice(&0x2000u64.to_le_bytes()); buf.extend_from_slice(&100u32.to_le_bytes());
buf.extend_from_slice(&200u32.to_le_bytes());
buf.extend_from_slice(&8u32.to_le_bytes()); let layout = DataLayout::parse(&buf, 8, 8).unwrap();
assert_eq!(
layout,
DataLayout::Chunked {
chunk_dimensions: vec![100, 200, 8],
index: ChunkIndexLayout::BTreeV1 {
address: Some(0x2000)
},
}
);
}
#[test]
fn v4_compact() {
let mut buf = vec![4u8, 0]; buf.extend_from_slice(&3u16.to_le_bytes());
buf.extend_from_slice(&[1, 2, 3]);
let layout = DataLayout::parse(&buf, 8, 8).unwrap();
assert_eq!(
layout,
DataLayout::Compact {
data: vec![1, 2, 3]
}
);
}
#[test]
fn v4_contiguous() {
let mut buf = vec![4u8, 1];
buf.extend_from_slice(&0x5000u64.to_le_bytes());
buf.extend_from_slice(&512u64.to_le_bytes());
let layout = DataLayout::parse(&buf, 8, 8).unwrap();
assert_eq!(
layout,
DataLayout::Contiguous {
address: Some(0x5000),
size: 512,
}
);
}
fn v4_chunked(flags: u8, index_type: u8, info: &[u8]) -> Vec<u8> {
let mut buf = vec![4u8, 2]; buf.push(flags);
buf.push(1); buf.push(4); buf.extend_from_slice(&64u32.to_le_bytes()); buf.push(index_type);
buf.extend_from_slice(info);
buf.extend_from_slice(&0x3000u64.to_le_bytes()); buf
}
#[test]
fn v4_chunked_single_chunk_no_filters() {
let layout = DataLayout::parse(&v4_chunked(0, 1, &[]), 8, 8).unwrap();
assert_eq!(
layout,
DataLayout::Chunked {
chunk_dimensions: vec![64],
index: ChunkIndexLayout::SingleChunk {
filtered: None,
address: Some(0x3000),
},
}
);
}
#[test]
fn v4_chunked_single_chunk_with_filters() {
let mut info = 1024u64.to_le_bytes().to_vec();
info.extend_from_slice(&0x0000_0003u32.to_le_bytes());
let layout = DataLayout::parse(&v4_chunked(0x02, 1, &info), 8, 8).unwrap();
assert_eq!(
layout,
DataLayout::Chunked {
chunk_dimensions: vec![64],
index: ChunkIndexLayout::SingleChunk {
filtered: Some(FilteredSingleChunk {
filtered_size: 1024,
filter_mask: 3,
}),
address: Some(0x3000),
},
}
);
}
#[test]
fn v4_chunked_implicit_index_ignores_the_filter_flag() {
let layout = DataLayout::parse(&v4_chunked(0x02, 2, &[]), 8, 8).unwrap();
assert_eq!(
layout,
DataLayout::Chunked {
chunk_dimensions: vec![64],
index: ChunkIndexLayout::Implicit {
address: Some(0x3000)
},
}
);
}
#[test]
fn v4_chunked_fixed_array_skips_page_bits() {
let layout = DataLayout::parse(&v4_chunked(0, 3, &[10]), 8, 8).unwrap();
assert_eq!(
layout,
DataLayout::Chunked {
chunk_dimensions: vec![64],
index: ChunkIndexLayout::FixedArray {
address: Some(0x3000)
},
}
);
}
#[test]
fn v4_chunked_extensible_array_skips_creation_parameters() {
let layout = DataLayout::parse(&v4_chunked(0, 4, &[32, 4, 4, 16, 10]), 8, 8).unwrap();
assert_eq!(
layout,
DataLayout::Chunked {
chunk_dimensions: vec![64],
index: ChunkIndexLayout::ExtensibleArray {
address: Some(0x3000)
},
}
);
}
#[test]
fn v4_chunked_btree_v2_skips_node_parameters() {
let mut info = 2048u32.to_le_bytes().to_vec();
info.extend_from_slice(&[100, 40]);
let layout = DataLayout::parse(&v4_chunked(0, 5, &info), 8, 8).unwrap();
assert_eq!(
layout,
DataLayout::Chunked {
chunk_dimensions: vec![64],
index: ChunkIndexLayout::BTreeV2 {
address: Some(0x3000)
},
}
);
}
#[test]
fn v4_chunked_undefined_index_address_parses_as_none() {
let mut buf = v4_chunked(0, 4, &[32, 4, 4, 16, 10]);
let addr_at = buf.len() - 8;
buf[addr_at..].copy_from_slice(&[0xFF; 8]);
let layout = DataLayout::parse(&buf, 8, 8).unwrap();
assert_eq!(
layout,
DataLayout::Chunked {
chunk_dimensions: vec![64],
index: ChunkIndexLayout::ExtensibleArray { address: None },
}
);
}
#[test]
fn v4_chunked_unknown_index_type_is_rejected() {
let err = DataLayout::parse(&v4_chunked(0, 6, &[]), 8, 8).unwrap_err();
assert_eq!(err, FormatError::InvalidChunkIndexType(6));
}
#[test]
fn v4_chunked_truncated_index_type_reports_eof() {
let buf = vec![4u8, 2, 0, 1, 4, 64, 0, 0, 0];
let err = DataLayout::parse(&buf, 8, 8).unwrap_err();
assert_eq!(
err,
FormatError::UnexpectedEof {
expected: 10,
available: 9,
}
);
}
#[test]
fn invalid_version() {
let buf = vec![5u8, 0, 0, 0];
let err = DataLayout::parse(&buf, 8, 8).unwrap_err();
assert_eq!(err, FormatError::InvalidLayoutVersion(5));
}
#[test]
fn invalid_class_v3() {
let buf = vec![3u8, 5];
let err = DataLayout::parse(&buf, 8, 8).unwrap_err();
assert_eq!(err, FormatError::InvalidLayoutClass(5));
}
#[test]
fn invalid_class_v4() {
let buf = vec![4u8, 7];
let err = DataLayout::parse(&buf, 8, 8).unwrap_err();
assert_eq!(err, FormatError::InvalidLayoutClass(7));
}
#[test]
fn v3_contiguous_4byte_offsets() {
let mut buf = vec![3u8, 1];
buf.extend_from_slice(&0x800u32.to_le_bytes());
buf.extend_from_slice(&24u32.to_le_bytes());
let layout = DataLayout::parse(&buf, 4, 4).unwrap();
assert_eq!(
layout,
DataLayout::Contiguous {
address: Some(0x800),
size: 24,
}
);
}
#[test]
fn v4_virtual() {
let buf = vec![4u8, 3];
let layout = DataLayout::parse(&buf, 8, 8).unwrap();
assert_eq!(layout, DataLayout::Virtual);
}
}