lamfold_iso/el_torito.rs
1//! El Torito boot-catalog parsing — locating the embedded **UEFI** boot image.
2//!
3//! This is what boot-from-ISO needs: a downloaded `.iso` exposes its UEFI boot
4//! loader through an El Torito catalog, and LamBoot reads that image's byte
5//! range (then chainloads it as the embedded FAT ESP, `the boot-from-ISO design notes`
6//! path B). The catalog is a side-channel outside the filesystem surface — it
7//! has no directory/inode analogue.
8//!
9//! Clean-roomed from the public El Torito v1.0 specification.
10
11/// Platform id for UEFI El Torito entries (the 0xEF we want; 0x00 = x86 BIOS).
12const PLATFORM_UEFI: u8 = 0xEF;
13const BOOTABLE: u8 = 0x88;
14
15/// The El Torito UEFI boot image as a byte-range on the optical source. The
16/// range is `lba * 2048 .. + sectors * 512` (the LBA is a 2048-byte logical
17/// block; the sector count is in 512-byte virtual sectors, per the spec).
18#[derive(Clone, Copy, Debug, PartialEq, Eq)]
19pub struct UefiImage {
20 pub lba: u32,
21 pub sectors: u32,
22}
23
24/// Parse a boot catalog (one 2048-byte sector is plenty for the common case) and
25/// return the first UEFI boot image, or `None` if there is no UEFI entry.
26///
27/// Handles both layouts real tools emit: a validation entry already on platform
28/// 0xEF (the default/initial entry is then the UEFI image), and a BIOS default
29/// entry followed by a 0xEF section header + section entries.
30pub fn parse_catalog(cat: &[u8]) -> Option<UefiImage> {
31 if cat.len() < 64 {
32 return None;
33 }
34 // Validation Entry (offset 0): header_id 0x01, platform @1, key 0x55AA @30..32.
35 if cat[0] != 0x01 || cat[30] != 0x55 || cat[31] != 0xAA {
36 return None;
37 }
38 if cat[1] == PLATFORM_UEFI {
39 // The Initial/Default Entry (offset 32) is the UEFI image.
40 return parse_entry(&cat[32..64]);
41 }
42 // Otherwise walk Section Headers (offset 64) for a 0xEF platform.
43 let mut off = 64;
44 while off + 32 <= cat.len() {
45 let hdr = &cat[off..off + 32];
46 let indicator = hdr[0]; // 0x90 = more headers follow, 0x91 = final header
47 if indicator != 0x90 && indicator != 0x91 {
48 break;
49 }
50 let platform = hdr[1];
51 let n_entries = u16::from_le_bytes([hdr[2], hdr[3]]) as usize;
52 off += 32;
53 if platform == PLATFORM_UEFI {
54 for _ in 0..n_entries {
55 if off + 32 > cat.len() {
56 break;
57 }
58 if let Some(img) = parse_entry(&cat[off..off + 32]) {
59 return Some(img);
60 }
61 off += 32;
62 }
63 } else {
64 off = off.saturating_add(n_entries.saturating_mul(32));
65 }
66 if indicator == 0x91 {
67 break;
68 }
69 }
70 None
71}
72
73/// Parse a 32-byte Section/Default Entry: boot_indicator @0, sector_count (u16
74/// LE) @6, load_rba (u32 LE) @8.
75fn parse_entry(e: &[u8]) -> Option<UefiImage> {
76 if e.len() < 12 || e[0] != BOOTABLE {
77 return None;
78 }
79 let sectors = u32::from(u16::from_le_bytes([e[6], e[7]]));
80 let lba = u32::from_le_bytes([e[8], e[9], e[10], e[11]]);
81 Some(UefiImage { lba, sectors })
82}
83
84#[cfg(test)]
85mod tests {
86 use super::*;
87
88 #[test]
89 fn parses_validation_uefi_default_entry() {
90 // The exact shape `xorrisofs -e ... -no-emul-boot` emits (see the et.iso
91 // fixture): validation entry platform 0xEF, default entry = the image.
92 let mut cat = [0u8; 96];
93 cat[0] = 0x01; // header id
94 cat[1] = 0xEF; // platform UEFI
95 cat[30] = 0x55;
96 cat[31] = 0xAA; // key
97 cat[32] = 0x88; // bootable
98 cat[38] = 0x04; // sector_count LE = 4
99 cat[40] = 0x22; // load_rba LE = 0x22 = 34
100 assert_eq!(
101 parse_catalog(&cat),
102 Some(UefiImage {
103 lba: 34,
104 sectors: 4
105 })
106 );
107 }
108
109 #[test]
110 fn finds_uefi_section_after_bios_default() {
111 let mut cat = [0u8; 128];
112 cat[0] = 0x01;
113 cat[1] = 0x00; // validation platform = BIOS
114 cat[30] = 0x55;
115 cat[31] = 0xAA;
116 cat[32] = 0x88; // a BIOS default entry (ignored)
117 // Section header at offset 64: final (0x91), platform UEFI, 1 entry
118 cat[64] = 0x91;
119 cat[65] = 0xEF;
120 cat[66] = 0x01;
121 // Section entry at offset 96
122 cat[96] = 0x88;
123 cat[96 + 6] = 0x08; // 8 sectors
124 cat[96 + 8] = 0x40; // lba 0x40 = 64
125 assert_eq!(
126 parse_catalog(&cat),
127 Some(UefiImage {
128 lba: 64,
129 sectors: 8
130 })
131 );
132 }
133
134 #[test]
135 fn no_uefi_entry_returns_none() {
136 let mut cat = [0u8; 64];
137 cat[0] = 0x01;
138 cat[1] = 0x00;
139 cat[30] = 0x55;
140 cat[31] = 0xAA;
141 assert_eq!(parse_catalog(&cat), None);
142 }
143}