use std::collections::HashMap;
use std::path::{Path, PathBuf};
use lumen::check::Check;
use lumen::container::{self, ChunkType};
use lumen::crypto::{self, SessionKey};
use lumen::reader::LumenFile;
use lumen::validate::{self, Level};
use serde::Deserialize;
use sha2::{Digest, Sha256};
#[derive(Deserialize)]
struct Manifest {
valid: Vec<ValidEntry>,
invalid: Vec<InvalidEntry>,
}
#[derive(Deserialize)]
struct ValidEntry {
name: String,
file: String,
file_size: u64,
file_sha256: String,
chunk_count: u32,
dir_offset: u64,
header_flags: u32,
total_uncompressed_size: u64,
trailer_crc32c: String,
display_width_px: u32,
display_height_px: u32,
layer_height_um: u32,
total_layers: u32,
multi_sector: bool,
layr_chunks: Vec<LayrChunkRecord>,
ltbl: Vec<LtblRecord>,
dict: DictRecord,
layers: Vec<LayerRecord>,
merkle_root: String,
#[serde(default)]
crypto: Option<CryptoRecord>,
#[serde(default)]
resolved_timing: Vec<ResolvedTimingPoint>,
}
#[derive(Deserialize)]
struct ResolvedTimingPoint {
layer: u32,
sector: u32,
layer_height_um: u32,
exposure_ms: u32,
light_pwm: u32,
lift_slow_distance_um: u32,
lift_slow_speed_um_min: u32,
lift_fast_distance_um: u32,
lift_fast_speed_um_min: u32,
retract_fast_distance_um: u32,
retract_fast_speed_um_min: u32,
retract_slow_distance_um: u32,
retract_slow_speed_um_min: u32,
wait_time_before_cure_ms: u32,
wait_time_after_cure_ms: u32,
wait_time_after_lift_ms: u32,
}
#[derive(Deserialize)]
struct LayrChunkRecord {
index: u32,
sector_id: u32,
first_layer: u32,
layer_count: u32,
version: u32,
dict_id: u32,
content_size: u64,
frame_size: u64,
stored_len: u64,
sealed: bool,
}
#[derive(Deserialize)]
struct LtblRecord {
entry_index: u32,
layer: u32,
sector_id: u32,
data_size: u32,
first_lrov: u32,
first_layr: u32,
additional_sector_count: u32,
data_offset: u64,
}
#[derive(Deserialize)]
struct DictRecord {
present: bool,
dict_id: u32,
dict_size: u32,
}
#[derive(Deserialize)]
struct LayerRecord {
index: u32,
empty: bool,
sector_count: u32,
#[serde(default)]
tag: Option<u8>,
#[serde(default)]
sector_tags: Vec<Option<u8>>,
decompressed_sha256: String,
lhas_leaf: String,
}
#[derive(Deserialize)]
struct CryptoRecord {
cipher_id: String,
auth_version: u32,
mode: u32,
#[serde(default)]
password_utf8: Option<String>,
#[serde(default)]
local_recipient_private_key: Option<String>,
}
#[derive(Deserialize)]
struct InvalidEntry {
name: String,
file: String,
#[serde(default)]
base_vector: Option<String>,
#[serde(default)]
crypto: Option<CryptoRecord>,
expected_failure: String,
#[serde(default)]
strict_only: bool,
file_sha256: String,
}
fn vectors_dir() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).join("../../test-vectors")
}
fn load() -> (Manifest, PathBuf) {
let dir = vectors_dir();
let text = std::fs::read_to_string(dir.join("manifest.json")).unwrap_or_else(|_| {
panic!(
"no conformance corpus at {}.\n\
The corpus lives beside the specification, not inside this crate: run these tests \
from a checkout of https://github.com/Open-Resin-Alliance/LumenFormat.\n\
Verifying a published .crate in isolation cannot establish conformance, so this \
test refuses to pass on the strength of not having looked.",
dir.display()
)
});
(
serde_json::from_str(&text).expect("the corpus manifest must parse"),
dir,
)
}
fn sha256_hex(bytes: &[u8]) -> String {
let mut hasher = Sha256::new();
hasher.update(bytes);
hex::encode(hasher.finalize())
}
fn hex_bytes32(text: &str) -> [u8; 32] {
let mut out = [0u8; 32];
hex::decode_to_slice(text, &mut out).expect("a 32-byte hex value");
out
}
fn session_key(bytes: &[u8], crypto: &CryptoRecord) -> Option<SessionKey> {
let header = container::FileHeader::parse(bytes).ok()?;
let directory = container::parse_directory(bytes, &header).ok()?;
let descriptor = directory.find(ChunkType::AUTH)?;
let payload = &bytes
[descriptor.offset as usize..descriptor.offset as usize + descriptor.stored_len() as usize];
let auth = crypto::parse_auth(payload).ok()?;
if let Some(password) = crypto.password_utf8.as_deref() {
return crypto::unwrap_password(&auth, password).ok();
}
let private = crypto.local_recipient_private_key.as_deref()?;
crypto::unwrap_machine(&auth, &hex_bytes32(private)).ok()
}
fn open_vector<'a>(
name: &str,
bytes: &'a [u8],
entry: &ValidEntry,
key: Option<SessionKey>,
) -> LumenFile<'a> {
match entry.crypto.as_ref() {
None => LumenFile::open(bytes, Level::Strict),
Some(crypto_record) => {
let key = key.unwrap_or_else(|| panic!("{name}: an encrypted vector has a key"));
match crypto_record.password_utf8.as_deref() {
Some(password) => LumenFile::open_with_password(bytes, Level::Strict, password),
None => {
let private = hex_bytes32(
crypto_record
.local_recipient_private_key
.as_deref()
.expect("a recipient key"),
);
LumenFile::open_with_machine_key(bytes, Level::Strict, &private)
.or_else(|_| LumenFile::open_with_key(bytes, Level::Strict, key))
}
}
}
}
.unwrap_or_else(|e| panic!("{name}: opening must succeed: {e}"))
}
fn layer_data(file: &LumenFile<'_>, index: usize) -> Vec<u8> {
let mut out = Vec::new();
for entry in file.layer_table().layer_entries(index as u32) {
if entry.is_empty() {
continue;
}
let chunk = file
.layr_chunk_data(entry.first_layr)
.unwrap_or_else(|e| panic!("layer {index}: chunk {} must read: {e}", entry.first_layr));
let start = entry.data_offset as usize;
out.extend_from_slice(&chunk[start..start + entry.data_size as usize]);
}
out
}
fn slice_tag(file: &LumenFile<'_>, entry: &lumen::chunks::ltbl::LayerEntry) -> Option<u8> {
if entry.is_empty() {
return None;
}
let chunk = file
.layr_chunk_data(entry.first_layr)
.expect("a readable chunk");
Some(chunk[entry.data_offset as usize])
}
fn layer_of(table: &lumen::chunks::ltbl::LayerTable, index: usize) -> u32 {
let mut cursor = 0usize;
let mut layer = 0u32;
while cursor + 1 + table.entries[cursor].additional_sector_count as usize <= index {
cursor += 1 + table.entries[cursor].additional_sector_count as usize;
layer += 1;
}
layer
}
fn frame_of(bytes: &[u8], file: &LumenFile<'_>, index: u32, key: Option<&SessionKey>) -> Vec<u8> {
let descriptor = &file.directory().descriptors[index as usize];
assert_eq!(
descriptor.chunk_type,
ChunkType::LAYR,
"directory index {index} is a LAYR chunk"
);
let start = descriptor.offset as usize;
let stored = &bytes[start..start + descriptor.stored_len() as usize];
let frame = lumen::chunks::layr::frame(stored).expect("a LAYR container");
if !descriptor.is_encrypted() {
return frame.to_vec();
}
let cipher = file.auth().expect("a sealed file carries AUTH").cipher;
let key = key.expect("a readable sealed vector has a key");
lumen::crypto::open(cipher, key, ChunkType::LAYR, index, frame)
.expect("the frame authenticates under its directory index")
}
#[test]
fn valid_vectors_pass_and_match_golden_data() {
let (manifest, dir) = load();
assert!(!manifest.valid.is_empty(), "the corpus has valid vectors");
for entry in &manifest.valid {
let bytes = std::fs::read(dir.join(&entry.file)).expect("a readable vector");
let name = &entry.name;
assert_eq!(bytes.len() as u64, entry.file_size, "{name}: file size");
assert_eq!(sha256_hex(&bytes), entry.file_sha256, "{name}: file hash");
validate::validate(&bytes, Level::Loose)
.unwrap_or_else(|e| panic!("{name}: a loose read must accept it: {e}"));
validate::validate(&bytes, Level::Strict)
.unwrap_or_else(|e| panic!("{name}: a strict read must accept it: {e}"));
let key = entry.crypto.as_ref().and_then(|c| session_key(&bytes, c));
if let Some(key) = key {
validate::validate_with_key(&bytes, Level::Strict, key)
.unwrap_or_else(|e| panic!("{name}: unlocking must accept it: {e}"));
}
let file = open_vector(name, &bytes, entry, key);
if let Some(crypto_record) = entry.crypto.as_ref() {
let auth = file.auth().expect("an encrypted vector carries AUTH");
assert_eq!(
String::from_utf8_lossy(&auth.cipher.id()),
crypto_record.cipher_id,
"{name}: cipher_id"
);
assert_eq!(
auth.auth_version, crypto_record.auth_version,
"{name}: auth_version"
);
assert_eq!(auth.mode, crypto_record.mode, "{name}: AUTH mode");
}
assert_eq!(
file.header().chunk_count,
entry.chunk_count,
"{name}: chunk count"
);
assert_eq!(
file.header().dir_offset,
entry.dir_offset,
"{name}: dir offset"
);
assert_eq!(
file.header().flags,
entry.header_flags,
"{name}: header flags"
);
assert_eq!(
file.header().total_uncompressed_size,
entry.total_uncompressed_size,
"{name}: total uncompressed size"
);
assert_eq!(
container::parse_trailer(&bytes).unwrap(),
u32::from_str_radix(entry.trailer_crc32c.trim_start_matches("0x"), 16).unwrap(),
"{name}: trailer CRC-32C"
);
assert_eq!(
file.head().display_width_px,
entry.display_width_px,
"{name}: width"
);
assert_eq!(
file.head().display_height_px,
entry.display_height_px,
"{name}: height"
);
assert_eq!(
file.head().layer_height_um,
entry.layer_height_um,
"{name}: layer height"
);
assert_eq!(
file.layer_count(),
entry.total_layers,
"{name}: layer count"
);
assert_eq!(
file.multi_sector(),
entry.multi_sector,
"{name}: multi-sector flag"
);
assert_eq!(
file.layr_chunks().len(),
entry.layr_chunks.len(),
"{name}: LAYR chunk count"
);
for (k, chunk) in entry.layr_chunks.iter().enumerate() {
let got = file.layr_chunks()[k];
assert_eq!(got.index, chunk.index, "{name}: LAYR chunk {k} index");
let descriptor = &file.directory().descriptors[chunk.index as usize];
assert_eq!(
descriptor.stored_len(),
chunk.stored_len,
"{name}: LAYR chunk {k} stored length"
);
assert_eq!(
descriptor.is_encrypted(),
chunk.sealed,
"{name}: LAYR chunk {k} sealed bit"
);
let frame = frame_of(&bytes, &file, chunk.index, key.as_ref());
assert_eq!(
frame.len() as u64,
chunk.frame_size - if chunk.sealed { 28 } else { 0 },
"{name}: LAYR chunk {k} frame size"
);
assert_eq!(
lumen::chunks::frame_content_size(&frame).expect("a frame with its content size"),
chunk.content_size,
"{name}: LAYR chunk {k} content size"
);
assert_eq!(
lumen::chunks::frame_dict_id(&frame).expect("a frame header"),
chunk.dict_id,
"{name}: LAYR chunk {k} dictionary ID"
);
assert_eq!(
lumen::chunks::layr::parse(
&bytes[descriptor.offset as usize
..descriptor.offset as usize + descriptor.stored_len() as usize]
)
.expect("a LAYR container")
.1,
chunk.version,
"{name}: LAYR chunk {k} version"
);
let holders: Vec<(usize, &lumen::chunks::ltbl::LayerEntry)> = file
.layer_table()
.entries
.iter()
.enumerate()
.filter(|(_, e)| e.first_layr == chunk.index)
.collect();
if holders.is_empty() {
assert_eq!(
chunk.content_size, 0,
"{name}: LAYR chunk {k} holds no slice yet carries {} bytes",
chunk.content_size
);
}
assert!(
holders.len() as u64 <= u64::from(chunk.layer_count),
"{name}: LAYR chunk {k} holds {} slices for a range of {} layers",
holders.len(),
chunk.layer_count
);
assert!(
holders.iter().all(|(_, e)| e.sector_id == chunk.sector_id),
"{name}: LAYR chunk {k} holds only sector {}",
chunk.sector_id
);
for (position, _) in holders {
let layer = layer_of(file.layer_table(), position);
assert!(
(chunk.first_layer..chunk.first_layer + chunk.layer_count).contains(&layer),
"{name}: LAYR chunk {k} names layer {layer} outside {}..{}",
chunk.first_layer,
chunk.first_layer + chunk.layer_count
);
}
}
assert_eq!(
file.layer_table().entries.len(),
entry.ltbl.len(),
"{name}: LTBL entry count"
);
assert_eq!(
file.layer_table().entry_count,
entry.ltbl.len() as u32,
"{name}: LTBL header entry_count"
);
assert_eq!(
file.layer_table().layer_count,
entry.total_layers,
"{name}: LTBL layer count"
);
for (i, golden) in entry.ltbl.iter().enumerate() {
assert_eq!(golden.entry_index as usize, i, "{name}: entry {i} index");
let got = &file.layer_table().entries[i];
assert_eq!(got.sector_id, golden.sector_id, "{name}: entry {i} sector");
assert_eq!(got.data_size, golden.data_size, "{name}: entry {i} size");
assert_eq!(
got.first_lrov, golden.first_lrov,
"{name}: entry {i} overrides"
);
assert_eq!(got.first_layr, golden.first_layr, "{name}: entry {i} chunk");
assert_eq!(
got.additional_sector_count, golden.additional_sector_count,
"{name}: entry {i} further entries"
);
assert_eq!(
got.data_offset, golden.data_offset,
"{name}: entry {i} offset"
);
assert_eq!(
layer_of(file.layer_table(), i),
golden.layer,
"{name}: entry {i} belongs to layer {}",
golden.layer
);
}
assert_eq!(
file.dictionary().is_some(),
entry.dict.present,
"{name}: dictionary presence"
);
if let Some(dictionary) = file.dictionary() {
assert_eq!(
dictionary.dict_id, entry.dict.dict_id,
"{name}: dictionary ID"
);
assert_eq!(
dictionary.dict_bytes.len(),
entry.dict.dict_size as usize,
"{name}: dictionary size"
);
}
for layer in &entry.layers {
let index = layer.index as usize;
let entries = file.layer_table().layer_entries(layer.index);
assert_eq!(
entries.len(),
layer.sector_count as usize,
"{name}: layer {index} entry count"
);
assert_eq!(
entries.iter().all(|e| e.is_empty()),
layer.empty,
"{name}: layer {index} empty"
);
assert_eq!(
entries.first().and_then(|e| slice_tag(&file, e)),
layer.tag,
"{name}: layer {index} sector 0 tag"
);
if !layer.sector_tags.is_empty() {
let tags: Vec<Option<u8>> = entries.iter().map(|e| slice_tag(&file, e)).collect();
assert_eq!(tags, layer.sector_tags, "{name}: layer {index} sector tags");
}
let data = layer_data(&file, index);
assert_eq!(
sha256_hex(&data),
layer.decompressed_sha256,
"{name}: layer {index} decompressed bytes"
);
let mut hasher = Sha256::new();
hasher.update([0u8]);
hasher.update(&data);
assert_eq!(
hex::encode(hasher.finalize()),
layer.lhas_leaf,
"{name}: layer {index} leaf hash"
);
}
let hashes = file.layer_hashes().expect("the corpus vectors carry LHAS");
assert_eq!(
hex::encode(hashes.merkle_root),
entry.merkle_root,
"{name}: Merkle root"
);
file.verify_all()
.unwrap_or_else(|e| panic!("{name}: verify_all must accept a valid file: {e}"));
for index in 0..file.layer_count() {
let sector_ids: Vec<u32> = if file.multi_sector() {
file.layer_sectors(index)
.unwrap_or_else(|e| panic!("{name}: layer {index} sectors: {e}"))
.into_iter()
.map(|s| s.sector_id)
.collect()
} else {
vec![0]
};
for sector_id in sector_ids {
file.timing_for(index, sector_id)
.unwrap_or_else(|e| panic!("{name}: layer {index} sector {sector_id}: {e}"));
}
}
}
}
#[test]
fn resolved_timing_matches_the_manifest() {
let (manifest, dir) = load();
let mut checked = 0usize;
for entry in &manifest.valid {
let bytes = std::fs::read(dir.join(&entry.file)).expect("a readable vector");
let name = &entry.name;
let key = entry.crypto.as_ref().and_then(|c| session_key(&bytes, c));
let file = open_vector(name, &bytes, entry, key);
assert!(
!entry.resolved_timing.is_empty(),
"{name}: the manifest records resolved timing for every valid vector"
);
for point in &entry.resolved_timing {
let (layer, sector) = (point.layer, point.sector);
let resolved = file.timing_for(layer, sector).unwrap_or_else(|e| {
panic!("{name}: layer {layer} sector {sector} must resolve: {e}")
});
let fields = [
(
"layer_height_um",
point.layer_height_um,
resolved.layer_height_um,
),
("exposure_ms", point.exposure_ms, resolved.exposure_ms),
("light_pwm", point.light_pwm, resolved.light_pwm),
(
"lift_slow_distance_um",
point.lift_slow_distance_um,
resolved.lift_slow_distance_um,
),
(
"lift_slow_speed_um_min",
point.lift_slow_speed_um_min,
resolved.lift_slow_speed_um_min,
),
(
"lift_fast_distance_um",
point.lift_fast_distance_um,
resolved.lift_fast_distance_um,
),
(
"lift_fast_speed_um_min",
point.lift_fast_speed_um_min,
resolved.lift_fast_speed_um_min,
),
(
"retract_fast_distance_um",
point.retract_fast_distance_um,
resolved.retract_fast_distance_um,
),
(
"retract_fast_speed_um_min",
point.retract_fast_speed_um_min,
resolved.retract_fast_speed_um_min,
),
(
"retract_slow_distance_um",
point.retract_slow_distance_um,
resolved.retract_slow_distance_um,
),
(
"retract_slow_speed_um_min",
point.retract_slow_speed_um_min,
resolved.retract_slow_speed_um_min,
),
(
"wait_time_before_cure_ms",
point.wait_time_before_cure_ms,
resolved.wait_time_before_cure_ms,
),
(
"wait_time_after_cure_ms",
point.wait_time_after_cure_ms,
resolved.wait_time_after_cure_ms,
),
(
"wait_time_after_lift_ms",
point.wait_time_after_lift_ms,
resolved.wait_time_after_lift_ms,
),
];
for (field, expected, actual) in fields {
assert_eq!(
actual, expected,
"{name}: layer {layer} sector {sector}: {field}"
);
}
checked += 1;
}
}
assert!(checked > 0, "the corpus records resolved timing to check");
}
#[test]
fn invalid_vectors_fail_the_check_they_pin() {
let (manifest, dir) = load();
assert!(
!manifest.invalid.is_empty(),
"the corpus has invalid vectors"
);
let valid: HashMap<&str, &ValidEntry> = manifest
.valid
.iter()
.map(|v| (v.name.as_str(), v))
.collect();
for entry in &manifest.invalid {
let bytes = std::fs::read(dir.join(&entry.file)).expect("a readable vector");
let name = &entry.name;
assert_eq!(sha256_hex(&bytes), entry.file_sha256, "{name}: file hash");
if check_by_name(&entry.expected_failure).is_none() {
panic!("{name}: unknown expected check {}", entry.expected_failure);
}
let crypto_record = entry.crypto.as_ref().or_else(|| {
entry
.base_vector
.as_deref()
.and_then(|base| valid.get(base).copied())
.and_then(|base| base.crypto.as_ref())
});
let key = crypto_record.and_then(|record| session_key(&bytes, record));
let check = |level: Level| match key {
Some(key) => validate::validate_with_key(&bytes, level, key),
None => validate::validate(&bytes, level),
};
if entry.strict_only {
if let Err(e) = check(Level::Loose) {
panic!("{name}: a loose read must accept it, got {e}");
}
} else {
let err = check(Level::Loose)
.err()
.unwrap_or_else(|| panic!("{name}: a loose read must reject it"));
assert_eq!(
err.check_name(),
entry.expected_failure,
"{name}: failed check"
);
}
let err = check(Level::Strict)
.err()
.unwrap_or_else(|| panic!("{name}: a strict read must reject it"));
assert_eq!(
err.check_name(),
entry.expected_failure,
"{name}: failed check"
);
}
}
fn check_by_name(name: &str) -> Option<Check> {
const NAMES: &[(&str, Check)] = &[
("trailer.magic", Check::TrailerMagic),
("trailer.crc32c", Check::TrailerCrc32c),
("header.flags_reserved", Check::HeaderFlagsReserved),
("presence.auth", Check::PresenceAuth),
("presence.zdic", Check::PresenceZdic),
("presence.ltbl", Check::PresenceLtbl),
("auth.cipher_known", Check::AuthCipherKnown),
("crypt.mode_empty", Check::CryptModeEmpty),
("crypt.password_section_len", Check::CryptPasswordSectionLen),
("crypt.machine_section_len", Check::CryptMachineSectionLen),
("crypt.argon2_budget", Check::CryptArgon2Budget),
("crypt.chunk_flags", Check::CryptChunkFlags),
("crypt.tag_verify", Check::CryptTagVerify),
("crypt.unit_index_binding", Check::CryptUnitIndexBinding),
("meta.time_integer", Check::MetaTimeInteger),
("pwm.range", Check::PwmRange),
("meta.sectors_shape", Check::MetaSectorsShape),
("meta.sector_material_index", Check::MetaSectorMaterialIndex),
("prof.profile_type", Check::ProfProfileType),
("prof.profile_identity", Check::ProfProfileIdentity),
("prof.settings_time_integer", Check::ProfSettingsTimeInteger),
("prof.settings_exposure", Check::ProfSettingsExposure),
("prof.settings_layer_height", Check::ProfSettingsLayerHeight),
("prof.cure_curve", Check::ProfCureCurve),
("prof.profile_uuid", Check::ProfProfileUuid),
("prof.materials_shape", Check::ProfMaterialsShape),
("lrov.json", Check::LrovJson),
("lrov.time_integer", Check::LrovTimeInteger),
("lrov.orphan", Check::LrovOrphan),
("prev.flags", Check::PrevFlags),
("prev.png_signature", Check::PrevPngSignature),
("voxl.signature", Check::VoxlSignature),
("extd.frame", Check::ExtdFrame),
("extd.ext_type", Check::ExtdExtType),
("extd.flags", Check::ExtdFlags),
("extd.critical", Check::ExtdCritical),
("ltbl.version", Check::LtblVersion),
("ltbl.layer_count", Check::LtblLayerCount),
("ltbl.entry_size", Check::LtblEntrySize),
("ltbl.entry_count", Check::LtblEntryCount),
("ltbl.layer_index_range", Check::LtblLayerIndexRange),
("ltbl.sector_ids_ascending", Check::LtblSectorIdsAscending),
("ltbl.sector_id_unique", Check::LtblSectorIdUnique),
(
"ltbl.first_entry_is_sector_zero",
Check::LtblFirstEntryIsSectorZero,
),
("ltbl.first_layr_in_range", Check::LtblFirstLayrInRange),
("ltbl.first_lrov_null", Check::LtblFirstLrovNull),
("ltbl.first_lrov_in_range", Check::LtblFirstLrovInRange),
("ltbl.offset_within_chunk", Check::LtblOffsetWithinChunk),
("ltbl.slices_disjoint", Check::LtblSlicesDisjoint),
("layr.version", Check::LayrVersion),
("layr.content_size_present", Check::LayrContentSizePresent),
(
"layr.frame_decompressed_size",
Check::LayrFrameDecompressedSize,
),
("frame.allocation_bound", Check::FrameAllocationBound),
("layr.dict_id_match", Check::LayrDictIdMatch),
("layr.dict_id_absent", Check::LayrDictIdAbsent),
("head.frame", Check::HeadFrame),
("head.multi_sector_flag", Check::HeadMultiSectorFlag),
("zdic.dict_size", Check::ZdicDictSize),
("zdic.dict_id_match", Check::ZdicDictIdMatch),
("zdic.single", Check::ZdicSingle),
("zdic.dict_size", Check::ZdicDictSize),
("zdic.dict_id_match", Check::ZdicDictIdMatch),
("zdic.single", Check::ZdicSingle),
("lhas.frame", Check::LhasFrame),
("lhas.hash_algorithm", Check::LhasHashAlgorithm),
("lhas.layer_count", Check::LhasLayerCount),
("lhas.root_recompute", Check::LhasRootRecompute),
("lhas.leaf_match", Check::LhasLeafMatch),
("ree.tag", Check::ReeTag),
("ree.varint", Check::ReeVarint),
("ree.first_value", Check::ReeFirstValue),
("ree.no_run_count_zero", Check::ReeNoRunCountZero),
("ree.run_lengths", Check::ReeRunLengths),
("ree.grayscale_runs", Check::ReeGrayscaleRuns),
("ree.grayscale_all_binary", Check::ReeGrayscaleAllBinary),
("ree.split_all_binary", Check::ReeSplitAllBinary),
("ree.split_threshold", Check::ReeSplitThreshold),
("ree.split_positions", Check::ReeSplitPositions),
("ree.attach_positions", Check::ReeAttachPositions),
("ree.attach_count", Check::ReeAttachCount),
("ree.attach_bits", Check::ReeAttachBits),
("ree.attach_threshold", Check::ReeAttachThreshold),
("ree.end_positions", Check::ReeEndPositions),
("ree.data_size", Check::ReeDataSize),
("sector.partition", Check::SectorPartition),
("crypt.key_unwrap", Check::CryptKeyUnwrap),
("crypt.recipient_entry", Check::CryptRecipientEntry),
("crypt.low_order_point", Check::CryptLowOrderPoint),
("crypt.no_key", Check::CryptNoKey),
];
NAMES
.iter()
.find(|(candidate, _)| *candidate == name)
.map(|(_, check)| *check)
}
#[test]
fn every_corpus_check_name_resolves() {
let (manifest, _) = load();
for entry in &manifest.invalid {
assert!(
check_by_name(&entry.expected_failure).is_some(),
"the corpus names {} and this crate does not know it",
entry.expected_failure
);
}
}