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