use std::convert::TryInto;
pub const MAGIC: [u8; 4] = *b"RETE";
pub const CURRENT_FORMAT_VERSION: u8 = 0x05;
pub const MIN_STABLE_READ_VERSION: u8 = 0x05;
pub const HEADER_LEN: usize = 1024;
const SECTION_DIR_OFFSET: usize = 64;
const SECTION_ENTRY_LEN: usize = 24;
pub const MAX_SECTIONS: usize = (HEADER_LEN - SECTION_DIR_OFFSET) / SECTION_ENTRY_LEN;
pub const FLAG_HAS_QUADS: u8 = 0b0000_0001;
pub const FLAG_TILE_SYNOPSIS: u8 = 0b0000_0010;
pub const FLAG_HAS_QUOTED_TRIPLES: u8 = 0b0000_0100;
pub const FLAG_QUAD_INDEX: u8 = 0b0000_1000;
pub const FLAG_TEXT_INDEX_NAMED_GRAPHS: u8 = 0b0001_0000;
pub const TEXT_INDEX_PARTIAL_DIGEST_LEN: u64 = 16;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SectionKind {
Metadata,
Dictionary,
Index,
PyramidMeta,
NamedGraphs,
TextIndex,
TextIndexPartial,
BuildInfo,
LabelIndex,
Schema,
QuadIndex,
Unknown(u16),
}
impl SectionKind {
fn to_u16(self) -> u16 {
match self {
SectionKind::Metadata => 1,
SectionKind::Dictionary => 2,
SectionKind::Index => 3,
SectionKind::PyramidMeta => 4,
SectionKind::NamedGraphs => 5,
SectionKind::TextIndex => 6,
SectionKind::BuildInfo => 7,
SectionKind::LabelIndex => 8,
SectionKind::Schema => 9,
SectionKind::QuadIndex => 10,
SectionKind::TextIndexPartial => 11,
SectionKind::Unknown(k) => k,
}
}
fn from_u16(k: u16) -> Self {
match k {
1 => SectionKind::Metadata,
2 => SectionKind::Dictionary,
3 => SectionKind::Index,
4 => SectionKind::PyramidMeta,
5 => SectionKind::NamedGraphs,
6 => SectionKind::TextIndex,
7 => SectionKind::BuildInfo,
8 => SectionKind::LabelIndex,
9 => SectionKind::Schema,
10 => SectionKind::QuadIndex,
11 => SectionKind::TextIndexPartial,
other => SectionKind::Unknown(other),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Section {
pub kind: SectionKind,
pub flags: u16,
pub offset: u64,
pub length: u64,
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum HeaderError {
#[error("buffer too small: need {HEADER_LEN} bytes, got {0}")]
TooSmall(usize),
#[error("bad magic: expected RETE")]
BadMagic,
#[error(
"unsupported .rete format {found:#04x}; this Rete build reads {min:#04x}..={max:#04x}. Pre-1.0 files must be rebuilt from RDF source with `rete build`"
)]
UnsupportedVersion { found: u8, min: u8, max: u8 },
#[error("section count {0} overruns the header frame")]
BadSectionCount(usize),
#[error("unusable index permutation mask {mask:#04x}: {why}")]
BadPermMask { mask: u8, why: &'static str },
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Header {
pub version: u8,
pub flags: u8,
pub metadata_offset: u64,
pub metadata_len: u64,
pub dictionary_offset: u64,
pub dictionary_len: u64,
pub root_dir_offset: u64,
pub root_dir_len: u64,
pub pyramid_meta_offset: u64,
pub pyramid_meta_len: u64,
pub dict_codec: u8,
pub block_codec: u8,
pub pyramid_levels: u16,
pub quad_count: u64,
pub term_count: u64,
pub content_hash: [u8; 16],
pub named_graphs_offset: u64,
pub named_graphs_len: u64,
pub schema_meta_len: u32,
pub text_index_offset: u64,
pub text_index_len: u64,
pub text_index_partial: bool,
pub build_info_offset: u64,
pub build_info_len: u64,
pub perms: crate::index::PermSet,
pub extra_sections: Vec<Section>,
}
impl Header {
pub fn to_bytes(&self) -> [u8; HEADER_LEN] {
let mut b = [0u8; HEADER_LEN];
b[0..4].copy_from_slice(&MAGIC);
b[4] = self.version;
b[5] = self.flags;
b[6..8].copy_from_slice(&(HEADER_LEN as u16).to_le_bytes());
b[8..24].copy_from_slice(&self.content_hash);
b[24..32].copy_from_slice(&self.quad_count.to_le_bytes());
b[32..40].copy_from_slice(&self.term_count.to_le_bytes());
b[40..42].copy_from_slice(&self.pyramid_levels.to_le_bytes());
b[42] = self.dict_codec;
b[43] = self.block_codec;
b[46..50].copy_from_slice(&self.schema_meta_len.to_le_bytes());
b[50] = if self.perms == crate::index::PermSet::ALL {
0
} else {
self.perms.bits()
};
let entry = |kind, offset, length| Section {
kind,
flags: 0,
offset,
length,
};
let mut entries: Vec<Section> = vec![
entry(
SectionKind::Metadata,
self.metadata_offset,
self.metadata_len,
),
entry(
SectionKind::Dictionary,
self.dictionary_offset,
self.dictionary_len,
),
entry(SectionKind::Index, self.root_dir_offset, self.root_dir_len),
entry(
SectionKind::PyramidMeta,
self.pyramid_meta_offset,
self.pyramid_meta_len,
),
entry(
SectionKind::NamedGraphs,
self.named_graphs_offset,
self.named_graphs_len,
),
];
if self.text_index_len > 0 {
entries.push(entry(
if self.text_index_partial {
SectionKind::TextIndexPartial
} else {
SectionKind::TextIndex
},
self.text_index_offset,
self.text_index_len,
));
}
if self.build_info_len > 0 {
entries.push(entry(
SectionKind::BuildInfo,
self.build_info_offset,
self.build_info_len,
));
}
entries.extend(self.extra_sections.iter().copied());
debug_assert!(
entries.len() <= MAX_SECTIONS,
"too many sections for a 1 KB header"
);
let n = entries.len().min(MAX_SECTIONS);
b[44..46].copy_from_slice(&(n as u16).to_le_bytes());
for (i, s) in entries.iter().take(n).enumerate() {
let p = SECTION_DIR_OFFSET + i * SECTION_ENTRY_LEN;
b[p..p + 2].copy_from_slice(&s.kind.to_u16().to_le_bytes());
b[p + 2..p + 4].copy_from_slice(&s.flags.to_le_bytes());
b[p + 8..p + 16].copy_from_slice(&s.offset.to_le_bytes());
b[p + 16..p + 24].copy_from_slice(&s.length.to_le_bytes());
}
b
}
pub fn from_bytes(b: &[u8]) -> Result<Self, HeaderError> {
if b.len() < HEADER_LEN {
return Err(HeaderError::TooSmall(b.len()));
}
if b[0..4] != MAGIC {
return Err(HeaderError::BadMagic);
}
if !(MIN_STABLE_READ_VERSION..=CURRENT_FORMAT_VERSION).contains(&b[4]) {
return Err(HeaderError::UnsupportedVersion {
found: b[4],
min: MIN_STABLE_READ_VERSION,
max: CURRENT_FORMAT_VERSION,
});
}
let u16_at = |o: usize| u16::from_le_bytes(b[o..o + 2].try_into().unwrap());
let u32_at = |o: usize| u32::from_le_bytes(b[o..o + 4].try_into().unwrap());
let u64_at = |o: usize| u64::from_le_bytes(b[o..o + 8].try_into().unwrap());
let section_count = u16_at(44) as usize;
if SECTION_DIR_OFFSET + section_count * SECTION_ENTRY_LEN > HEADER_LEN {
return Err(HeaderError::BadSectionCount(section_count));
}
let perms = if b[50] == 0 {
crate::index::PermSet::ALL
} else {
crate::index::PermSet::from_bits(b[50])
.map_err(|why| HeaderError::BadPermMask { mask: b[50], why })?
};
let mut h = Header {
version: b[4],
flags: b[5],
metadata_offset: 0,
metadata_len: 0,
dictionary_offset: 0,
dictionary_len: 0,
root_dir_offset: 0,
root_dir_len: 0,
pyramid_meta_offset: 0,
pyramid_meta_len: 0,
dict_codec: b[42],
block_codec: b[43],
pyramid_levels: u16_at(40),
quad_count: u64_at(24),
term_count: u64_at(32),
content_hash: b[8..24].try_into().unwrap(),
named_graphs_offset: 0,
named_graphs_len: 0,
schema_meta_len: u32_at(46),
perms,
text_index_offset: 0,
text_index_len: 0,
text_index_partial: false,
build_info_offset: 0,
build_info_len: 0,
extra_sections: Vec::new(),
};
for i in 0..section_count {
let p = SECTION_DIR_OFFSET + i * SECTION_ENTRY_LEN;
let kind = SectionKind::from_u16(u16_at(p));
let offset = u64_at(p + 8);
let length = u64_at(p + 16);
match kind {
SectionKind::Metadata => {
h.metadata_offset = offset;
h.metadata_len = length;
}
SectionKind::Dictionary => {
h.dictionary_offset = offset;
h.dictionary_len = length;
}
SectionKind::Index => {
h.root_dir_offset = offset;
h.root_dir_len = length;
}
SectionKind::PyramidMeta => {
h.pyramid_meta_offset = offset;
h.pyramid_meta_len = length;
}
SectionKind::NamedGraphs => {
h.named_graphs_offset = offset;
h.named_graphs_len = length;
}
SectionKind::TextIndex => {
h.text_index_offset = offset;
h.text_index_len = length;
h.text_index_partial = false;
}
SectionKind::TextIndexPartial => {
h.text_index_offset = offset;
h.text_index_len = length;
h.text_index_partial = true;
}
SectionKind::BuildInfo => {
h.build_info_offset = offset;
h.build_info_len = length;
}
SectionKind::LabelIndex
| SectionKind::Schema
| SectionKind::QuadIndex
| SectionKind::Unknown(_) => h.extra_sections.push(Section {
kind,
flags: u16_at(p + 2),
offset,
length,
}),
}
}
Ok(h)
}
pub fn label_index_section(&self) -> Option<Section> {
self.section(SectionKind::LabelIndex)
.filter(|s| s.length > 0)
}
pub fn schema_section(&self) -> Option<Section> {
self.section(SectionKind::Schema).filter(|s| s.length > 0)
}
pub fn has_quads(&self) -> bool {
self.flags & FLAG_HAS_QUADS != 0
}
pub fn expected_file_len(&self) -> Option<u64> {
let mut end = HEADER_LEN as u64;
let named = [
(self.metadata_offset, self.metadata_len),
(self.dictionary_offset, self.dictionary_len),
(self.root_dir_offset, self.root_dir_len),
(self.pyramid_meta_offset, self.pyramid_meta_len),
(self.named_graphs_offset, self.named_graphs_len),
(self.text_index_offset, self.text_index_len),
(self.build_info_offset, self.build_info_len),
];
let extras = self.extra_sections.iter().map(|s| (s.offset, s.length));
for (offset, length) in named.into_iter().chain(extras) {
if length == 0 {
continue; }
end = end.max(offset.checked_add(length)?);
}
end.checked_add(MAGIC.len() as u64)
}
pub fn has_quoted_triples(&self) -> bool {
self.flags & FLAG_HAS_QUOTED_TRIPLES != 0
}
pub fn has_tile_synopsis(&self) -> bool {
self.flags & FLAG_TILE_SYNOPSIS != 0
}
pub fn text_index_covers_named_graphs(&self) -> bool {
self.flags & FLAG_TEXT_INDEX_NAMED_GRAPHS != 0
}
pub fn text_index_payload(&self) -> (u64, u64) {
if self.text_index_partial {
(
self.text_index_offset
.saturating_add(TEXT_INDEX_PARTIAL_DIGEST_LEN),
self.text_index_len
.saturating_sub(TEXT_INDEX_PARTIAL_DIGEST_LEN),
)
} else {
(self.text_index_offset, self.text_index_len)
}
}
pub fn section(&self, kind: SectionKind) -> Option<Section> {
let (offset, length) = match kind {
SectionKind::Metadata => (self.metadata_offset, self.metadata_len),
SectionKind::Dictionary => (self.dictionary_offset, self.dictionary_len),
SectionKind::Index => (self.root_dir_offset, self.root_dir_len),
SectionKind::PyramidMeta => (self.pyramid_meta_offset, self.pyramid_meta_len),
SectionKind::NamedGraphs => (self.named_graphs_offset, self.named_graphs_len),
SectionKind::TextIndex if !self.text_index_partial => {
(self.text_index_offset, self.text_index_len)
}
SectionKind::TextIndexPartial if self.text_index_partial => {
(self.text_index_offset, self.text_index_len)
}
SectionKind::TextIndex | SectionKind::TextIndexPartial => (0, 0),
SectionKind::BuildInfo => (self.build_info_offset, self.build_info_len),
SectionKind::LabelIndex
| SectionKind::Schema
| SectionKind::QuadIndex
| SectionKind::Unknown(_) => {
return self.extra_sections.iter().find(|s| s.kind == kind).copied()
}
};
Some(Section {
kind,
flags: 0,
offset,
length,
})
}
pub fn with_section(mut self, kind: SectionKind, offset: u64, length: u64) -> Self {
match kind {
SectionKind::Metadata => {
self.metadata_offset = offset;
self.metadata_len = length;
}
SectionKind::Dictionary => {
self.dictionary_offset = offset;
self.dictionary_len = length;
}
SectionKind::Index => {
self.root_dir_offset = offset;
self.root_dir_len = length;
}
SectionKind::PyramidMeta => {
self.pyramid_meta_offset = offset;
self.pyramid_meta_len = length;
}
SectionKind::NamedGraphs => {
self.named_graphs_offset = offset;
self.named_graphs_len = length;
}
SectionKind::TextIndex => {
self.text_index_offset = offset;
self.text_index_len = length;
self.text_index_partial = false;
}
SectionKind::TextIndexPartial => {
self.text_index_offset = offset;
self.text_index_len = length;
self.text_index_partial = true;
}
SectionKind::BuildInfo => {
self.build_info_offset = offset;
self.build_info_len = length;
}
SectionKind::LabelIndex | SectionKind::Schema | SectionKind::QuadIndex => {
match self.extra_sections.iter_mut().find(|s| s.kind == kind) {
Some(s) => {
s.offset = offset;
s.length = length;
}
None => self.extra_sections.push(Section {
kind,
flags: 0,
offset,
length,
}),
}
}
SectionKind::Unknown(_) => self.extra_sections.push(Section {
kind,
flags: 0,
offset,
length,
}),
}
self
}
pub fn has_quad_index(&self) -> bool {
self.flags & FLAG_QUAD_INDEX != 0
}
pub fn quad_index_section(&self) -> Option<Section> {
self.section(SectionKind::QuadIndex)
.filter(|s| s.length > 0)
}
pub fn index_container_sections(&self) -> usize {
self.perms.len() + usize::from(self.has_quad_index())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample() -> Header {
Header {
version: CURRENT_FORMAT_VERSION,
flags: FLAG_HAS_QUADS,
metadata_offset: 1024,
metadata_len: 42,
dictionary_offset: 1066,
dictionary_len: 2048,
root_dir_offset: 3114,
root_dir_len: 256,
pyramid_meta_offset: 3370,
pyramid_meta_len: 64,
dict_codec: 1,
block_codec: 2,
pyramid_levels: 3,
perms: crate::index::PermSet::ALL,
quad_count: 5,
term_count: 9,
content_hash: [7u8; 16],
named_graphs_offset: 3434,
named_graphs_len: 48,
schema_meta_len: 99,
text_index_offset: 0,
text_index_len: 0,
text_index_partial: false,
build_info_offset: 0,
build_info_len: 0,
extra_sections: Vec::new(),
}
}
#[test]
fn round_trip() {
let h = sample();
let bytes = h.to_bytes();
assert_eq!(bytes.len(), HEADER_LEN);
assert_eq!(&bytes[0..4], b"RETE");
let back = Header::from_bytes(&bytes).unwrap();
assert_eq!(h, back);
assert!(back.has_quads());
}
#[test]
fn byte_layout_matches_spec() {
let h = Header {
content_hash: [0xCC; 16],
quad_count: 0x99,
term_count: 0xAA,
pyramid_levels: 0xABCD,
dict_codec: 0xA1,
block_codec: 0xA2,
schema_meta_len: 0xD00D,
metadata_offset: 0x11,
metadata_len: 0x22,
dictionary_offset: 0x33,
dictionary_len: 0x44,
..sample()
};
let b = h.to_bytes();
let u16_at = |o: usize| u16::from_le_bytes(b[o..o + 2].try_into().unwrap());
let u64_at = |o: usize| u64::from_le_bytes(b[o..o + 8].try_into().unwrap());
assert_eq!(&b[0..4], b"RETE");
assert_eq!(b[4], CURRENT_FORMAT_VERSION);
assert_eq!(b[5], FLAG_HAS_QUADS);
assert_eq!(u16_at(6), HEADER_LEN as u16);
assert_eq!(&b[8..24], &[0xCC; 16]); assert_eq!(u64_at(24), 0x99); assert_eq!(u64_at(32), 0xAA); assert_eq!(u16_at(40), 0xABCD); assert_eq!(b[42], 0xA1); assert_eq!(b[43], 0xA2); assert_eq!(u16_at(44), 5); assert_eq!(u32::from_le_bytes(b[46..50].try_into().unwrap()), 0xD00D); assert_eq!(u16_at(64), 1); assert_eq!(u64_at(72), 0x11); assert_eq!(u64_at(80), 0x22); assert_eq!(u16_at(88), 2);
assert_eq!(u64_at(96), 0x33);
assert_eq!(u64_at(104), 0x44);
assert_eq!(b.len(), HEADER_LEN);
}
#[test]
fn expected_file_len_is_last_section_end_plus_footer() {
assert_eq!(sample().expected_file_len(), Some(3482 + 4));
let mut h = sample();
h.text_index_offset = 1 << 40;
h.text_index_len = 0;
assert_eq!(h.expected_file_len(), Some(3482 + 4));
let mut e = sample();
e.metadata_len = 0;
e.dictionary_len = 0;
e.root_dir_len = 0;
e.pyramid_meta_len = 0;
e.named_graphs_len = 0;
assert_eq!(e.expected_file_len(), Some(1024 + 4));
let mut c = sample();
c.named_graphs_offset = u64::MAX - 8;
c.named_graphs_len = 64;
assert_eq!(c.expected_file_len(), None);
let f = sample().with_section(SectionKind::Unknown(99), 1 << 20, 512);
assert_eq!(f.expected_file_len(), Some((1 << 20) + 512 + 4));
}
#[test]
fn rejects_bad_magic() {
let mut bytes = [0u8; HEADER_LEN];
bytes[4] = CURRENT_FORMAT_VERSION;
assert!(matches!(
Header::from_bytes(&bytes),
Err(HeaderError::BadMagic)
));
}
#[test]
fn stable_reader_accepts_v1_baseline_and_rejects_pre_v1() {
let current = sample().to_bytes();
assert_eq!(current[4], 0x05);
assert_eq!(Header::from_bytes(¤t).unwrap().version, 0x05);
for old in 0x01..=0x04 {
let mut bytes = current;
bytes[4] = old;
let error = Header::from_bytes(&bytes).unwrap_err();
assert!(matches!(
&error,
HeaderError::UnsupportedVersion {
found,
min: 0x05,
max: 0x05
} if *found == old
));
assert!(error
.to_string()
.contains("Pre-1.0 files must be rebuilt from RDF source with `rete build`"));
}
for unsupported in [0x00, 0x06, 0xff] {
let mut bytes = current;
bytes[4] = unsupported;
assert!(matches!(
Header::from_bytes(&bytes),
Err(HeaderError::UnsupportedVersion {
found,
min: 0x05,
max: 0x05
}) if found == unsupported
));
}
}
#[test]
fn rejects_overrunning_section_count() {
let mut bad = sample().to_bytes();
bad[44..46].copy_from_slice(&9999u16.to_le_bytes());
assert!(matches!(
Header::from_bytes(&bad),
Err(HeaderError::BadSectionCount(9999))
));
}
#[test]
fn unknown_section_survives_round_trip() {
let h = sample().with_section(SectionKind::Unknown(99), 4096, 512);
let back = Header::from_bytes(&h.to_bytes()).unwrap();
assert_eq!(back.extra_sections.len(), 1);
let s = back.section(SectionKind::Unknown(99)).unwrap();
assert_eq!((s.offset, s.length), (4096, 512));
let dict = back.section(SectionKind::Dictionary).unwrap();
assert_eq!(dict.offset, h.dictionary_offset);
assert_eq!(h, back);
}
#[test]
fn build_info_section_round_trips_and_is_optional() {
let plain = sample().to_bytes();
assert_eq!(u16::from_le_bytes(plain[44..46].try_into().unwrap()), 5);
assert_eq!(sample().section(SectionKind::BuildInfo).unwrap().length, 0);
let h = sample().with_section(SectionKind::BuildInfo, 1066, 300);
let bytes = h.to_bytes();
assert_eq!(u16::from_le_bytes(bytes[44..46].try_into().unwrap()), 6);
let back = Header::from_bytes(&bytes).unwrap();
assert_eq!(h, back);
let s = back.section(SectionKind::BuildInfo).unwrap();
assert_eq!((s.offset, s.length), (1066, 300));
assert!(back.extra_sections.is_empty(), "BuildInfo is a known kind");
let far = sample().with_section(SectionKind::BuildInfo, 1 << 21, 128);
assert_eq!(far.expected_file_len(), Some((1 << 21) + 128 + 4));
}
#[test]
fn label_index_and_schema_sections_are_known_extras() {
assert!(sample().label_index_section().is_none());
assert!(sample().schema_section().is_none());
let h = sample()
.with_section(SectionKind::LabelIndex, 8000, 300)
.with_section(SectionKind::Schema, 8300, 40);
let bytes = h.to_bytes();
assert_eq!(u16::from_le_bytes(bytes[44..46].try_into().unwrap()), 7);
assert_eq!(
u16::from_le_bytes(bytes[64 + 5 * 24..][..2].try_into().unwrap()),
8
);
assert_eq!(
u16::from_le_bytes(bytes[64 + 6 * 24..][..2].try_into().unwrap()),
9
);
let back = Header::from_bytes(&bytes).unwrap();
assert_eq!(h, back);
assert_eq!(back.extra_sections.len(), 2, "stored with the extras");
let l = back.label_index_section().unwrap();
assert_eq!((l.offset, l.length), (8000, 300));
let s = back.schema_section().unwrap();
assert_eq!((s.offset, s.length), (8300, 40));
assert_eq!(back.expected_file_len(), Some(8340 + 4));
let again = back.with_section(SectionKind::Schema, 9000, 10);
assert_eq!(again.extra_sections.len(), 2);
assert_eq!(again.schema_section().unwrap().offset, 9000);
let empty = sample().with_section(SectionKind::LabelIndex, 0, 0);
assert!(empty.label_index_section().is_none());
}
#[test]
fn quad_index_flag_and_section_round_trip() {
let plain = sample();
assert!(!plain.has_quad_index());
assert!(plain.quad_index_section().is_none());
assert_eq!(plain.index_container_sections(), 6);
let mut h = sample().with_section(SectionKind::QuadIndex, 7000, 900);
h.flags |= FLAG_QUAD_INDEX;
h.named_graphs_offset = 0;
h.named_graphs_len = 0;
let bytes = h.to_bytes();
assert_eq!(bytes[5] & 0b1000, 0b1000, "flag bit 3");
let n = u16::from_le_bytes(bytes[44..46].try_into().unwrap()) as usize;
let kinds: Vec<u16> = (0..n)
.map(|i| {
let p = SECTION_DIR_OFFSET + i * SECTION_ENTRY_LEN;
u16::from_le_bytes(bytes[p..p + 2].try_into().unwrap())
})
.collect();
assert!(kinds.contains(&10), "a kind-10 directory entry: {kinds:?}");
let back = Header::from_bytes(&bytes).unwrap();
assert_eq!(back, h);
assert!(back.has_quad_index());
let s = back.quad_index_section().unwrap();
assert_eq!(
(s.kind, s.offset, s.length),
(SectionKind::QuadIndex, 7000, 900)
);
assert_eq!(back.index_container_sections(), 7);
assert_eq!(back.expected_file_len(), Some(7000 + 900 + 4));
let mut lean = back.clone();
lean.perms = crate::index::PermSet::CORE;
assert_eq!(lean.index_container_sections(), 4);
let again = back.with_section(SectionKind::QuadIndex, 8000, 10);
assert_eq!(again.extra_sections.len(), 1);
assert_eq!(again.quad_index_section().unwrap().offset, 8000);
}
#[test]
fn text_index_section_round_trips_and_is_optional() {
assert_eq!(
u16::from_le_bytes(sample().to_bytes()[44..46].try_into().unwrap()),
5
);
assert!(sample().section(SectionKind::TextIndex).unwrap().length == 0);
let h = sample().with_section(SectionKind::TextIndex, 5000, 4096);
let bytes = h.to_bytes();
assert_eq!(u16::from_le_bytes(bytes[44..46].try_into().unwrap()), 6);
let back = Header::from_bytes(&bytes).unwrap();
assert_eq!(h, back);
let s = back.section(SectionKind::TextIndex).unwrap();
assert_eq!((s.offset, s.length), (5000, 4096));
assert!(back.extra_sections.is_empty(), "TextIndex is a known kind");
assert!(!back.text_index_partial);
}
#[test]
fn partial_text_index_is_kind_11_and_invisible_as_kind_6() {
let h = sample().with_section(SectionKind::TextIndexPartial, 5000, 4096);
assert!(h.text_index_partial);
let bytes = h.to_bytes();
assert_eq!(u16::from_le_bytes(bytes[44..46].try_into().unwrap()), 6);
let p = SECTION_DIR_OFFSET + 5 * SECTION_ENTRY_LEN;
assert_eq!(u16::from_le_bytes(bytes[p..p + 2].try_into().unwrap()), 11);
let back = Header::from_bytes(&bytes).unwrap();
assert_eq!(h, back);
assert!(back.text_index_partial);
assert_eq!((back.text_index_offset, back.text_index_len), (5000, 4096));
let s = back.section(SectionKind::TextIndexPartial).unwrap();
assert_eq!((s.offset, s.length), (5000, 4096));
assert_eq!(back.section(SectionKind::TextIndex).unwrap().length, 0);
assert!(back.extra_sections.is_empty(), "kind 11 is known here");
let mut old_view = bytes;
old_view[p..p + 2].copy_from_slice(&0x7777u16.to_le_bytes());
let old = Header::from_bytes(&old_view).unwrap();
assert_eq!(old.text_index_len, 0);
assert_eq!(old.extra_sections.len(), 1);
}
}