use crate::utils::ISO_SECTOR_SIZE;
use std::fs::File;
use std::io::{self, Write};
pub const LBA_BOOT_CATALOG: u32 = 19;
pub const BOOT_CATALOG_HEADER_SIGNATURE: u16 = 0xAA55;
pub const BOOT_CATALOG_VALIDATION_ENTRY_HEADER_ID: u8 = 1;
pub const BOOT_CATALOG_BOOT_ENTRY_HEADER_ID: u8 = 0x88;
pub const BOOT_CATALOG_EFI_PLATFORM_ID: u8 = 0xEF;
pub const ID_FIELD_OFFSET: usize = 4;
pub const BOOT_CATALOG_CHECKSUM_OFFSET: usize = 28;
pub struct BootCatalogEntry {
pub platform_id: u8,
pub boot_image_lba: u32,
pub boot_image_sectors: u16,
pub bootable: bool,
}
pub fn write_boot_catalog(iso: &mut File, entries: Vec<BootCatalogEntry>) -> io::Result<()> {
let mut catalog = [0u8; ISO_SECTOR_SIZE];
let mut offset = 0;
let mut val = [0u8; 32];
val[0] = BOOT_CATALOG_VALIDATION_ENTRY_HEADER_ID;
let first_platform = entries.first().map_or(0u8, |e| e.platform_id);
val[1] = first_platform;
let id_bytes = if first_platform == BOOT_CATALOG_EFI_PLATFORM_ID {
[0u8; 24]
} else {
let mut bytes = [0u8; 24];
let spec = b"EL TORITO SPECIFICATION";
bytes[0..spec.len()].copy_from_slice(spec);
bytes
};
val[ID_FIELD_OFFSET..ID_FIELD_OFFSET + 24].copy_from_slice(&id_bytes);
val[30..32].copy_from_slice(&BOOT_CATALOG_HEADER_SIGNATURE.to_le_bytes());
let mut sum: u16 = 0;
for i in (0..32).step_by(2) {
if i == BOOT_CATALOG_CHECKSUM_OFFSET {
continue;
}
let word = u16::from_le_bytes(val[i..i + 2].try_into().unwrap());
sum = sum.wrapping_add(word);
}
let checksum = 0u16.wrapping_sub(sum);
val[BOOT_CATALOG_CHECKSUM_OFFSET..BOOT_CATALOG_CHECKSUM_OFFSET + 2]
.copy_from_slice(&checksum.to_le_bytes());
catalog[offset..offset + 32].copy_from_slice(&val);
offset += 32;
for entry_data in entries {
let mut entry = [0u8; 32];
let boot_indicator = if entry_data.bootable {
BOOT_CATALOG_BOOT_ENTRY_HEADER_ID } else {
0x00u8
};
entry[0] = boot_indicator;
entry[1] = 0x00; entry[2..4].copy_from_slice(&0u16.to_le_bytes()); entry[4] = entry_data.platform_id;
let sectors = entry_data.boot_image_sectors;
entry[6..8].copy_from_slice(§ors.to_le_bytes());
let load_rba = entry_data.boot_image_lba;
entry[8..12].copy_from_slice(&load_rba.to_le_bytes());
catalog[offset..offset + 32].copy_from_slice(&entry);
offset += 32;
}
iso.write_all(&catalog)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::{Read, Seek, SeekFrom};
use tempfile::NamedTempFile;
fn verify_checksum(validation_entry: &[u8; 32]) {
let mut sum: u16 = 0;
for i in (0..32).step_by(2) {
sum = sum.wrapping_add(u16::from_le_bytes([
validation_entry[i],
validation_entry[i + 1],
]));
}
assert_eq!(sum, 0, "Boot catalog validation entry checksum is invalid");
}
#[test]
fn test_single_efi_boot_entry() -> io::Result<()> {
let mut temp_file = NamedTempFile::new()?;
let entries = vec![BootCatalogEntry {
platform_id: BOOT_CATALOG_EFI_PLATFORM_ID,
boot_image_lba: 100,
boot_image_sectors: 50,
bootable: true,
}];
write_boot_catalog(temp_file.as_file_mut(), entries)?;
let mut buffer = [0u8; ISO_SECTOR_SIZE];
temp_file.seek(SeekFrom::Start(0))?;
temp_file.read_exact(&mut buffer)?;
let val_entry: &[u8; 32] = &buffer[0..32].try_into().unwrap();
assert_eq!(val_entry[0], BOOT_CATALOG_VALIDATION_ENTRY_HEADER_ID);
assert_eq!(val_entry[1], BOOT_CATALOG_EFI_PLATFORM_ID);
assert_eq!(
&val_entry[30..32],
&BOOT_CATALOG_HEADER_SIGNATURE.to_le_bytes()
);
verify_checksum(val_entry);
let boot_entry: &[u8; 32] = &buffer[32..64].try_into().unwrap();
assert_eq!(boot_entry[0], BOOT_CATALOG_BOOT_ENTRY_HEADER_ID);
assert_eq!(boot_entry[1], 0x00); assert_eq!(&boot_entry[6..8], &50u16.to_le_bytes()); assert_eq!(&boot_entry[8..12], &100u32.to_le_bytes());
Ok(())
}
#[test]
fn test_non_bootable_entry() -> io::Result<()> {
let mut temp_file = NamedTempFile::new()?;
let entries = vec![BootCatalogEntry {
platform_id: 0, boot_image_lba: 200,
boot_image_sectors: 20,
bootable: false,
}];
write_boot_catalog(temp_file.as_file_mut(), entries)?;
let mut buffer = [0u8; ISO_SECTOR_SIZE];
temp_file.seek(SeekFrom::Start(0))?;
temp_file.read_exact(&mut buffer)?;
let boot_entry: &[u8; 32] = &buffer[32..64].try_into().unwrap();
assert_eq!(boot_entry[0], 0x00);
Ok(())
}
}