1use super::io::{self, Read, Seek, Write};
6use core::fmt::Debug;
7
8use crate::types::{Endian, LittleEndian, U16, U32};
9#[cfg(feature = "alloc")]
10use alloc::vec::Vec;
11
12sync_only! {
13#[cfg(feature = "write")]
14use super::{
15 boot::options::BootOptions,
16 volume::BootRecordVolumeDescriptor,
17 write::{InputEntry, InputEntryKind, InputTree},
18};
19}
20
21#[derive(Debug, Clone, Copy, PartialEq, Eq)]
23pub enum BootError {
24 Io,
26 InvalidValidationChecksum,
28 InvalidValidationHeader,
30 InvalidValidationKey,
32 InvalidDefaultEntry,
34 ExpectedSectionHeader(u8),
36 UnexpectedEndOfCatalog,
38}
39
40impl core::fmt::Display for BootError {
41 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
42 match self {
43 Self::Io => write!(f, "I/O error"),
44 Self::InvalidValidationChecksum => {
45 write!(f, "invalid boot catalog validation checksum")
46 }
47 Self::InvalidValidationHeader => {
48 write!(f, "invalid boot catalog validation header (expected 0x01)")
49 }
50 Self::InvalidValidationKey => write!(
51 f,
52 "invalid boot catalog validation key (expected 0x55, 0xAA)"
53 ),
54 Self::InvalidDefaultEntry => write!(f, "default boot entry is not marked as bootable"),
55 Self::ExpectedSectionHeader(id) => write!(f, "expected section header, got: {id:#x}"),
56 Self::UnexpectedEndOfCatalog => write!(f, "boot catalog ended unexpectedly"),
57 }
58 }
59}
60
61#[cfg(feature = "std")]
62impl std::error::Error for BootError {}
63
64#[derive(Debug, Clone)]
75pub struct BaseBootCatalog {
76 pub validation: BootValidationEntry,
78 pub default_entry: BootSectionEntry,
80}
81
82impl Default for BaseBootCatalog {
83 fn default() -> Self {
84 Self::new(EmulationType::NoEmulation, 0, 0, 0)
85 }
86}
87
88impl BaseBootCatalog {
89 pub fn new(
91 media_type: EmulationType,
92 load_segment: u16,
93 sector_count: u16,
94 load_rba: u32,
95 ) -> Self {
96 Self {
97 validation: BootValidationEntry::new(),
98 default_entry: BootSectionEntry::new(media_type, load_segment, sector_count, load_rba),
99 }
100 }
101}
102
103#[cfg(feature = "alloc")]
105#[derive(Debug, Clone)]
106pub struct BootCatalog {
107 base: BaseBootCatalog,
108 sections: Vec<(BootSectionHeaderEntry, Vec<BootSectionEntry>)>,
109}
110
111#[cfg(feature = "alloc")]
112impl Default for BootCatalog {
113 fn default() -> Self {
114 Self::new(EmulationType::NoEmulation, 0, 0, 0)
115 }
116}
117
118#[cfg(feature = "alloc")]
119impl BootCatalog {
120 pub fn new(
122 media_type: EmulationType,
123 load_segment: u16,
124 sector_count: u16,
125 load_rba: u32,
126 ) -> Self {
127 Self {
128 base: BaseBootCatalog::new(media_type, load_segment, sector_count, load_rba),
129 sections: Vec::new(),
130 }
131 }
132
133 pub fn set_default_entry(&mut self, entry: BootSectionEntry) {
135 self.base.default_entry = entry;
136 }
137
138 pub fn add_section(&mut self, platform_id: PlatformId, entries: Vec<BootSectionEntry>) {
140 if let Some((header, _entry)) = self.sections.last_mut() {
141 header.header_type = 0x90;
143 }
144
145 let header = BootSectionHeaderEntry {
146 header_type: 0x91,
147 platform_id: platform_id.to_u8(),
148 section_count: U16::new(1),
149 section_ident: [0; 28],
150 };
151
152 self.sections.push((header, entries));
153 }
154
155 pub fn size(&self) -> usize {
164 let sections_size: usize = self
168 .sections
169 .iter()
170 .map(|(_, entries)| (entries.len() + 1) * 32)
171 .sum();
172 64 + sections_size + 32 }
174}
175
176#[derive(Debug, Clone, Copy)]
177pub enum BootCatalogEntry {
179 Validation(BootValidationEntry),
181 SectionHeader(BootSectionHeaderEntry),
183 SectionEntry(BootSectionEntry),
185 SectionEntryExtension(BootSectionEntryExtension),
187}
188
189impl BootCatalogEntry {
190 pub fn as_bytes(&self) -> &[u8] {
192 match self {
193 BootCatalogEntry::Validation(entry) => bytemuck::bytes_of(entry),
194 BootCatalogEntry::SectionHeader(entry) => bytemuck::bytes_of(entry),
195 BootCatalogEntry::SectionEntry(entry) => bytemuck::bytes_of(entry),
196 BootCatalogEntry::SectionEntryExtension(entry) => bytemuck::bytes_of(entry),
197 }
198 }
199
200 pub const fn size(&self) -> usize {
202 32
203 }
204}
205
206#[derive(Debug, Clone, Copy, PartialEq, Eq)]
207pub enum PlatformId {
209 X80X86,
211 PowerPC,
213 Macintosh,
215 UEFI,
217 Unknown(u8),
219}
220
221impl PlatformId {
222 pub fn from_u8(value: u8) -> Self {
224 match value {
225 0x00 => Self::X80X86,
226 0x01 => Self::PowerPC,
227 0x02 => Self::Macintosh,
228 0xEF => Self::UEFI,
229 value => Self::Unknown(value),
230 }
231 }
232
233 pub fn to_u8(self) -> u8 {
235 match self {
236 Self::X80X86 => 0x00,
237 Self::PowerPC => 0x01,
238 Self::Macintosh => 0x02,
239 Self::UEFI => 0xEF,
240 Self::Unknown(value) => value,
241 }
242 }
243}
244
245#[repr(C)]
253#[derive(Clone, Copy, bytemuck::Zeroable, bytemuck::Pod)]
254pub struct BootValidationEntry {
255 pub header_id: u8,
257 pub platform_id: u8,
259 pub reserved: [u8; 2],
261 pub manufacturer: [u8; 24],
263 pub checksum: U16<LittleEndian>,
265 pub key: [u8; 2],
267}
268
269impl Default for BootValidationEntry {
270 fn default() -> Self {
271 Self::new()
272 }
273}
274
275impl BootValidationEntry {
276 pub fn new() -> Self {
278 let mut entry = Self {
279 header_id: 1,
280 platform_id: 0,
281 reserved: [0; 2],
282 manufacturer: [0; 24],
283 checksum: U16::new(0),
284 key: [0x55, 0xAA],
285 };
286 entry.checksum.set(entry.calculate_checksum());
287 entry
288 }
289}
290
291impl Debug for BootValidationEntry {
292 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
293 f.debug_struct("BootValidationEntry")
294 .field("header_id", &format_args!("{:#x}", self.header_id))
295 .field("platform_id", &PlatformId::from_u8(self.platform_id))
296 .field(
297 "manufacturer",
298 &core::str::from_utf8(&self.manufacturer).unwrap(),
299 )
300 .field("checksum", &self.checksum.get())
301 .field("key", &self.key)
302 .finish_non_exhaustive()
303 }
304}
305
306impl BootValidationEntry {
307 pub fn is_valid(&self) -> bool {
309 self.header_id == 0x01
310 && self.key == [0x55, 0xAA]
311 && self.checksum.get() == self.calculate_checksum()
312 }
313
314 pub fn validate(&self) -> Result<(), BootError> {
316 if self.header_id != 0x01 {
317 return Err(BootError::InvalidValidationHeader);
318 }
319 if self.key != [0x55, 0xAA] {
320 return Err(BootError::InvalidValidationKey);
321 }
322 if self.checksum.get() != self.calculate_checksum() {
323 return Err(BootError::InvalidValidationChecksum);
324 }
325 Ok(())
326 }
327
328 pub fn calculate_checksum(&self) -> u16 {
334 let mut bytes = [0u8; 32];
336 bytes.copy_from_slice(bytemuck::bytes_of(self));
337 bytes[28] = 0;
340 bytes[29] = 0;
341 let mut checksum = 0u16;
342 for i in (0..32).step_by(2) {
343 let value = u16::from_le_bytes([bytes[i], bytes[i + 1]]);
344 checksum = checksum.wrapping_add(value);
345 }
346 (!checksum) + 1
348 }
349}
350
351#[repr(C)]
360#[derive(Clone, Copy)]
361pub struct BootSectionHeaderEntry {
362 pub header_type: u8,
365 pub platform_id: u8,
367 pub section_count: U16<LittleEndian>,
369 pub section_ident: [u8; 28],
371}
372
373impl Debug for BootSectionHeaderEntry {
374 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
375 f.debug_struct("BootSectionHeaderEntry")
376 .field("header_type", &format_args!("{:#x}", self.header_type))
377 .field("platform_id", &PlatformId::from_u8(self.platform_id))
378 .field("section_count", &self.section_count.get())
379 .field(
380 "section_ident",
381 &core::str::from_utf8(&self.section_ident).unwrap(),
382 )
383 .finish_non_exhaustive()
384 }
385}
386
387unsafe impl bytemuck::Zeroable for BootSectionHeaderEntry {}
388unsafe impl bytemuck::Pod for BootSectionHeaderEntry {}
389
390#[derive(Debug, Clone, Copy, PartialEq, Eq)]
391pub enum EmulationType {
394 NoEmulation,
396 Floppy1_2,
398 Floppy1_44,
400 Floppy2_88,
402 HardDisk,
404 Unknown(u8),
406}
407
408impl EmulationType {
409 pub fn from_u8(value: u8) -> Self {
411 match value {
412 0x00 => Self::NoEmulation,
413 0x01 => Self::Floppy1_2,
414 0x02 => Self::Floppy1_44,
415 0x03 => Self::Floppy2_88,
416 0x04 => Self::HardDisk,
417 value => Self::Unknown(value),
418 }
419 }
420
421 pub fn to_u8(self) -> u8 {
423 match self {
424 Self::NoEmulation => 0x00,
425 Self::Floppy1_2 => 0x01,
426 Self::Floppy1_44 => 0x02,
427 Self::Floppy2_88 => 0x03,
428 Self::HardDisk => 0x04,
429 Self::Unknown(value) => value,
430 }
431 }
432
433 pub fn is_emulated(self) -> bool {
442 (self.to_u8() & 0x0F) != 0
443 }
444}
445
446#[repr(C)]
455#[derive(Clone, Copy)]
456pub struct BootSectionEntry {
457 pub boot_indicator: u8,
459 pub boot_media_type: u8,
461 pub load_segment: U16<LittleEndian>,
463 pub system_type: u8,
465 pub reserved0: u8,
467 pub sector_count: U16<LittleEndian>,
469 pub load_rba: U32<LittleEndian>,
471 pub selection_criteria: u8,
473 pub vendor_unique: [u8; 19],
475}
476
477impl BootSectionEntry {
478 pub fn new(
480 media_type: EmulationType,
481 load_segment: u16,
482 sector_count: u16,
483 load_rba: u32,
484 ) -> Self {
485 Self {
486 boot_indicator: 0x88,
487 boot_media_type: media_type.to_u8(),
488 load_segment: U16::new(load_segment),
489 system_type: 0,
490 reserved0: 0,
491 sector_count: U16::new(sector_count),
492 load_rba: U32::new(load_rba),
493 selection_criteria: 0,
494 vendor_unique: [0; 19],
495 }
496 }
497}
498
499impl Debug for BootSectionEntry {
500 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
501 f.debug_struct("BootSectionEntry")
502 .field(
503 "boot_indicator",
504 &format_args!("{:#x}", self.boot_indicator),
505 )
506 .field(
507 "boot_media_type",
508 &EmulationType::from_u8(self.boot_media_type),
509 )
510 .field("load_segment", &self.load_segment.get())
511 .field("system_type", &self.system_type)
512 .field("sector_count", &self.sector_count.get())
513 .field("load_rba", &self.load_rba.get())
514 .field("selection_criteria", &self.selection_criteria)
515 .finish_non_exhaustive()
516 }
517}
518
519impl BootSectionEntry {
520 pub fn is_bootable(&self) -> bool {
522 self.boot_indicator == 0x88
523 }
524
525 pub fn is_valid(&self) -> bool {
527 self.is_bootable()
528 }
529}
530
531unsafe impl bytemuck::Zeroable for BootSectionEntry {}
532unsafe impl bytemuck::Pod for BootSectionEntry {}
533
534#[repr(C)]
535#[derive(Debug, Clone, Copy)]
536pub struct BootSectionEntryExtension {
538 pub extension_indicator: u8,
541 pub flags: u8,
544 pub vendor_unique: [u8; 30],
546}
547
548unsafe impl bytemuck::Zeroable for BootSectionEntryExtension {}
549unsafe impl bytemuck::Pod for BootSectionEntryExtension {}
550
551#[repr(C)]
556#[derive(Debug, Clone, Copy, bytemuck::Zeroable, bytemuck::Pod)]
557pub struct BootInfoTable {
558 pub iso_start: U32<LittleEndian>,
560 pub file_lba: U32<LittleEndian>,
562 pub file_len: U32<LittleEndian>,
564 pub checksum: U32<LittleEndian>,
566}
567
568impl BootInfoTable {
569 #[allow(dead_code)]
571 pub(crate) fn new(file_lba: u32, file_len: u32, checksum: u32) -> Self {
572 Self {
573 iso_start: U32::new(16),
574 file_lba: U32::new(file_lba),
575 file_len: U32::new(file_len),
576 checksum: U32::new(checksum),
577 }
578 }
579}
580
581#[repr(C)]
587#[derive(Debug, Clone, Copy, bytemuck::Zeroable, bytemuck::Pod)]
588pub struct Grub2BootInfoTable {
589 pub pvd_lba: U32<LittleEndian>,
591 pub file_lba: U32<LittleEndian>,
593 pub file_len: U32<LittleEndian>,
595 pub checksum: U32<LittleEndian>,
597 pub reserved: [u8; 40],
599}
600
601impl Grub2BootInfoTable {
602 #[allow(dead_code)]
604 pub(crate) fn new(file_lba: u32, file_len: u32, checksum: u32) -> Self {
605 Self {
606 pvd_lba: U32::new(16),
607 file_lba: U32::new(file_lba),
608 file_len: U32::new(file_len),
609 checksum: U32::new(checksum),
610 reserved: [0u8; 40],
611 }
612 }
613}
614
615io_transform! {
616impl BaseBootCatalog {
617 pub async fn parse<R: Read + Seek>(reader: &mut R) -> Result<Self, BootError> {
625 let validation = BootValidationEntry::parse(reader).await.map_err(|_| BootError::Io)?;
626 validation.validate()?;
627
628 let default_entry = BootSectionEntry::parse(reader).await.map_err(|_| BootError::Io)?;
629 if !default_entry.is_bootable() {
630 return Err(BootError::InvalidDefaultEntry);
631 }
632
633 Ok(Self {
634 validation,
635 default_entry,
636 })
637 }
638
639 pub async fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
641 writer.write_all(bytemuck::bytes_of(&self.validation)).await?;
642 writer.write_all(bytemuck::bytes_of(&self.default_entry)).await?;
643 Ok(())
644 }
645}
646
647impl BootValidationEntry {
648 pub async fn parse<T: Read>(reader: &mut T) -> Result<Self, io::Error> {
650 let mut buf = [0u8; 32];
651 reader.read_exact(&mut buf).await?;
652 Ok(bytemuck::cast(buf))
653 }
654}
655
656impl BootSectionEntry {
657 pub async fn parse<T: Read>(reader: &mut T) -> Result<Self, io::Error> {
659 let mut buf: [u8; 32] = [0; 32];
660 reader.read_exact(&mut buf).await?;
661 Ok(bytemuck::cast(buf))
662 }
663}
664
665#[cfg(feature = "alloc")]
666impl BootCatalog {
667 pub async fn parse<T: Read + Seek>(reader: &mut T) -> Result<Self, BootError> {
673 let base = BaseBootCatalog::parse(reader).await?;
674 let mut sections = Vec::new();
675 let mut buffer = [0u8; 32];
676 let mut has_more = false;
677 let mut header = None;
678 let mut entries = Vec::new();
679 loop {
680 reader.read_exact(&mut buffer).await.map_err(|_| BootError::Io)?;
681 match buffer[0] {
682 0x00 if !has_more => break,
683 0x90 => {
684 has_more = true;
685 if let Some(header) = header.take() {
686 sections.push((header, entries));
687 entries = Vec::new();
688 }
689 header = Some(bytemuck::cast(buffer));
690 }
691 0x91 => {
692 has_more = false;
693 if let Some(header) = header.take() {
694 sections.push((header, entries));
695 entries = Vec::new();
696 }
697 header = Some(bytemuck::cast(buffer));
698 }
699 id => {
700 if header.is_none() {
701 return Err(BootError::ExpectedSectionHeader(id));
702 }
703 entries.push(bytemuck::cast(buffer));
704 }
705 }
706 }
707
708 if has_more {
709 return Err(BootError::UnexpectedEndOfCatalog);
710 }
711 if let Some(header) = header {
712 sections.push((header, entries));
713 }
714
715 Ok(Self { base, sections })
716 }
717
718 pub async fn write<W: Write>(&self, writer: &mut W) -> io::Result<()> {
720 self.base.write(writer).await?;
721 for (header, entries) in self.sections.iter() {
722 writer.write_all(bytemuck::bytes_of(header)).await?;
723 for entry in entries {
724 writer.write_all(bytemuck::bytes_of(entry)).await?;
725 }
726 }
727 writer.write_all(&[0; 32]).await?;
729 Ok(())
730 }
731}
732} sync_only! {
735#[cfg(feature = "write")]
736pub struct ElToritoWriter;
738
739#[cfg(feature = "write")]
740impl ElToritoWriter {
741 pub fn create_descriptor(
746 opts: &BootOptions,
747 files: &mut InputTree,
748 ) -> BootRecordVolumeDescriptor {
749 if opts.write_boot_catalog {
750 let size = 96 + opts.entries.len() * 64;
751 let size = (size + 2047) & !2047;
752 let dir_pos = files
753 .entries
754 .iter()
755 .position(|entry| matches!(entry.kind, InputEntryKind::Directory(_)))
756 .unwrap_or(0);
757 files.entries.insert(
758 dir_pos.saturating_sub(1),
759 InputEntry::file("boot.catalog", alloc::vec![0; size]),
760 );
761 }
762 BootRecordVolumeDescriptor::new(0)
763 }
764}
765}
766
767#[cfg(feature = "write")]
768pub mod options {
770 use alloc::string::String;
771 use alloc::vec::Vec;
772 use core::num::NonZeroU16;
773
774 use super::{EmulationType, PlatformId};
775
776 #[derive(Debug, Clone, Default)]
777 pub struct BootOptions {
779 pub write_boot_catalog: bool,
781 pub default: BootEntryOptions,
783 pub entries: Vec<(BootSectionOptions, BootEntryOptions)>,
785 }
786
787 impl BootOptions {
788 pub fn sections(&self) -> Vec<(Option<BootSectionOptions>, BootEntryOptions)> {
790 let mut sections = Vec::with_capacity(self.entries.len() + 1);
791 sections.push((None, self.default.clone()));
792 for (section_ops, ops) in &self.entries {
793 sections.push((Some(section_ops.clone()), ops.clone()));
794 }
795 sections
796 }
797 }
798
799 #[derive(Debug, Clone)]
800 pub struct BootSectionOptions {
802 pub platform: PlatformId,
804 }
805
806 #[derive(Debug, Clone)]
807 pub struct BootEntryOptions {
809 pub load_size: Option<NonZeroU16>,
811 pub boot_image_path: String,
817 pub boot_info_table: bool,
819 pub grub2_boot_info: bool,
821 pub emulation: EmulationType,
823 }
824
825 impl Default for BootEntryOptions {
826 fn default() -> Self {
827 Self {
828 load_size: None,
829 boot_image_path: String::new(),
830 boot_info_table: false,
831 grub2_boot_info: false,
832 emulation: EmulationType::NoEmulation,
833 }
834 }
835 }
836}
837
838sync_only! {
839#[cfg(all(test, feature = "alloc"))]
840mod tests {
841 use super::*;
842 use alloc::vec;
843 use alloc::vec::Vec;
844
845 static_assertions::assert_eq_size!(BootValidationEntry, [u8; 32]);
846 static_assertions::assert_eq_size!(BootSectionHeaderEntry, [u8; 32]);
847 static_assertions::assert_eq_size!(BootSectionEntry, [u8; 32]);
848
849 static_assertions::assert_eq_align!(BootValidationEntry, u8);
850 static_assertions::assert_eq_align!(BootSectionHeaderEntry, u8);
851 static_assertions::assert_eq_align!(BootSectionEntry, u8);
852
853 #[test]
854 fn test_validation_entry_new() {
855 let entry = BootValidationEntry::new();
856 assert_eq!(entry.header_id, 0x01);
857 assert_eq!(entry.key, [0x55, 0xAA]);
858 assert!(entry.is_valid());
859 assert!(entry.validate().is_ok());
860 }
861
862 #[test]
863 fn test_validation_entry_checksum() {
864 let entry = BootValidationEntry::new();
865 let bytes = bytemuck::bytes_of(&entry);
867 let mut sum = 0u16;
868 for i in (0..32).step_by(2) {
869 let value = u16::from_le_bytes([bytes[i], bytes[i + 1]]);
870 sum = sum.wrapping_add(value);
871 }
872 assert_eq!(sum, 0, "checksum should make total sum equal to 0");
873 }
874
875 #[test]
876 fn test_validation_entry_invalid_header() {
877 let mut entry = BootValidationEntry::new();
878 entry.header_id = 0x00;
879 assert!(!entry.is_valid());
880 assert_eq!(entry.validate(), Err(BootError::InvalidValidationHeader));
881 }
882
883 #[test]
884 fn test_validation_entry_invalid_key() {
885 let mut entry = BootValidationEntry::new();
886 entry.key = [0x00, 0x00];
887 assert!(!entry.is_valid());
888 assert_eq!(entry.validate(), Err(BootError::InvalidValidationKey));
889 }
890
891 #[test]
892 fn test_validation_entry_invalid_checksum() {
893 let mut entry = BootValidationEntry::new();
894 entry.checksum.set(0x1234);
895 assert!(!entry.is_valid());
896 assert_eq!(entry.validate(), Err(BootError::InvalidValidationChecksum));
897 }
898
899 #[test]
900 fn test_section_entry_new() {
901 let entry = BootSectionEntry::new(EmulationType::NoEmulation, 0x07C0, 4, 20);
902 assert!(entry.is_bootable());
903 assert!(entry.is_valid());
904 assert_eq!(entry.boot_indicator, 0x88);
905 assert_eq!(entry.boot_media_type, 0x00);
906 assert_eq!(entry.load_segment.get(), 0x07C0);
907 assert_eq!(entry.sector_count.get(), 4);
908 assert_eq!(entry.load_rba.get(), 20);
909 }
910
911 #[test]
912 fn test_section_entry_not_bootable() {
913 let mut entry = BootSectionEntry::new(EmulationType::NoEmulation, 0, 1, 20);
914 entry.boot_indicator = 0x00;
915 assert!(!entry.is_bootable());
916 }
917
918 #[test]
919 fn test_base_boot_catalog_size() {
920 let catalog = BaseBootCatalog::default();
921 let mut buf = Vec::new();
922 catalog.write(&mut buf).unwrap();
923 assert_eq!(
924 buf.len(),
925 64,
926 "base catalog should be 64 bytes (validation + default entry)"
927 );
928 }
929
930 #[test]
931 fn test_boot_catalog_size() {
932 let catalog = BootCatalog::default();
933 assert_eq!(catalog.size(), 96);
935 }
936
937 #[test]
938 fn test_boot_catalog_with_section() {
939 let mut catalog = BootCatalog::default();
940 catalog.add_section(
941 PlatformId::X80X86,
942 vec![BootSectionEntry::new(EmulationType::NoEmulation, 0, 1, 30)],
943 );
944 assert_eq!(catalog.size(), 160);
946 }
947
948 #[test]
949 fn test_boot_catalog_roundtrip() {
950 use std::io::Cursor;
951
952 let mut catalog = BootCatalog::default();
953 catalog.add_section(
954 PlatformId::X80X86,
955 vec![BootSectionEntry::new(EmulationType::NoEmulation, 0, 1, 30)],
956 );
957
958 let mut buf = Vec::new();
959 catalog.write(&mut buf).unwrap();
960
961 let mut cursor = Cursor::new(buf);
962 let parsed = BootCatalog::parse(&mut cursor).unwrap();
963
964 assert_eq!(parsed.sections.len(), 1);
965 assert_eq!(parsed.sections[0].1.len(), 1);
966 }
967
968 #[test]
969 fn test_platform_id_roundtrip() {
970 for id in [
971 PlatformId::X80X86,
972 PlatformId::PowerPC,
973 PlatformId::Macintosh,
974 PlatformId::UEFI,
975 ] {
976 let byte = id.to_u8();
977 let recovered = PlatformId::from_u8(byte);
978 assert_eq!(recovered.to_u8(), byte);
979 }
980 }
981
982 #[test]
983 fn test_emulation_type_roundtrip() {
984 for (byte, ty, emulated) in [
985 (0x00u8, EmulationType::NoEmulation, false),
986 (0x01, EmulationType::Floppy1_2, true),
987 (0x02, EmulationType::Floppy1_44, true),
988 (0x03, EmulationType::Floppy2_88, true),
989 (0x04, EmulationType::HardDisk, true),
990 (0x42, EmulationType::Unknown(0x42), true),
992 (0x20, EmulationType::Unknown(0x20), false),
995 ] {
996 assert_eq!(ty.to_u8(), byte, "{ty:?} to_u8");
997 assert_eq!(EmulationType::from_u8(byte), ty, "from_u8({byte:#x})");
998 assert_eq!(ty.is_emulated(), emulated, "{ty:?} is_emulated");
999 }
1000 }
1001}
1002}