lumen/check.rs
1//! The named conformance checks.
2//!
3//! Every check carries the `<group>.<rule>` name the specification's conformance
4//! corpus uses in `expected_failure` ([`test-vectors/README.md`]), so a failure
5//! reported by this crate can be compared verbatim against the corpus. Names are
6//! stable: see `spec/17-validation.md` section 11, which groups them.
7
8/// A named conformance check, mirroring the corpus convention `<group>.<rule>`.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
10#[non_exhaustive]
11pub enum Check {
12 // -- group: trailer (section 3.3) --------------------------------------
13 /// `LEND` magic is absent from the last 8 bytes.
14 TrailerMagic,
15 /// Trailer CRC-32C does not match `bytes[0 .. file_len-8]`.
16 TrailerCrc32c,
17
18 // -- group: header (section 3.1) ---------------------------------------
19 /// `LUMN` magic is absent at offset 0.
20 HeaderMagic,
21 /// `header.version` is not recognized.
22 HeaderVersion,
23 /// `header.dir_offset` is outside the file, or the directory does not fit.
24 HeaderDirOffset,
25 /// `header.flags` sets a reserved bit (0, 2 or 4).
26 HeaderFlagsReserved,
27 /// `header.total_uncompressed_size` is non-zero and wrong.
28 HeaderTotalUncompressedSize,
29
30 // -- group: dir (section 3.2) ------------------------------------------
31 /// A chunk descriptor could not be read.
32 DirDescriptor,
33 /// Two chunks overlap.
34 DirOverlap,
35 /// A chunk's stored extent lies outside the file, or inside the directory.
36 DirChunkExtent,
37 /// The first chunk is not `HEAD`.
38 DirHeadFirst,
39
40 // -- group: presence (section 3, section 4) ----------------------------
41 /// A required chunk is missing.
42 PresenceHead,
43 /// `META` is missing.
44 PresenceMeta,
45 /// `LTBL` is missing.
46 PresenceLtbl,
47 /// `LAYR` is missing.
48 PresenceLayr,
49 /// The `ENCRYPTED` flag is set but `AUTH` is absent, or vice versa.
50 PresenceAuth,
51 /// Exactly one `ZDIC` is required (or forbidden) by the `LAYR` frames.
52 PresenceZdic,
53
54 // -- group: head (section 4.1) ------------------------------------------
55 /// `head_version` is not recognized.
56 HeadVersion,
57 /// `encoder_name_len` exceeds 256, or the chunk is not exactly the fixed
58 /// fields plus the name.
59 HeadFrame,
60 /// `total_layers == 0`, or disagrees with `LTBL.layer_count`.
61 HeadTotalLayers,
62 /// `layer_height_um == 0`.
63 HeadLayerHeight,
64 /// A build dimension is zero.
65 HeadBuildDims,
66 /// `display_width_px * display_height_px == 0`.
67 HeadDisplayPixels,
68 /// `MULTI_SECTOR` is set without a layer carrying more than one sector, or
69 /// clear while one does.
70 HeadMultiSectorFlag,
71
72 // -- group: auth (section 4.4) -----------------------------------------
73 /// `auth_version` is not recognized.
74 AuthVersion,
75 /// `cipher_id` is not `A256` or `C20P`.
76 AuthCipherKnown,
77 /// The AUTH payload is too short for its declared sections.
78 AuthFrame,
79
80 // -- group: meta (section 4.2) -----------------------------------------
81 /// The META payload is not a JSON object.
82 MetaJson,
83 /// `meta_version` is absent or unrecognized.
84 MetaVersion,
85 /// A required META field is absent.
86 MetaRequiredFields,
87 /// A META duration is not an integer number of milliseconds.
88 MetaTimeInteger,
89 /// An exposure time is not `> 0`.
90 MetaExposure,
91 /// `layer_height_um` is not `> 0`.
92 MetaLayerHeight,
93 /// `materials`, when present, is not a non-empty array of named entries.
94 MetaMaterialsShape,
95 /// `sectors`, when present, is not an array of objects with unique ids >= 1.
96 MetaSectorsShape,
97 /// A sector's `material_index` addresses no non-empty `materials` array.
98 MetaSectorMaterialIndex,
99 /// A cure curve has a non-positive `dp_um`/`ec_mj_cm2` or a negative `e0_mj_cm2`.
100 MetaCureCurve,
101 /// A temperature is outside `[0.0, 120.0]`.
102 MetaTemperatureRange,
103 /// A `light_pwm` or `bottom_light_pwm` is outside `0..=255`, in any payload
104 /// that carries one (section 4.2).
105 PwmRange,
106
107 // -- group: prof (section 4.3) -----------------------------------------
108 /// `profile_type` is not one of `material`, `printer`, `combined`.
109 ProfProfileType,
110 /// `profile_name` or `profile_version` is empty.
111 ProfProfileIdentity,
112 /// A profile duration is not an integer number of milliseconds.
113 ProfSettingsTimeInteger,
114 /// A profile exposure time is not `> 0`.
115 ProfSettingsExposure,
116 /// A profile `layer_height_um` is not `> 0`.
117 ProfSettingsLayerHeight,
118 /// A profile cure curve is out of range.
119 ProfCureCurve,
120 /// `profile_uuid` is not a well-formed UUID string.
121 ProfProfileUuid,
122 /// Profile materials are malformed.
123 ProfMaterialsShape,
124
125 // -- group: lrov (section 4.5) -----------------------------------------
126 /// The payload is not a JSON object.
127 LrovJson,
128 /// An overridden duration is not an integer number of milliseconds.
129 LrovTimeInteger,
130 /// An `LROV` chunk is referenced by no entry. Several entries naming one
131 /// chunk is how an override set covers a range of pairs, and is legal.
132 LrovOrphan,
133
134 // -- group: prev (section 4.6) -----------------------------------------
135 /// A reserved flag bit is set, or the role is outside 0-3.
136 PrevFlags,
137 /// The payload does not begin with the PNG signature (strict mode).
138 PrevPngSignature,
139
140 // -- group: voxl (section 4.11) ----------------------------------------
141 /// The payload is neither VOXL V2 nor a VOXL V1 document (strict mode).
142 VoxlSignature,
143
144 // -- group: extd (section 4.12) ----------------------------------------
145 /// The payload is shorter than the 8-byte frame.
146 ExtdFrame,
147 /// `ext_type` is not four ASCII characters.
148 ExtdExtType,
149 /// A reserved flag bit is set.
150 ExtdFlags,
151 /// An unimplemented extension carries `critical = 1`.
152 ExtdCritical,
153
154 // -- group: ltbl (section 4.7) -----------------------------------------
155 /// `table_version` is not recognized.
156 LtblVersion,
157 /// `layer_count` disagrees with `HEAD.total_layers`.
158 LtblLayerCount,
159 /// `entry_size < 28`, or the entries do not fit the chunk.
160 LtblEntrySize,
161 /// The entries do not account for every layer, or `entry_count` disagrees
162 /// with the sum of `1 + additional_sector_count` over each layer's first
163 /// entry, or a non-first entry carries a non-zero count.
164 LtblEntryCount,
165 /// A read or a table entry names a layer the table does not describe.
166 LtblLayerIndexRange,
167 /// Sector ids do not ascend within a layer.
168 LtblSectorIdsAscending,
169 /// The same sector id appears twice on one layer.
170 LtblSectorIdUnique,
171 /// A layer's first entry is not sector 0's.
172 LtblFirstEntryIsSectorZero,
173 /// `first_layr` is not the directory index of a `LAYR` chunk.
174 LtblFirstLayrInRange,
175 /// An `LROV` chunk no entry names carries a payload no named chunk carries.
176 LtblFirstLrovNull,
177 /// `first_lrov` is neither `0` nor the directory index of an `LROV` chunk.
178 LtblFirstLrovInRange,
179 /// `data_offset + data_size` exceeds the `LAYR` chunk's decompressed output.
180 LtblOffsetWithinChunk,
181 /// Two slices of one `LAYR` chunk overlap.
182 LtblSlicesDisjoint,
183
184 // -- group: layr (section 4.9) ----------------------------------------
185 /// `layr_version` is not recognized.
186 LayrVersion,
187 /// A frame carries no content size, so a reader cannot size its output.
188 LayrContentSizePresent,
189 /// A frame did not decompress to the size it declares.
190 LayrFrameDecompressedSize,
191 /// A frame's declared output exceeds what its stored size may expand to.
192 FrameAllocationBound,
193 /// A frame's dictionary ID disagrees with `ZDIC`.
194 LayrDictIdMatch,
195 /// A frame reports no dictionary while the file carries `ZDIC`.
196 LayrDictIdAbsent,
197
198 // -- group: zdic (section 4.8) -----------------------------------------
199 /// `zdic_version` is not recognized.
200 ZdicVersion,
201 /// `dict_size` exceeds `ZDIC_DICTSIZE_MAX`, or the bytes are missing.
202 ZdicDictSize,
203 /// `dict_id` disagrees with the `LAYR` frames.
204 ZdicDictIdMatch,
205 /// More than one non-null `ZDIC` chunk is present.
206 ZdicSingle,
207
208 // -- group: lhas (section 4.10) ----------------------------------------
209 /// The payload is too short for its header and hashes.
210 LhasFrame,
211 /// `hash_algorithm` is not `0x01` (SHA-256).
212 LhasHashAlgorithm,
213 /// `layer_count` disagrees with `HEAD.total_layers`.
214 LhasLayerCount,
215 /// The recomputed Merkle root differs from the stored one.
216 LhasRootRecompute,
217 /// A layer's recomputed leaf hash differs from the stored one (strict mode).
218 LhasLeafMatch,
219
220 // -- group: ree (section 5) --------------------------------------------
221 /// A layer carries an encoding tag other than `0x00`-`0x03`.
222 ReeTag,
223 /// A varint is overlong, truncated, or longer than 10 bytes.
224 ReeVarint,
225 /// The four plane lengths of a varint array describe planes that are not
226 /// prefix-closed, or that hold more bytes than the array's varints use
227 /// (strict); or a value needs more than four planes to encode.
228 ReePlanes,
229 /// A binary REE `first_value` is neither `0x00` nor `0xFF`.
230 ReeFirstValue,
231 /// The non-canonical `run_count == 0` form (strict mode).
232 ReeNoRunCountZero,
233 /// A stored run length is zero, or the lengths do not sum to `total_pixels` (strict).
234 ReeRunLengths,
235 /// Adjacent grayscale runs carry the same value, or a run is empty (strict).
236 ReeGrayscaleRuns,
237 /// An all-`0x00`/`0xFF` layer is stored as grayscale REE (strict).
238 ReeGrayscaleAllBinary,
239 /// An all-`0x00`/`0xFF` layer is stored as split REE, with no anti-aliasing
240 /// to overlay (strict).
241 ReeSplitAllBinary,
242 /// The split binary component does not threshold at `v >= 128` (strict).
243 ReeSplitThreshold,
244 /// Split overlay positions are not strictly increasing, or out of range.
245 ReeSplitPositions,
246 /// Attached-form escapes are not strictly increasing, fall outside the layer,
247 /// or do not sit strictly inside the core run they belong to.
248 ReeAttachPositions,
249 /// `aa_pixel_count` disagrees with the pixels the attachment bits and escapes
250 /// describe.
251 ReeAttachCount,
252 /// An attachment bit addresses a run that does not exist, or a run of one
253 /// pixel claims both of its ends, or padding bits past the last run are set
254 /// (strict).
255 ReeAttachBits,
256 /// An attached-form overlay value is `0x00`/`0xFF`, or does not threshold to
257 /// the core run it sits in (strict).
258 ReeAttachThreshold,
259 /// The decoded stream does not end exactly at `total_pixels`.
260 ReeEndPositions,
261 /// Bytes follow the end of a layer's REE stream.
262 ReeNoTrailingBytes,
263 /// A layer's `data_size` does not match its decoded stream.
264 ReeDataSize,
265
266 // -- group: sector (section 7) -----------------------------------------
267 /// Sector masks of one layer overlap (strict mode).
268 SectorPartition,
269
270 // -- group: crypt (section 9) ------------------------------------------
271 /// `AUTH.mode` has no bit set.
272 CryptModeEmpty,
273 /// The password section is shorter than its fixed 65 bytes.
274 CryptPasswordSectionLen,
275 /// The machine section holds no complete recipient entry.
276 CryptMachineSectionLen,
277 /// Argon2id parameters exceed the reader's budget.
278 CryptArgon2Budget,
279 /// A chunk's encrypted flag disagrees with the file's `ENCRYPTED` flag.
280 CryptChunkFlags,
281 /// An AEAD tag did not verify.
282 CryptTagVerify,
283 /// A `LAYR` frame's associated data did not bind it to its directory index.
284 CryptUnitIndexBinding,
285 /// A wrapped session key did not unwrap.
286 CryptKeyUnwrap,
287 /// A machine recipient entry is malformed.
288 CryptRecipientEntry,
289 /// An X25519 exchange produced the all-zero shared secret.
290 CryptLowOrderPoint,
291 /// No key could be recovered: wrong password, or no matching recipient.
292 CryptNoKey,
293}
294
295impl Check {
296 /// The `<group>.<rule>` name, verbatim as the conformance corpus spells it.
297 pub fn name(self) -> &'static str {
298 use Check::*;
299 match self {
300 TrailerMagic => "trailer.magic",
301 TrailerCrc32c => "trailer.crc32c",
302 HeaderMagic => "header.magic",
303 HeaderVersion => "header.version",
304 HeaderDirOffset => "header.dir_offset",
305 HeaderFlagsReserved => "header.flags_reserved",
306 HeaderTotalUncompressedSize => "header.total_uncompressed_size",
307 DirDescriptor => "dir.descriptor",
308 DirOverlap => "dir.overlap",
309 DirChunkExtent => "dir.chunk_extent",
310 DirHeadFirst => "dir.head_first",
311 PresenceHead => "presence.head",
312 PresenceMeta => "presence.meta",
313 PresenceLtbl => "presence.ltbl",
314 PresenceLayr => "presence.layr",
315 PresenceAuth => "presence.auth",
316 PresenceZdic => "presence.zdic",
317 HeadVersion => "head.version",
318 HeadFrame => "head.frame",
319 HeadTotalLayers => "head.total_layers",
320 HeadLayerHeight => "head.layer_height",
321 HeadBuildDims => "head.build_dims",
322 HeadDisplayPixels => "head.display_pixels",
323 HeadMultiSectorFlag => "head.multi_sector_flag",
324 AuthVersion => "auth.version",
325 AuthCipherKnown => "auth.cipher_known",
326 AuthFrame => "auth.frame",
327 MetaJson => "meta.json",
328 MetaVersion => "meta.version",
329 MetaRequiredFields => "meta.required_fields",
330 MetaTimeInteger => "meta.time_integer",
331 MetaExposure => "meta.exposure",
332 MetaLayerHeight => "meta.layer_height",
333 MetaMaterialsShape => "meta.materials_shape",
334 MetaSectorsShape => "meta.sectors_shape",
335 MetaSectorMaterialIndex => "meta.sector_material_index",
336 MetaCureCurve => "meta.cure_curve",
337 MetaTemperatureRange => "meta.temperature_range",
338 PwmRange => "pwm.range",
339 ProfProfileType => "prof.profile_type",
340 ProfProfileIdentity => "prof.profile_identity",
341 ProfSettingsTimeInteger => "prof.settings_time_integer",
342 ProfSettingsExposure => "prof.settings_exposure",
343 ProfSettingsLayerHeight => "prof.settings_layer_height",
344 ProfCureCurve => "prof.cure_curve",
345 ProfProfileUuid => "prof.profile_uuid",
346 ProfMaterialsShape => "prof.materials_shape",
347 LrovJson => "lrov.json",
348 LrovTimeInteger => "lrov.time_integer",
349 LrovOrphan => "lrov.orphan",
350 PrevFlags => "prev.flags",
351 PrevPngSignature => "prev.png_signature",
352 VoxlSignature => "voxl.signature",
353 ExtdFrame => "extd.frame",
354 ExtdExtType => "extd.ext_type",
355 ExtdFlags => "extd.flags",
356 ExtdCritical => "extd.critical",
357 LtblVersion => "ltbl.version",
358 LtblLayerCount => "ltbl.layer_count",
359 LtblEntrySize => "ltbl.entry_size",
360 LtblEntryCount => "ltbl.entry_count",
361 LtblLayerIndexRange => "ltbl.layer_index_range",
362 LtblSectorIdsAscending => "ltbl.sector_ids_ascending",
363 LtblSectorIdUnique => "ltbl.sector_id_unique",
364 LtblFirstEntryIsSectorZero => "ltbl.first_entry_is_sector_zero",
365 LtblFirstLayrInRange => "ltbl.first_layr_in_range",
366 LtblFirstLrovNull => "ltbl.first_lrov_null",
367 LtblFirstLrovInRange => "ltbl.first_lrov_in_range",
368 LtblOffsetWithinChunk => "ltbl.offset_within_chunk",
369 LtblSlicesDisjoint => "ltbl.slices_disjoint",
370 LayrVersion => "layr.version",
371 LayrContentSizePresent => "layr.content_size_present",
372 LayrFrameDecompressedSize => "layr.frame_decompressed_size",
373 FrameAllocationBound => "frame.allocation_bound",
374 LayrDictIdMatch => "layr.dict_id_match",
375 LayrDictIdAbsent => "layr.dict_id_absent",
376 ZdicVersion => "zdic.version",
377 ZdicDictSize => "zdic.dict_size",
378 ZdicDictIdMatch => "zdic.dict_id_match",
379 ZdicSingle => "zdic.single",
380 LhasFrame => "lhas.frame",
381 LhasHashAlgorithm => "lhas.hash_algorithm",
382 LhasLayerCount => "lhas.layer_count",
383 LhasRootRecompute => "lhas.root_recompute",
384 LhasLeafMatch => "lhas.leaf_match",
385 ReeTag => "ree.tag",
386 ReeVarint => "ree.varint",
387 ReePlanes => "ree.planes",
388 ReeFirstValue => "ree.first_value",
389 ReeNoRunCountZero => "ree.no_run_count_zero",
390 ReeRunLengths => "ree.run_lengths",
391 ReeGrayscaleRuns => "ree.grayscale_runs",
392 ReeGrayscaleAllBinary => "ree.grayscale_all_binary",
393 ReeSplitAllBinary => "ree.split_all_binary",
394 ReeSplitThreshold => "ree.split_threshold",
395 ReeSplitPositions => "ree.split_positions",
396 ReeAttachPositions => "ree.attach_positions",
397 ReeAttachCount => "ree.attach_count",
398 ReeAttachBits => "ree.attach_bits",
399 ReeAttachThreshold => "ree.attach_threshold",
400 ReeEndPositions => "ree.end_positions",
401 ReeNoTrailingBytes => "ree.no_trailing_bytes",
402 ReeDataSize => "ree.data_size",
403 SectorPartition => "sector.partition",
404 CryptModeEmpty => "crypt.mode_empty",
405 CryptPasswordSectionLen => "crypt.password_section_len",
406 CryptMachineSectionLen => "crypt.machine_section_len",
407 CryptArgon2Budget => "crypt.argon2_budget",
408 CryptChunkFlags => "crypt.chunk_flags",
409 CryptTagVerify => "crypt.tag_verify",
410 CryptUnitIndexBinding => "crypt.unit_index_binding",
411 CryptKeyUnwrap => "crypt.key_unwrap",
412 CryptRecipientEntry => "crypt.recipient_entry",
413 CryptLowOrderPoint => "crypt.low_order_point",
414 CryptNoKey => "crypt.no_key",
415 }
416 }
417
418 /// Whether the specification marks this check strict-mode only ([section 11.5]).
419 ///
420 /// A loose-mode read must not fail these; a strict validator must.
421 pub fn is_strict_only(self) -> bool {
422 use Check::*;
423 matches!(
424 self,
425 ReeNoRunCountZero
426 | ReePlanes
427 | ReeRunLengths
428 | ReeGrayscaleRuns
429 | ReeGrayscaleAllBinary
430 | ReeSplitAllBinary
431 | ReeSplitThreshold
432 | ReeAttachBits
433 | ReeAttachThreshold
434 | SectorPartition
435 | LhasLeafMatch
436 | PrevPngSignature
437 | VoxlSignature
438 )
439 }
440}
441
442impl core::fmt::Display for Check {
443 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
444 f.write_str(self.name())
445 }
446}