cat_dev/fsemul/
bsf.rs

1//! Code for creating/reading/managing BOOT1 System Files.
2//!
3//! `.bsf` files, or BOOT1 System Files as they're called in several places in
4//! the original cat-dev sources/documentations are the original files served
5//! by PCFS.
6
7use crate::errors::FSError;
8use bytes::{BufMut, Bytes, BytesMut};
9
10const MIN_FILE_SIZE: usize = 32_usize;
11const MAX_FILE_SIZE: usize = 1_048_576_usize;
12const BOOTOS_BOOT_BLOCK_MAGIC: u32 = 0xFD9B_5B7A_u32;
13
14/// A boot file system.
15#[derive(Clone, Debug, PartialEq, Eq)]
16pub struct BootSystemFile {
17	header: BootOSBootBlockHeader,
18	data_info: Vec<BootOSDataInfo>,
19}
20
21impl BootSystemFile {
22	#[must_use]
23	pub const fn header(&self) -> &BootOSBootBlockHeader {
24		&self.header
25	}
26	#[must_use]
27	pub const fn data_info(&self) -> &Vec<BootOSDataInfo> {
28		&self.data_info
29	}
30}
31
32impl Default for BootSystemFile {
33	fn default() -> Self {
34		Self {
35			header: BootOSBootBlockHeader {
36				platform_info_physical_address: 0x2000_8000,
37				package_info_physical_address: 0x2000_8800,
38				boot1_flags: 0x8000_0000,
39				sticky_register_bits: 0,
40			},
41			data_info: Vec::with_capacity(0),
42		}
43	}
44}
45
46impl From<BootSystemFile> for Bytes {
47	fn from(value: BootSystemFile) -> Self {
48		let mut buff = BytesMut::with_capacity(32 + (value.data_info.len() * 8));
49
50		buff.put_u32(BOOTOS_BOOT_BLOCK_MAGIC);
51		// Can't have more infos than this without extending file size.
52		let actual_len = std::cmp::min(
53			u32::try_from(value.data_info().len()).unwrap_or(131_068),
54			131_068,
55		);
56		buff.put_u32(actual_len);
57		buff.put_u32(value.header().platform_info_physical_address());
58		buff.put_u32(value.header().package_info_physical_address());
59		buff.put_u32(value.header().boot1_flags());
60		buff.put_u32(value.header().sticky_register_bits());
61		// Padding Bytes
62		buff.put_u32(0x0);
63		buff.put_u32(0x0);
64		for idx in 0..usize::try_from(actual_len).unwrap_or(usize::MAX) {
65			buff.put_u32(value.data_info[idx].address());
66			buff.put_u32(value.data_info[idx].length());
67		}
68
69		buff.freeze()
70	}
71}
72
73impl TryFrom<Bytes> for BootSystemFile {
74	type Error = FSError;
75
76	fn try_from(value: Bytes) -> Result<Self, Self::Error> {
77		if value.len() < 32 {
78			return Err(FSError::TooSmall(MIN_FILE_SIZE, value.len()));
79		}
80		if value.len() > MAX_FILE_SIZE {
81			return Err(FSError::TooLarge(MAX_FILE_SIZE, value.len()));
82		}
83
84		let actual_magic = u32::from_be_bytes([value[0], value[1], value[2], value[3]]);
85		if actual_magic != BOOTOS_BOOT_BLOCK_MAGIC {
86			return Err(FSError::InvalidFileMagic(
87				BOOTOS_BOOT_BLOCK_MAGIC,
88				actual_magic,
89			));
90		}
91
92		let data_info_count = u32::from_be_bytes([value[4], value[5], value[6], value[7]]);
93		let expected_file_size =
94			32_usize + (usize::try_from(data_info_count).unwrap_or(usize::MAX) * 8_usize);
95		if value.len() != expected_file_size {
96			return Err(FSError::InvalidFileSize(expected_file_size, value.len()));
97		}
98
99		let platform_info_addr = u32::from_be_bytes([value[8], value[9], value[10], value[11]]);
100		let package_info_addr = u32::from_be_bytes([value[12], value[13], value[14], value[15]]);
101		let boot1_flags = u32::from_be_bytes([value[16], value[17], value[18], value[19]]);
102		let sticky_register_bits = u32::from_be_bytes([value[20], value[21], value[22], value[23]]);
103
104		// There are two u32's here that are just used for padding here.
105		// Then we get into the data info blocks.
106		let mut index = 32_usize;
107		let mut data_infos =
108			Vec::with_capacity(usize::try_from(data_info_count).unwrap_or(usize::MAX));
109		for _ in 0..data_info_count {
110			let address = u32::from_be_bytes([
111				value[index],
112				value[index + 1],
113				value[index + 2],
114				value[index + 3],
115			]);
116			let length = u32::from_be_bytes([
117				value[index + 4],
118				value[index + 5],
119				value[index + 6],
120				value[index + 7],
121			]);
122
123			data_infos.push(BootOSDataInfo { address, length });
124			index += 8;
125		}
126
127		Ok(Self {
128			header: BootOSBootBlockHeader {
129				platform_info_physical_address: platform_info_addr,
130				package_info_physical_address: package_info_addr,
131				boot1_flags,
132				sticky_register_bits,
133			},
134			data_info: data_infos,
135		})
136	}
137}
138
139#[derive(Clone, Debug, PartialEq, Eq)]
140pub struct BootOSBootBlockHeader {
141	platform_info_physical_address: u32,
142	package_info_physical_address: u32,
143	boot1_flags: u32,
144	sticky_register_bits: u32,
145}
146impl BootOSBootBlockHeader {
147	#[must_use]
148	pub const fn platform_info_physical_address(&self) -> u32 {
149		self.platform_info_physical_address
150	}
151	#[must_use]
152	pub const fn package_info_physical_address(&self) -> u32 {
153		self.package_info_physical_address
154	}
155	#[must_use]
156	pub const fn boot1_flags(&self) -> u32 {
157		self.boot1_flags
158	}
159	#[must_use]
160	pub const fn sticky_register_bits(&self) -> u32 {
161		self.sticky_register_bits
162	}
163}
164
165#[derive(Clone, Debug, PartialEq, Eq)]
166pub struct BootOSDataInfo {
167	address: u32,
168	length: u32,
169}
170impl BootOSDataInfo {
171	#[must_use]
172	pub const fn address(&self) -> u32 {
173		self.address
174	}
175	#[must_use]
176	pub const fn length(&self) -> u32 {
177		self.length
178	}
179}
180
181#[cfg(test)]
182mod unit_tests {
183	use super::*;
184	use std::path::PathBuf;
185
186	#[test]
187	pub fn can_parse_boot_system_file() {
188		let mut test_data_dir = PathBuf::from(
189			std::env::var("CARGO_MANIFEST_DIR")
190				.expect("Failed to read `CARGO_MANIFEST_DIR` to locate test files!"),
191		);
192		test_data_dir.push("src");
193		test_data_dir.push("fsemul");
194		test_data_dir.push("test-data");
195
196		{
197			// Real life bsf that came from a caferun directory.
198			let mut bsf_file = test_data_dir.clone();
199			bsf_file.push("ppc.bsf");
200			let file_contents =
201				std::fs::read(&bsf_file).expect("Failed to read real life bsf file!");
202			let file = BootSystemFile::try_from(Bytes::from(file_contents))
203				.expect("Failed to parse real life ppc.bsf");
204
205			assert!(
206				file.data_info().is_empty(),
207				"Expected an empty data info list for Boot System File."
208			);
209
210			assert_eq!(
211				file.header().platform_info_physical_address(),
212				0x20008000,
213				"Invalid Platform Info Physical Address from Boot System File.",
214			);
215			assert_eq!(
216				file.header().package_info_physical_address(),
217				0x20008800,
218				"Invalid Package Info Physical Address from Boot System File.",
219			);
220			assert_eq!(
221				file.header().boot1_flags(),
222				0x80000000,
223				"Invalid Boot1 Flags from Boot System File.",
224			);
225			assert_eq!(
226				file.header().sticky_register_bits(),
227				0,
228				"Invalid Sticky Register Bits for Boot System File."
229			);
230		}
231	}
232}