1pub const RESERVED_ENTRIES: u32 = 2;
7
8#[derive(Debug, Copy, Clone, PartialEq, Eq)]
10pub enum FatType {
11 Fat16,
13 Fat32,
15}
16
17mod bpb;
18mod info;
19mod ondiskdirentry;
20mod volume;
21
22pub use bpb::Bpb;
23pub use info::{Fat16Info, Fat32Info, FatSpecificInfo, InfoSector};
24pub use ondiskdirentry::OnDiskDirEntry;
25pub use volume::{FatVolume, VolumeName, parse_volume};
26
27#[cfg(test)]
34mod test {
35
36 use hex_literal::hex;
37
38 use super::*;
39 use crate::{Attributes, BlockIdx, ClusterId, DirEntry, ShortFileName, Timestamp};
40
41 fn parse(input: &str) -> Vec<u8> {
42 let mut output = Vec::new();
43 for line in input.lines() {
44 let line = line.trim();
45 if !line.is_empty() {
46 for index in 0..32 {
48 let start = index * 2;
49 let end = start + 1;
50 let piece = &line[start..=end];
51 let value = u8::from_str_radix(piece, 16).unwrap();
52 output.push(value);
53 }
54 }
55 }
56 output
57 }
58
59 #[test]
86 fn test_dir_entries() {
87 #[derive(Debug)]
88 enum Expected {
89 Lfn(bool, u8, u8, [u16; 13]),
90 Short(DirEntry),
91 }
92 let raw_data = r#"
93 626f6f7420202020202020080000699c754775470000699c7547000000000000 boot ...i.uGuG..i.uG......
94 416f007600650072006c000f00476100790073000000ffffffff0000ffffffff Ao.v.e.r.l...Ga.y.s.............
95 4f5645524c4159532020201000001b9f6148614800001b9f6148030000000000 OVERLAYS .....aHaH....aH......
96 422d0070006c00750073000f00792e006400740062000000ffff0000ffffffff B-.p.l.u.s...y..d.t.b...........
97 01620063006d00320037000f0079300038002d0072007000690000002d006200 .b.c.m.2.7...y0.8.-.r.p.i...-.b.
98 42434d3237307e31445442200064119f614861480000119f61480900702b0000 BCM270~1DTB .d..aHaH....aH..p+..
99 4143004f005000590049000f00124e0047002e006c0069006e00000075007800 AC.O.P.Y.I....N.G...l.i.n...u.x.
100 434f5059494e7e314c494e2000000f9f6148614800000f9f6148050005490000 COPYIN~1LIN ....aHaH....aH...I..
101 4263006f006d000000ffff0f0067ffffffffffffffffffffffff0000ffffffff Bc.o.m.......g..................
102 014c004900430045004e000f0067430045002e00620072006f00000061006400 .L.I.C.E.N...gC.E...b.r.o...a.d.
103 4c4943454e437e3142524f200000119f614861480000119f61480800d6050000 LICENC~1BRO ....aHaH....aH......
104 422d0062002e00640074000f001962000000ffffffffffffffff0000ffffffff B-.b...d.t....b.................
105 01620063006d00320037000f0019300039002d0072007000690000002d003200 .b.c.m.2.7....0.9.-.r.p.i...-.2.
106 42434d3237307e34445442200064129f614861480000129f61480f004c2f0000 BCM270~4DTB .d..aHaH....aH..L/..
107 422e0064007400620000000f0059ffffffffffffffffffffffff0000ffffffff B..d.t.b.....Y..................
108 01620063006d00320037000f0059300038002d0072007000690000002d006200 .b.c.m.2.7...Y0.8.-.r.p.i...-.b.
109 "#;
110
111 let results = [
112 Expected::Short(DirEntry {
113 name: unsafe {
114 VolumeName::create_from_str("boot")
115 .unwrap()
116 .to_short_filename()
117 },
118 mtime: Timestamp::from_calendar(2015, 11, 21, 19, 35, 18).unwrap(),
119 ctime: Timestamp::from_calendar(2015, 11, 21, 19, 35, 18).unwrap(),
120 attributes: Attributes::create_from_fat(Attributes::VOLUME),
121 cluster: ClusterId(0),
122 size: 0,
123 entry_block: BlockIdx(0),
124 entry_offset: 0,
125 }),
126 Expected::Lfn(
127 true,
128 1,
129 0x47,
130 [
131 'o' as u16, 'v' as u16, 'e' as u16, 'r' as u16, 'l' as u16, 'a' as u16,
132 'y' as u16, 's' as u16, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
133 ],
134 ),
135 Expected::Short(DirEntry {
136 name: ShortFileName::create_from_str("OVERLAYS").unwrap(),
137 mtime: Timestamp::from_calendar(2016, 3, 1, 19, 56, 54).unwrap(),
138 ctime: Timestamp::from_calendar(2016, 3, 1, 19, 56, 54).unwrap(),
139 attributes: Attributes::create_from_fat(Attributes::DIRECTORY),
140 cluster: ClusterId(3),
141 size: 0,
142 entry_block: BlockIdx(0),
143 entry_offset: 0,
144 }),
145 Expected::Lfn(
146 true,
147 2,
148 0x79,
149 [
150 '-' as u16, 'p' as u16, 'l' as u16, 'u' as u16, 's' as u16, '.' as u16,
151 'd' as u16, 't' as u16, 'b' as u16, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF,
152 ],
153 ),
154 Expected::Lfn(
155 false,
156 1,
157 0x79,
158 [
159 'b' as u16, 'c' as u16, 'm' as u16, '2' as u16, '7' as u16, '0' as u16,
160 '8' as u16, '-' as u16, 'r' as u16, 'p' as u16, 'i' as u16, '-' as u16,
161 'b' as u16,
162 ],
163 ),
164 Expected::Short(DirEntry {
165 name: ShortFileName::create_from_str("BCM270~1.DTB").unwrap(),
166 mtime: Timestamp::from_calendar(2016, 3, 1, 19, 56, 34).unwrap(),
167 ctime: Timestamp::from_calendar(2016, 3, 1, 19, 56, 34).unwrap(),
168 attributes: Attributes::create_from_fat(Attributes::ARCHIVE),
169 cluster: ClusterId(9),
170 size: 11120,
171 entry_block: BlockIdx(0),
172 entry_offset: 0,
173 }),
174 Expected::Lfn(
175 true,
176 1,
177 0x12,
178 [
179 'C' as u16, 'O' as u16, 'P' as u16, 'Y' as u16, 'I' as u16, 'N' as u16,
180 'G' as u16, '.' as u16, 'l' as u16, 'i' as u16, 'n' as u16, 'u' as u16,
181 'x' as u16,
182 ],
183 ),
184 Expected::Short(DirEntry {
185 name: ShortFileName::create_from_str("COPYIN~1.LIN").unwrap(),
186 mtime: Timestamp::from_calendar(2016, 3, 1, 19, 56, 30).unwrap(),
187 ctime: Timestamp::from_calendar(2016, 3, 1, 19, 56, 30).unwrap(),
188 attributes: Attributes::create_from_fat(Attributes::ARCHIVE),
189 cluster: ClusterId(5),
190 size: 18693,
191 entry_block: BlockIdx(0),
192 entry_offset: 0,
193 }),
194 Expected::Lfn(
195 true,
196 2,
197 0x67,
198 [
199 'c' as u16,
200 'o' as u16,
201 'm' as u16,
202 '\u{0}' as u16,
203 0xFFFF,
204 0xFFFF,
205 0xFFFF,
206 0xFFFF,
207 0xFFFF,
208 0xFFFF,
209 0xFFFF,
210 0xFFFF,
211 0xFFFF,
212 ],
213 ),
214 Expected::Lfn(
215 false,
216 1,
217 0x67,
218 [
219 'L' as u16, 'I' as u16, 'C' as u16, 'E' as u16, 'N' as u16, 'C' as u16,
220 'E' as u16, '.' as u16, 'b' as u16, 'r' as u16, 'o' as u16, 'a' as u16,
221 'd' as u16,
222 ],
223 ),
224 Expected::Short(DirEntry {
225 name: ShortFileName::create_from_str("LICENC~1.BRO").unwrap(),
226 mtime: Timestamp::from_calendar(2016, 3, 1, 19, 56, 34).unwrap(),
227 ctime: Timestamp::from_calendar(2016, 3, 1, 19, 56, 34).unwrap(),
228 attributes: Attributes::create_from_fat(Attributes::ARCHIVE),
229 cluster: ClusterId(8),
230 size: 1494,
231 entry_block: BlockIdx(0),
232 entry_offset: 0,
233 }),
234 Expected::Lfn(
235 true,
236 2,
237 0x19,
238 [
239 '-' as u16, 'b' as u16, '.' as u16, 'd' as u16, 't' as u16, 'b' as u16, 0x0000,
240 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
241 ],
242 ),
243 Expected::Lfn(
244 false,
245 1,
246 0x19,
247 [
248 'b' as u16, 'c' as u16, 'm' as u16, '2' as u16, '7' as u16, '0' as u16,
249 '9' as u16, '-' as u16, 'r' as u16, 'p' as u16, 'i' as u16, '-' as u16,
250 '2' as u16,
251 ],
252 ),
253 Expected::Short(DirEntry {
254 name: ShortFileName::create_from_str("BCM270~4.DTB").unwrap(),
255 mtime: Timestamp::from_calendar(2016, 3, 1, 19, 56, 36).unwrap(),
256 ctime: Timestamp::from_calendar(2016, 3, 1, 19, 56, 36).unwrap(),
257 attributes: Attributes::create_from_fat(Attributes::ARCHIVE),
258 cluster: ClusterId(15),
259 size: 12108,
260 entry_block: BlockIdx(0),
261 entry_offset: 0,
262 }),
263 Expected::Lfn(
264 true,
265 2,
266 0x59,
267 [
268 '.' as u16, 'd' as u16, 't' as u16, 'b' as u16, 0x0000, 0xFFFF, 0xFFFF, 0xFFFF,
269 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF, 0xFFFF,
270 ],
271 ),
272 Expected::Lfn(
273 false,
274 1,
275 0x59,
276 [
277 'b' as u16, 'c' as u16, 'm' as u16, '2' as u16, '7' as u16, '0' as u16,
278 '8' as u16, '-' as u16, 'r' as u16, 'p' as u16, 'i' as u16, '-' as u16,
279 'b' as u16,
280 ],
281 ),
282 ];
283
284 let data = parse(raw_data);
285 for (part, expected) in data.chunks(OnDiskDirEntry::LEN).zip(results.iter()) {
286 let on_disk_entry = OnDiskDirEntry::new(part);
287 match expected {
288 Expected::Lfn(start, index, csum, contents) if on_disk_entry.is_lfn() => {
289 let (calc_start, calc_index, calc_csum, calc_contents) =
290 on_disk_entry.lfn_contents().unwrap();
291 assert_eq!(*start, calc_start);
292 assert_eq!(*index, calc_index);
293 assert_eq!(*contents, calc_contents);
294 assert_eq!(*csum, calc_csum);
295 }
296 Expected::Short(expected_entry) if !on_disk_entry.is_lfn() => {
297 let parsed_entry = on_disk_entry.get_entry(FatType::Fat32, BlockIdx(0), 0);
298 assert_eq!(*expected_entry, parsed_entry);
299 }
300 _ => {
301 panic!(
302 "Bad dir entry, expected:\n{:#?}\nhad\n{:#?}",
303 expected, on_disk_entry
304 );
305 }
306 }
307 }
308 }
309
310 #[test]
311 fn test_bpb() {
312 const BPB_EXAMPLE: [u8; 512] = hex!(
314 "EB 3C 90 6D 6B 66 73 2E 66 61 74 00 02 10 01 00
315 02 00 02 00 00 F8 20 00 3F 00 FF 00 00 00 00 00
316 00 E0 01 00 80 01 29 BB B0 71 77 62 6F 6F 74 20
317 20 20 20 20 20 20 46 41 54 31 36 20 20 20 0E 1F
318 BE 5B 7C AC 22 C0 74 0B 56 B4 0E BB 07 00 CD 10
319 5E EB F0 32 E4 CD 16 CD 19 EB FE 54 68 69 73 20
320 69 73 20 6E 6F 74 20 61 20 62 6F 6F 74 61 62 6C
321 65 20 64 69 73 6B 2E 20 20 50 6C 65 61 73 65 20
322 69 6E 73 65 72 74 20 61 20 62 6F 6F 74 61 62 6C
323 65 20 66 6C 6F 70 70 79 20 61 6E 64 0D 0A 70 72
324 65 73 73 20 61 6E 79 20 6B 65 79 20 74 6F 20 74
325 72 79 20 61 67 61 69 6E 20 2E 2E 2E 20 0D 0A 00
326 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
327 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
328 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
329 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
330 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
331 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
332 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
333 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
334 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
335 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
336 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
337 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
338 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
339 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
340 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
341 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
342 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
343 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
344 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
345 00 00 00 00 00 00 00 00 00 00 00 00 00 00 55 AA"
346 );
347 let bpb = Bpb::create_from_bytes(&BPB_EXAMPLE).unwrap();
348 assert_eq!(bpb.footer(), Bpb::FOOTER_VALUE);
349 assert_eq!(bpb.oem_name(), b"mkfs.fat");
350 assert_eq!(bpb.bytes_per_block(), 512);
351 assert_eq!(bpb.blocks_per_cluster(), 16);
352 assert_eq!(bpb.reserved_block_count(), 1);
353 assert_eq!(bpb.num_fats(), 2);
354 assert_eq!(bpb.root_entries_count(), 512);
355 assert_eq!(bpb.total_blocks16(), 0);
356 assert_eq!(bpb.fat_size16(), 32);
357 assert_eq!(bpb.total_blocks32(), 122_880);
358 assert_eq!(bpb.footer(), 0xAA55);
359 assert_eq!(bpb.volume_label(), *b"boot ");
360 assert_eq!(bpb.fat_size(), 32);
361 assert_eq!(bpb.total_blocks(), 122_880);
362 assert_eq!(bpb.fat_type, FatType::Fat16);
363 }
364}
365
366