use super::*;
use crate::manifest_blocks::FLAG_FOOTER_MIRROR_ENABLED;
fn sample_payload() -> FooterPayload {
FooterPayload::new(
FLAG_FOOTER_MIRROR_ENABLED,
vec![
TocEntry {
name: "format_version".to_string(),
block_offset: 4096,
block_size: 64,
section_checksum: 0xDEAD_BEEF_DEAD_BEEF_DEAD_BEEF_DEAD_BEEF_u128,
},
TocEntry {
name: "tables".to_string(),
block_offset: 4160,
block_size: 512,
section_checksum: 0xCAFE_BABE_CAFE_BABE_CAFE_BABE_CAFE_BABE_u128,
},
],
)
}
#[test]
fn footer_payload_roundtrip_preserves_all_fields() {
let original = sample_payload();
let mut buf = Vec::new();
original.encode(&mut buf).expect("encode succeeds");
let decoded = FooterPayload::decode(&buf[..]).expect("decode succeeds");
assert_eq!(decoded, original);
}
#[test]
fn footer_payload_section_lookup_finds_by_name() {
let payload = sample_payload();
let tables = payload.section("tables").expect("tables section exists");
assert_eq!(tables.block_offset, 4160);
assert_eq!(tables.block_size, 512);
assert!(payload.section("nonexistent").is_none());
}
#[test]
fn footer_decode_rejects_unknown_layout_version() {
let mut buf = Vec::new();
buf.write_u8(2).unwrap(); buf.write_u8(0).unwrap();
buf.write_u16::<LittleEndian>(0).unwrap();
let err = FooterPayload::decode(&buf[..]).expect_err("must reject");
assert!(matches!(err, crate::Error::ManifestFooterInvalid(_)));
}
#[test]
fn footer_decode_rejects_empty_section_name() {
let mut buf = Vec::new();
buf.write_u8(MANIFEST_LAYOUT_VERSION_V1).unwrap();
buf.write_u8(0).unwrap();
buf.write_u16::<LittleEndian>(1).unwrap();
buf.write_u16::<LittleEndian>(0).unwrap(); buf.write_u64::<LittleEndian>(0).unwrap();
buf.write_u32::<LittleEndian>(0).unwrap();
let err = FooterPayload::decode(&buf[..]).expect_err("must reject");
assert!(matches!(err, crate::Error::ManifestFooterInvalid(_)));
}
#[test]
fn footer_decode_rejects_duplicate_section_names() {
let entries = vec![
TocEntry {
name: "tables".to_string(),
block_offset: 4096,
block_size: 64,
section_checksum: 0,
},
TocEntry {
name: "tables".to_string(), block_offset: 4160,
block_size: 64,
section_checksum: 0,
},
];
let payload = FooterPayload::new(0, entries);
let mut buf = Vec::new();
buf.write_u8(payload.layout_version).unwrap();
buf.write_u8(payload.flags).unwrap();
buf.write_u16::<LittleEndian>(payload.sections.len() as u16)
.unwrap();
for e in &payload.sections {
buf.write_u16::<LittleEndian>(e.name.len() as u16).unwrap();
buf.write_all(e.name.as_bytes()).unwrap();
buf.write_u64::<LittleEndian>(e.block_offset).unwrap();
buf.write_u32::<LittleEndian>(e.block_size).unwrap();
buf.write_u128::<LittleEndian>(e.section_checksum).unwrap();
}
let err = FooterPayload::decode(&buf[..]).expect_err("must reject");
assert!(matches!(err, crate::Error::ManifestFooterInvalid(_)));
}
#[test]
fn footer_decode_rejects_oversized_name_length() {
let mut buf = Vec::new();
buf.write_u8(MANIFEST_LAYOUT_VERSION_V1).unwrap();
buf.write_u8(0).unwrap();
buf.write_u16::<LittleEndian>(1).unwrap();
#[expect(
clippy::cast_possible_truncation,
reason = "test crafts bad input deliberately"
)]
let oversized = (MAX_SECTION_NAME_BYTES + 1) as u16;
buf.write_u16::<LittleEndian>(oversized).unwrap();
let err = FooterPayload::decode(&buf[..]).expect_err("must reject");
assert!(matches!(err, crate::Error::ManifestFooterInvalid(_)));
}
#[test]
fn footer_encode_rejects_empty_section_name() {
let payload = FooterPayload::new(
0,
vec![TocEntry {
name: String::new(),
block_offset: 0,
block_size: 0,
section_checksum: 0,
}],
);
let mut buf = Vec::new();
let err = payload.encode(&mut buf).expect_err("must reject");
assert!(matches!(err, crate::Error::ManifestFooterInvalid(_)));
}
#[test]
fn footer_encode_rejects_oversized_section_name() {
let oversized_name = "x".repeat(MAX_SECTION_NAME_BYTES + 1);
let payload = FooterPayload::new(
0,
vec![TocEntry {
name: oversized_name,
block_offset: 0,
block_size: 0,
section_checksum: 0,
}],
);
let mut buf = Vec::new();
let err = payload.encode(&mut buf).expect_err("must reject");
assert!(matches!(err, crate::Error::ManifestFooterInvalid(_)));
}