Skip to main content

lumen/chunks/
ltbl.rs

1//! The `LTBL` chunk: the layer table
2//! ([`spec/09-layer-data.md`] section 4.7).
3//!
4//! One 28-byte entry per (layer, sector), grouped by layer and ascending
5//! `sector_id` within a layer, a layer's first entry being sector 0's. An entry
6//! carries everything needed to reach one sector's mask without walking the
7//! table: the `LAYR` chunk that holds the layer's run for that sector, the
8//! offset and size of the layer's slice inside that chunk's decompressed output,
9//! the `LROV` chunk holding the (layer, sector)'s overrides or `0`, and how many
10//! further entries the same layer has.
11
12use crate::check::Check;
13use crate::error::{Error, Result};
14use crate::io::{Reader, Writer};
15
16/// The `LTBL` chunk header: `table_version`, `layer_count`, `entry_size`,
17/// `entry_count`.
18pub const LTBL_HEADER_LEN: usize = 16;
19/// Bytes per entry in a v1 table.
20pub const LTBL_ENTRY_SIZE_V1: u32 = 28;
21
22/// One (layer, sector) of the layer table.
23#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
24pub struct LayerEntry {
25    /// Bytes of this layer's data for this sector; `0` means none.
26    pub data_size: u32,
27    /// Directory index of this (layer, sector)'s `LROV` chunk; `0` means no
28    /// overrides. Index 0 is `HEAD`, so `0` is a safe null.
29    pub first_lrov: u32,
30    /// Directory index of the `LAYR` chunk holding this sector's run.
31    pub first_layr: u32,
32    /// Further entries for this layer, ascending `sector_id`; non-zero only on
33    /// the layer's first entry.
34    pub additional_sector_count: u32,
35    /// Offset of this layer's slice inside `first_layr`'s decompressed output.
36    pub data_offset: u64,
37    /// This entry's sector. `0` is the primary sector.
38    pub sector_id: u32,
39}
40
41impl LayerEntry {
42    /// Whether this (layer, sector) carries no data.
43    pub fn is_empty(&self) -> bool {
44        self.data_size == 0
45    }
46}
47
48/// The `LTBL` chunk.
49#[derive(Debug, Clone, PartialEq, Eq)]
50pub struct LayerTable {
51    /// Layout version. `1` for this specification.
52    pub table_version: u32,
53    /// Layers the table describes, which must equal `HEAD.total_layers`.
54    pub layer_count: u32,
55    /// Bytes per entry; readers stride by this and skip unknown trailing fields.
56    pub entry_size: u32,
57    /// Entries in the table.
58    pub entry_count: u32,
59    /// Every entry, in table order.
60    pub entries: Vec<LayerEntry>,
61    /// Index of each layer's first entry, derived from `entries`.
62    first: Vec<u32>,
63}
64
65impl LayerTable {
66    /// Parse the chunk payload.
67    pub fn parse(payload: &[u8]) -> Result<LayerTable> {
68        let mut r = Reader::checked(payload, Check::LtblEntrySize);
69        let table_version = r.u32()?;
70        if table_version != 1 {
71            return Err(Error::new(
72                Check::LtblVersion,
73                format!("unsupported table_version {table_version}"),
74            ));
75        }
76        let layer_count = r.u32()?;
77        let entry_size = r.u32()?;
78        let entry_count = r.u32()?;
79        if entry_size < LTBL_ENTRY_SIZE_V1 {
80            return Err(Error::new(
81                Check::LtblEntrySize,
82                format!("entry_size {entry_size} is below the {LTBL_ENTRY_SIZE_V1} byte v1 layout"),
83            ));
84        }
85        let table_len = (entry_count as usize)
86            .checked_mul(entry_size as usize)
87            .ok_or_else(|| Error::new(Check::LtblEntryCount, "the layer table length overflows"))?;
88        // The declared count is what the chunk must hold and end at, so a payload
89        // that is shorter or longer than `entry_count` entries is the count's
90        // failure, not the entry layout's: `ltbl.entry_size` owns an entry_size
91        // below the v1 layout, and nothing else.
92        if table_len != r.remaining() {
93            return Err(Error::new(
94                Check::LtblEntryCount,
95                format!(
96                    "entry_count {entry_count} of {entry_size} bytes needs {table_len} bytes, {} remain",
97                    r.remaining()
98                ),
99            ));
100        }
101        let mut entries = Vec::with_capacity(entry_count as usize);
102        for _ in 0..entry_count {
103            entries.push(LayerEntry {
104                data_size: r.u32()?,
105                first_lrov: r.u32()?,
106                first_layr: r.u32()?,
107                additional_sector_count: r.u32()?,
108                data_offset: r.u64()?,
109                sector_id: r.u32()?,
110            });
111            // Future versions may append fields; stride past them.
112            r.skip(entry_size as usize - LTBL_ENTRY_SIZE_V1 as usize)?;
113        }
114        let first = layer_index(&entries, layer_count)?;
115        Ok(LayerTable {
116            table_version,
117            layer_count,
118            entry_size,
119            entry_count,
120            entries,
121            first,
122        })
123    }
124
125    /// Build a table over `entries`, which must account for `layer_count`
126    /// layers exactly as [`LayerTable::parse`] requires of a file.
127    pub fn new(entries: Vec<LayerEntry>, layer_count: u32) -> Result<LayerTable> {
128        let first = layer_index(&entries, layer_count)?;
129        Ok(LayerTable {
130            table_version: 1,
131            layer_count,
132            entry_size: LTBL_ENTRY_SIZE_V1,
133            entry_count: entries.len() as u32,
134            entries,
135            first,
136        })
137    }
138
139    /// Serialize the chunk payload at the v1 entry size.
140    pub fn to_bytes(&self) -> Vec<u8> {
141        let mut w = Writer::with_capacity(
142            LTBL_HEADER_LEN + self.entries.len() * LTBL_ENTRY_SIZE_V1 as usize,
143        );
144        w.u32(self.table_version);
145        w.u32(self.layer_count);
146        w.u32(LTBL_ENTRY_SIZE_V1);
147        w.u32(self.entries.len() as u32);
148        for entry in &self.entries {
149            w.u32(entry.data_size);
150            w.u32(entry.first_lrov);
151            w.u32(entry.first_layr);
152            w.u32(entry.additional_sector_count);
153            w.u64(entry.data_offset);
154            w.u32(entry.sector_id);
155        }
156        w.into_vec()
157    }
158
159    /// The entries of layer `layer`, its first entry included.
160    ///
161    /// Empty when the table describes no such layer.
162    pub fn layer_entries(&self, layer: u32) -> &[LayerEntry] {
163        let Some(&start) = self.first.get(layer as usize) else {
164            return &[];
165        };
166        let start = start as usize;
167        // The count was proven to fit the table when the index was built.
168        let count = 1 + self.entries[start].additional_sector_count as usize;
169        &self.entries[start..start + count]
170    }
171
172    /// The first entry of layer `layer`, which is sector 0's.
173    pub fn first_entry(&self, layer: u32) -> Option<&LayerEntry> {
174        self.layer_entries(layer).first()
175    }
176
177    /// The entry for `(layer, sector_id)`.
178    pub fn entry(&self, layer: u32, sector_id: u32) -> Option<&LayerEntry> {
179        self.layer_entries(layer)
180            .iter()
181            .find(|entry| entry.sector_id == sector_id)
182    }
183
184    /// Whether some layer carries more than one sector with data.
185    ///
186    /// A layer carries a sector when its entry for it holds bytes, so an entry
187    /// that exists only to point at an `LROV` chunk does not make a file
188    /// multi-sector. This is what the `MULTI_SECTOR` header flag asserts.
189    pub fn is_multi_sector(&self) -> bool {
190        (0..self.layer_count).any(|layer| {
191            self.layer_entries(layer)
192                .iter()
193                .filter(|entry| !entry.is_empty())
194                .count()
195                > 1
196        })
197    }
198}
199
200/// The index of every layer's first entry, checking the accounting rules.
201///
202/// A layer's entries are `1 + additional_sector_count`, `additional_sector_count`
203/// is non-zero only on a layer's first entry, and the walk must land exactly on
204/// the end of the table.
205fn layer_index(entries: &[LayerEntry], layer_count: u32) -> Result<Vec<u32>> {
206    // Section 11 orders these rules, and the order decides which name a file
207    // that breaks several of them is reported under: the accounting first, then
208    // the walk over the layers, and only then the per-layer sector rules. So the
209    // walk is completed before any layer's contents are judged.
210    let mut first = Vec::with_capacity(layer_count as usize);
211    let mut cursor = 0usize;
212    for layer in 0..layer_count {
213        if cursor >= entries.len() {
214            return Err(Error::new(
215                Check::LtblLayerIndexRange,
216                format!(
217                    "layer {layer} has no first entry: {} of the {layer_count} layers account \
218                     for every one of the {} entries",
219                    first.len(),
220                    entries.len()
221                ),
222            ));
223        }
224        let count = 1 + entries[cursor].additional_sector_count as usize;
225        if entries.len() - cursor < count {
226            return Err(Error::new(
227                Check::LtblEntryCount,
228                format!(
229                    "layer {layer} declares {count} entries, but only {} remain after {}",
230                    entries.len() - cursor,
231                    cursor
232                ),
233            ));
234        }
235        for (k, entry) in entries[cursor..cursor + count].iter().enumerate().skip(1) {
236            if entry.additional_sector_count != 0 {
237                return Err(Error::new(
238                    Check::LtblEntryCount,
239                    format!(
240                        "layer {layer}'s entry {k} carries additional_sector_count {}, which only \
241                         a layer's first entry may",
242                        entry.additional_sector_count
243                    ),
244                ));
245            }
246        }
247        first.push(cursor as u32);
248        cursor += count;
249    }
250    if cursor != entries.len() {
251        return Err(Error::new(
252            Check::LtblEntryCount,
253            format!(
254                "{layer_count} layers account for {cursor} entries, but the table declares {}",
255                entries.len()
256            ),
257        ));
258    }
259
260    // Within a layer, the sectors ascend and none repeats.
261    for layer in 0..layer_count {
262        let start = first[layer as usize] as usize;
263        let count = 1 + entries[start].additional_sector_count as usize;
264        for pair in entries[start..start + count].windows(2) {
265            if pair[1].sector_id == pair[0].sector_id {
266                return Err(Error::new(
267                    Check::LtblSectorIdUnique,
268                    format!("layer {layer} names sector {} twice", pair[1].sector_id),
269                ));
270            }
271            if pair[1].sector_id < pair[0].sector_id {
272                return Err(Error::new(
273                    Check::LtblSectorIdsAscending,
274                    format!(
275                        "layer {layer} names sector {} after sector {}",
276                        pair[1].sector_id, pair[0].sector_id
277                    ),
278                ));
279            }
280        }
281    }
282
283    // A layer's first entry is sector 0's.
284    for layer in 0..layer_count {
285        let start = first[layer as usize] as usize;
286        if entries[start].sector_id != 0 {
287            return Err(Error::new(
288                Check::LtblFirstEntryIsSectorZero,
289                format!(
290                    "layer {layer}'s first entry names sector {}, not the primary sector 0",
291                    entries[start].sector_id
292                ),
293            ));
294        }
295    }
296    Ok(first)
297}
298
299#[cfg(test)]
300mod tests {
301    use super::*;
302
303    /// The v1 decision for a (layer, sector): which chunk, which slice, which
304    /// overrides, and how many further entries the layer has.
305    fn entry(
306        sector_id: u32,
307        first_layr: u32,
308        data_offset: u64,
309        data_size: u32,
310        additional: u32,
311        first_lrov: u32,
312    ) -> LayerEntry {
313        LayerEntry {
314            data_size,
315            first_lrov,
316            first_layr,
317            additional_sector_count: additional,
318            data_offset,
319            sector_id,
320        }
321    }
322
323    /// Two layers: layer 0 with two sectors, layer 1 with sector 0 only.
324    fn table() -> LayerTable {
325        LayerTable::new(
326            vec![
327                entry(0, 3, 0, 5, 1, 0),
328                entry(1, 4, 0, 7, 0, 6),
329                entry(0, 3, 5, 0, 0, 0),
330            ],
331            2,
332        )
333        .unwrap()
334    }
335
336    #[test]
337    fn a_table_round_trips_and_indexes_its_layers() {
338        let table = table();
339        assert_eq!(table.table_version, 1);
340        assert_eq!(table.layer_count, 2);
341        assert_eq!(table.entry_size, 28);
342        assert_eq!(table.entry_count, 3);
343        assert_eq!(table.layer_entries(0).len(), 2);
344        assert_eq!(table.layer_entries(1).len(), 1);
345        assert_eq!(table.layer_entries(2).len(), 0, "no such layer");
346        assert_eq!(table.first_entry(1).unwrap().data_size, 0);
347        assert_eq!(table.entry(0, 1).unwrap().first_lrov, 6);
348        assert_eq!(table.entry(0, 2), None);
349        assert!(table.is_multi_sector(), "layer 0 carries two sectors");
350
351        let bytes = table.to_bytes();
352        assert_eq!(bytes.len(), LTBL_HEADER_LEN + 3 * 28);
353        let parsed = LayerTable::parse(&bytes).unwrap();
354        assert_eq!(parsed, table);
355    }
356
357    #[test]
358    fn a_single_sector_file_is_not_multi_sector() {
359        let table =
360            LayerTable::new(vec![entry(0, 3, 0, 5, 0, 0), entry(0, 3, 5, 0, 0, 0)], 2).unwrap();
361        assert!(!table.is_multi_sector());
362
363        // A second entry that carries no data is not a second sector.
364        let table =
365            LayerTable::new(vec![entry(0, 3, 0, 5, 1, 0), entry(4, 4, 0, 0, 0, 7)], 1).unwrap();
366        assert!(!table.is_multi_sector(), "sector 4 holds no bytes");
367    }
368
369    #[test]
370    fn the_entry_accounting_is_enforced() {
371        let rejects = |entries: Vec<LayerEntry>, layers: u32, check: Check| {
372            assert_eq!(LayerTable::new(entries, layers).unwrap_err().check(), check);
373        };
374
375        // A layer that declares more entries than the table holds.
376        rejects(vec![entry(0, 3, 0, 5, 1, 0)], 1, Check::LtblEntryCount);
377        // A non-first entry carrying a count of its own.
378        rejects(
379            vec![entry(0, 3, 0, 5, 1, 0), entry(1, 4, 0, 7, 1, 0)],
380            1,
381            Check::LtblEntryCount,
382        );
383        // A table holding entries no layer accounts for.
384        rejects(
385            vec![entry(0, 3, 0, 5, 0, 0), entry(0, 3, 5, 1, 0, 0)],
386            1,
387            Check::LtblEntryCount,
388        );
389        // A layer with no entry at all: the walk runs out of table before it has
390        // described every layer the header declares.
391        rejects(vec![entry(0, 3, 0, 5, 0, 0)], 2, Check::LtblLayerIndexRange);
392        // A layer whose first entry is not sector 0's.
393        rejects(
394            vec![entry(3, 3, 0, 5, 0, 0)],
395            1,
396            Check::LtblFirstEntryIsSectorZero,
397        );
398        // Sector ids that do not ascend within a layer.
399        rejects(
400            vec![
401                entry(0, 3, 0, 5, 2, 0),
402                entry(2, 3, 5, 1, 0, 0),
403                entry(1, 3, 6, 1, 0, 0),
404            ],
405            1,
406            Check::LtblSectorIdsAscending,
407        );
408        // The same sector named twice on one layer.
409        rejects(
410            vec![
411                entry(0, 3, 0, 5, 2, 0),
412                entry(1, 3, 5, 1, 0, 0),
413                entry(1, 3, 6, 1, 0, 0),
414            ],
415            1,
416            Check::LtblSectorIdUnique,
417        );
418    }
419
420    #[test]
421    fn a_table_whose_bytes_disagree_with_its_header_is_rejected() {
422        let bytes = table().to_bytes();
423
424        // Three entries declared, room for three, but the accounting then runs
425        // out of table before the second layer's entry.
426        let mut short = bytes.clone();
427        short[12..16].copy_from_slice(&2u32.to_le_bytes());
428        assert_eq!(
429            LayerTable::parse(&short).unwrap_err().check(),
430            Check::LtblEntryCount
431        );
432
433        // Nine entries declared: the payload does not hold them, which is the
434        // declared count's failure rather than the entry layout's.
435        let mut over = bytes.clone();
436        over[12..16].copy_from_slice(&9u32.to_le_bytes());
437        assert_eq!(
438            LayerTable::parse(&over).unwrap_err().check(),
439            Check::LtblEntryCount
440        );
441
442        // The table declares three entries; layer 0's first entry claims nine,
443        // so the walk runs past the end.
444        let mut long = bytes.clone();
445        long[16 + 12..16 + 16].copy_from_slice(&9u32.to_le_bytes());
446        assert_eq!(
447            LayerTable::parse(&long).unwrap_err().check(),
448            Check::LtblEntryCount
449        );
450    }
451
452    #[test]
453    fn a_bad_version_entry_size_or_stride_is_rejected() {
454        let mut bytes = table().to_bytes();
455
456        let mut version = bytes.clone();
457        version[0..4].copy_from_slice(&2u32.to_le_bytes());
458        assert_eq!(
459            LayerTable::parse(&version).unwrap_err().check(),
460            Check::LtblVersion
461        );
462
463        let mut small = bytes.clone();
464        small[8..12].copy_from_slice(&27u32.to_le_bytes());
465        assert_eq!(
466            LayerTable::parse(&small).unwrap_err().check(),
467            Check::LtblEntrySize
468        );
469
470        // entry_size is fine, but the chunk does not hold the declared entries.
471        bytes[12..16].copy_from_slice(&9u32.to_le_bytes());
472        assert_eq!(
473            LayerTable::parse(&bytes).unwrap_err().check(),
474            Check::LtblEntryCount
475        );
476    }
477
478    #[test]
479    fn a_wider_entry_strides_past_unknown_fields() {
480        // Three 32-byte entries: the v1 28-byte layout plus four unknown bytes.
481        let mut w = Writer::new();
482        w.u32(1);
483        w.u32(1);
484        w.u32(32);
485        w.u32(2);
486        for (sector, offset, size, additional) in [(0u32, 0u64, 5u32, 1u32), (2, 5, 7, 0)] {
487            w.u32(size);
488            w.u32(0);
489            w.u32(3);
490            w.u32(additional);
491            w.u64(offset);
492            w.u32(sector);
493            w.u32(0xDEAD_BEEF); // an unknown trailing field
494        }
495        let parsed = LayerTable::parse(&w.into_vec()).unwrap();
496        assert_eq!(parsed.entry_size, 32);
497        assert_eq!(parsed.layer_entries(0).len(), 2);
498        assert_eq!(parsed.entry(0, 2).unwrap().data_offset, 5);
499    }
500}