1use super::ModuleError;
2use super::codec::{Reader, Writer};
3use super::crc::{crc32, extend};
4use super::strings::StringTable;
5
6pub const MAGIC: [u8; 8] = [0x89, b'I', b'W', b'M', 0x0D, 0x0A, 0x1A, 0x0A];
7
8pub const OPTIONAL: u32 = 1;
10
11pub const EXTENSIONS: u32 = 0x8000;
13
14const HEADER: usize = 32;
15const ENTRY: usize = 28;
16const HEADER_CRC: usize = 28;
17
18#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
19pub struct Version {
20 pub major: u16,
21 pub minor: u16,
22}
23
24impl Version {
25 pub const CURRENT: Self = Self { major: 1, minor: 2 };
27
28 pub const OLDEST_READABLE: Self = Self { major: 1, minor: 0 };
30
31 pub const fn reads(self, found: Self) -> bool {
34 found.major == self.major && found.minor >= self.minor
35 }
36}
37
38const _: () = assert!(
39 Version::OLDEST_READABLE.major == Version::CURRENT.major && Version::OLDEST_READABLE.minor == 0,
40 "the oldest readable version is the current major's first minor"
41);
42
43#[derive(Clone, Copy, Debug, PartialEq, Eq)]
45pub struct Section {
46 pub id: u32,
47 pub name: &'static str,
48}
49
50impl Section {
51 pub const STRINGS: Self = Self { id: 1, name: "STRINGS" };
52 pub const DIRECTORY: Self = Self { id: 2, name: "DIRECTORY" };
53 pub const OPTIONS: Self = Self { id: 3, name: "OPTIONS" };
54 pub const LAYOUT: Self = Self { id: 4, name: "LAYOUT" };
55 pub const LIR: Self = Self { id: 5, name: "LIR" };
56 pub const SQL: Self = Self { id: 6, name: "SQL" };
57 pub const BMS: Self = Self { id: 7, name: "BMS" };
58 pub const DEBUG: Self = Self { id: 8, name: "DEBUG" };
59
60 pub const ALL: [Self; 8] =
61 [Self::STRINGS, Self::DIRECTORY, Self::OPTIONS, Self::LAYOUT, Self::LIR, Self::SQL, Self::BMS, Self::DEBUG];
62
63 pub fn by_id(id: u32) -> Option<Self> {
64 Self::ALL.into_iter().find(|s| s.id == id)
65 }
66}
67
68#[derive(Clone, Copy, Debug, PartialEq, Eq)]
69pub struct SectionEntry {
70 pub id: u32,
71 pub flags: u32,
72 pub offset: u64,
73 pub length: u64,
74 pub crc: u32,
75}
76
77impl SectionEntry {
78 pub fn name(&self) -> Option<&'static str> {
79 Section::by_id(self.id).map(|s| s.name)
80 }
81
82 pub fn optional(&self) -> bool {
83 self.flags & OPTIONAL != 0
84 }
85}
86
87fn le<const N: usize>(bytes: &[u8], at: usize) -> Option<[u8; N]> {
88 bytes.get(at..)?.first_chunk().copied()
89}
90
91fn u16_at(bytes: &[u8], at: usize) -> Option<u16> {
92 le(bytes, at).map(u16::from_le_bytes)
93}
94
95fn u32_at(bytes: &[u8], at: usize) -> Option<u32> {
96 le(bytes, at).map(u32::from_le_bytes)
97}
98
99fn u64_at(bytes: &[u8], at: usize) -> Option<u64> {
100 le(bytes, at).map(u64::from_le_bytes)
101}
102
103fn assemble(version: Version, features: u32, sections: &[(u32, u32, &[u8])]) -> Vec<u8> {
105 let table_end = HEADER + sections.len() * ENTRY;
106 let file_len = table_end + sections.iter().map(|s| s.2.len()).sum::<usize>();
107 let mut out = Vec::with_capacity(file_len);
108 out.extend_from_slice(&MAGIC);
109 out.extend_from_slice(&version.major.to_le_bytes());
110 out.extend_from_slice(&version.minor.to_le_bytes());
111 out.extend_from_slice(&features.to_le_bytes());
112 out.extend_from_slice(&(sections.len() as u32).to_le_bytes());
113 out.extend_from_slice(&(file_len as u64).to_le_bytes());
114 out.extend_from_slice(&[0; 4]);
115 let mut offset = table_end as u64;
116 for &(id, flags, body) in sections {
117 out.extend_from_slice(&id.to_le_bytes());
118 out.extend_from_slice(&flags.to_le_bytes());
119 out.extend_from_slice(&offset.to_le_bytes());
120 out.extend_from_slice(&(body.len() as u64).to_le_bytes());
121 out.extend_from_slice(&crc32(body).to_le_bytes());
122 offset += body.len() as u64;
123 }
124 let crc = extend(crc32(&out[..HEADER_CRC]), &out[HEADER..]);
125 out[HEADER_CRC..HEADER].copy_from_slice(&crc.to_le_bytes());
126 for &(_, _, body) in sections {
127 out.extend_from_slice(body);
128 }
129 out
130}
131
132#[derive(Debug, Default)]
134pub struct ModuleWriter {
135 writer: Writer,
136 sections: Vec<(u32, u32, Vec<u8>)>,
137}
138
139impl ModuleWriter {
140 pub fn new() -> Self {
141 Self::default()
142 }
143
144 pub fn section(&mut self, section: Section, build: impl FnOnce(&mut Writer)) {
146 self.push(section.id, 0, build);
147 }
148
149 pub fn extension(&mut self, id: u32, build: impl FnOnce(&mut Writer)) {
151 assert!(id >= EXTENSIONS, "extension section {id:#x} is below {EXTENSIONS:#x}");
152 self.push(id, OPTIONAL, build);
153 }
154
155 fn push(&mut self, id: u32, flags: u32, build: impl FnOnce(&mut Writer)) {
156 let last = self.sections.last().map_or(Section::STRINGS.id, |s| s.0);
157 assert!(id > last, "section {id:#x} written after section {last:#x}");
158 build(&mut self.writer);
159 let body = self.writer.take();
160 self.sections.push((id, flags, body));
161 }
162
163 pub fn finish(self) -> Vec<u8> {
165 self.finish_as(Version::CURRENT, 0)
166 }
167
168 fn finish_as(self, version: Version, features: u32) -> Vec<u8> {
169 for required in &Section::ALL[1..] {
170 assert!(self.sections.iter().any(|s| s.0 == required.id), "section {} was not written", required.name);
171 }
172 let strings = self.writer.strings().encode();
173 let mut sections = vec![(Section::STRINGS.id, 0, strings.as_slice())];
174 sections.extend(self.sections.iter().map(|(id, flags, body)| (*id, *flags, body.as_slice())));
175 assemble(version, features, §ions)
176 }
177}
178
179#[derive(Clone, Debug)]
181pub struct Module<'a> {
182 bytes: &'a [u8],
183 version: Version,
184 sections: Vec<SectionEntry>,
185}
186
187impl<'a> Module<'a> {
188 pub fn read(bytes: &'a [u8]) -> Result<Self, ModuleError> {
190 let actual = bytes.len() as u64;
191 let shown = bytes.len().min(MAGIC.len());
192 if bytes[..shown] != MAGIC[..shown] {
193 return Err(ModuleError::NotAModule);
194 }
195 let truncated = ModuleError::Truncated { expected: HEADER as u64, actual };
196 let header = (
197 u16_at(bytes, 8),
198 u16_at(bytes, 10),
199 u32_at(bytes, 12),
200 u32_at(bytes, 16),
201 u64_at(bytes, 20),
202 u32_at(bytes, HEADER_CRC),
203 );
204 let (Some(major), Some(minor), Some(features), Some(count), Some(file_len), Some(stored)) = header else {
205 return Err(truncated);
206 };
207 let version = Version { major, minor };
208 if !Version::OLDEST_READABLE.reads(version) {
209 return Err(ModuleError::Version(version));
210 }
211 if actual < file_len {
212 return Err(ModuleError::Truncated { expected: file_len, actual });
213 }
214 if actual > file_len {
215 return Err(ModuleError::TrailingBytes { expected: file_len, actual });
216 }
217 let table_end = HEADER as u64 + u64::from(count) * ENTRY as u64;
218 let table = bytes.get(HEADER..table_end.min(actual) as usize).unwrap_or_default();
219 let computed = extend(crc32(&bytes[..HEADER_CRC]), table);
220 if computed != stored {
221 return Err(ModuleError::HeaderChecksum { computed, stored });
222 }
223 if features != 0 {
224 return Err(ModuleError::Feature(features));
225 }
226 let malformed = |offset, reason| ModuleError::Malformed { section: "section table", offset, reason };
227 if table_end > file_len {
228 return Err(malformed(
229 16,
230 format!("{count} sections need a table to byte {table_end}, past the end at {file_len}"),
231 ));
232 }
233
234 let mut sections: Vec<SectionEntry> = Vec::with_capacity(count as usize);
235 let mut next = table_end;
236 for at in (HEADER..table_end as usize).step_by(ENTRY) {
237 let fields = (
238 u32_at(bytes, at),
239 u32_at(bytes, at + 4),
240 u64_at(bytes, at + 8),
241 u64_at(bytes, at + 16),
242 u32_at(bytes, at + 24),
243 );
244 let (Some(id), Some(flags), Some(offset), Some(length), Some(crc)) = fields else {
245 return Err(truncated);
246 };
247 let entry = SectionEntry { id, flags, offset, length, crc };
248 if let Some(last) = sections.last()
249 && last.id >= id
250 {
251 return Err(malformed(at, format!("section {id:#x} follows section {:#x}", last.id)));
252 }
253 if offset != next {
254 return Err(malformed(at, format!("section {id:#x} begins at byte {offset}, not {next}")));
255 }
256 next = match offset.checked_add(length) {
257 Some(end) if end <= file_len => end,
258 _ => return Err(malformed(at, format!("section {id:#x} of {length} bytes runs past the end"))),
259 };
260 if flags & !OPTIONAL != 0 {
261 return Err(malformed(at, format!("section {id:#x} has flags {flags:#x}")));
262 }
263 match (Section::by_id(id), entry.optional()) {
264 (Some(known), true) => {
265 return Err(malformed(at, format!("required section {} is flagged optional", known.name)));
266 }
267 (None, false) => return Err(ModuleError::UnknownSection(id)),
268 _ => {}
269 }
270 sections.push(entry);
271 }
272 if next != file_len {
273 return Err(malformed(HEADER, format!("the sections end at byte {next}, before the end at {file_len}")));
274 }
275 if let Some(missing) = Section::ALL.iter().find(|s| !sections.iter().any(|e| e.id == s.id)) {
276 return Err(ModuleError::MissingSection(missing.id));
277 }
278 Ok(Self { bytes, version, sections })
279 }
280
281 pub fn version(&self) -> Version {
282 self.version
283 }
284
285 pub fn sections(&self) -> &[SectionEntry] {
287 &self.sections
288 }
289
290 pub fn body(&self, id: u32) -> Result<&'a [u8], ModuleError> {
292 let entry = self.sections.iter().find(|e| e.id == id).ok_or(ModuleError::MissingSection(id))?;
293 let body = usize::try_from(entry.offset)
294 .ok()
295 .zip(usize::try_from(entry.length).ok())
296 .and_then(|(start, len)| self.bytes.get(start..start.checked_add(len)?))
297 .ok_or(ModuleError::Truncated {
298 expected: entry.offset.saturating_add(entry.length),
299 actual: self.bytes.len() as u64,
300 })?;
301 let computed = crc32(body);
302 if computed != entry.crc {
303 return Err(ModuleError::SectionChecksum { id, computed, stored: entry.crc });
304 }
305 Ok(body)
306 }
307
308 pub fn strings(&self) -> Result<StringTable, ModuleError> {
309 StringTable::decode(self.body(Section::STRINGS.id)?)
310 }
311
312 pub fn reader<'r>(&self, section: Section, strings: &'r StringTable) -> Result<Reader<'r>, ModuleError>
313 where
314 'a: 'r,
315 {
316 Ok(Reader::new(section.name, self.body(section.id)?, strings))
317 }
318}
319
320#[cfg(test)]
321mod tests {
322 use super::*;
323 use crate::module::codec::{Decode, Encode};
324
325 const TABLE_END: usize = HEADER + 8 * ENTRY;
326
327 fn names(texts: &[&str]) -> Vec<String> {
328 texts.iter().map(|s| (*s).to_owned()).collect()
329 }
330
331 fn sample(ids: &[String]) -> Vec<u8> {
333 let mut w = ModuleWriter::new();
334 w.section(Section::DIRECTORY, |w| ids.to_vec().encode(w));
335 for section in &Section::ALL[2..] {
336 w.section(*section, |w| w.count(0));
337 }
338 w.finish()
339 }
340
341 fn plain() -> Vec<(u32, u32, &'static [u8])> {
343 Section::ALL.iter().map(|s| (s.id, 0, &[0u8][..])).collect()
344 }
345
346 fn read(bytes: &[u8]) -> Result<Vec<u32>, ModuleError> {
347 Module::read(bytes).map(|m| m.sections().iter().map(|e| e.id).collect())
348 }
349
350 fn reseal(bytes: &mut [u8]) {
351 let count = u32::from_le_bytes(bytes[16..20].try_into().unwrap()) as usize;
352 let end = (HEADER + count * ENTRY).min(bytes.len());
353 let crc = extend(crc32(&bytes[..HEADER_CRC]), &bytes[HEADER..end]);
354 bytes[HEADER_CRC..HEADER].copy_from_slice(&crc.to_le_bytes());
355 }
356
357 fn table_malformed(result: Result<Vec<u32>, ModuleError>) -> String {
358 match result {
359 Err(ModuleError::Malformed { section: "section table", reason, .. }) => reason,
360 other => panic!("{other:?}"),
361 }
362 }
363
364 #[test]
365 fn a_module_lists_its_sections_and_decodes_them() {
366 let ids = names(&["PAYROLL", "SUB", "PAYROLL"]);
367 let bytes = sample(&ids);
368 let module = Module::read(&bytes).unwrap();
369 assert_eq!(module.version(), Version::CURRENT);
370 let listed: Vec<_> = module.sections().iter().map(|e| (e.id, e.name(), e.optional())).collect();
371 let expected: Vec<_> = Section::ALL.iter().map(|s| (s.id, Some(s.name), false)).collect();
372 assert_eq!(listed, expected);
373 let strings = module.strings().unwrap();
374 assert_eq!(strings.iter().collect::<Vec<_>>(), ["PAYROLL", "SUB"]);
375 let mut r = module.reader(Section::DIRECTORY, &strings).unwrap();
376 assert_eq!(Vec::<String>::decode(&mut r), Ok(ids));
377 assert_eq!(r.finish(), Ok(()));
378 assert_eq!(module.body(Section::DEBUG.id), Ok(&[0u8][..]));
379 }
380
381 #[test]
382 fn the_header_and_table_have_the_documented_layout() {
383 let bytes = sample(&names(&["A"]));
384 assert_eq!(bytes[..8], [0x89, 0x49, 0x57, 0x4D, 0x0D, 0x0A, 0x1A, 0x0A]);
385 assert_eq!(bytes[8..20], [1, 0, 2, 0, 0, 0, 0, 0, 8, 0, 0, 0]);
386 assert_eq!(u64_at(&bytes, 20), Some(bytes.len() as u64));
387 assert_eq!(u32_at(&bytes, HEADER_CRC), Some(extend(crc32(&bytes[..28]), &bytes[32..TABLE_END])));
388 let strings_body = [1, 1, b'A'];
389 assert_eq!(u32_at(&bytes, 32), Some(1));
390 assert_eq!(u32_at(&bytes, 36), Some(0));
391 assert_eq!(u64_at(&bytes, 40), Some(TABLE_END as u64));
392 assert_eq!(u64_at(&bytes, 48), Some(3));
393 assert_eq!(u32_at(&bytes, 56), Some(crc32(&strings_body)));
394 assert_eq!(bytes[TABLE_END..TABLE_END + 3], strings_body);
395 assert_eq!(u64_at(&bytes, 32 + ENTRY + 8), Some(TABLE_END as u64 + 3));
396 assert_eq!(bytes.len(), TABLE_END + 3 + 2 + 6);
397 }
398
399 #[test]
400 fn reading_and_writing_again_gives_the_same_bytes() {
401 let bytes = sample(&names(&["MAIN", "", "SUB", "MAIN"]));
402 assert_eq!(sample(&names(&["MAIN", "", "SUB", "MAIN"])), bytes);
403 let module = Module::read(&bytes).unwrap();
404 let strings = module.strings().unwrap();
405 let mut r = module.reader(Section::DIRECTORY, &strings).unwrap();
406 let decoded = Vec::<String>::decode(&mut r).unwrap();
407 assert_eq!(sample(&decoded), bytes);
408 }
409
410 #[test]
411 fn every_strict_prefix_is_truncated() {
412 let bytes = sample(&names(&["PAYROLL"]));
413 for len in 0..bytes.len() {
414 assert!(matches!(read(&bytes[..len]), Err(ModuleError::Truncated { .. })), "{len}");
415 }
416 assert_eq!(read(&bytes[..100]), Err(ModuleError::Truncated { expected: bytes.len() as u64, actual: 100 }));
417 assert_eq!(read(&bytes[..20]), Err(ModuleError::Truncated { expected: 32, actual: 20 }));
418 }
419
420 #[test]
421 fn a_bad_magic_is_not_a_module() {
422 let bytes = sample(&names(&["A"]));
423 let mut changed = bytes.clone();
424 changed[0] = 0x09;
425 assert_eq!(read(&changed), Err(ModuleError::NotAModule));
426 let text_mode: Vec<u8> = [&bytes[..4], &bytes[5..]].concat();
427 assert_eq!(read(&text_mode), Err(ModuleError::NotAModule));
428 assert_eq!(read(b"IDENTIFICATION DIVISION."), Err(ModuleError::NotAModule));
429 assert_eq!(read(b"\x89IX"), Err(ModuleError::NotAModule));
430 assert_eq!(ModuleError::NotAModule.to_string(), "not an ironwork load module");
431 }
432
433 #[test]
434 fn a_longer_file_has_trailing_bytes() {
435 let mut bytes = sample(&names(&["A"]));
436 let len = bytes.len() as u64;
437 bytes.push(0);
438 assert_eq!(read(&bytes), Err(ModuleError::TrailingBytes { expected: len, actual: len + 1 }));
439 }
440
441 fn stamped(version: Version, features: u32, extra: impl FnOnce(&mut ModuleWriter)) -> Vec<u8> {
443 let mut w = ModuleWriter::new();
444 for section in &Section::ALL[1..] {
445 w.section(*section, |w| w.count(0));
446 }
447 extra(&mut w);
448 w.finish_as(version, features)
449 }
450
451 #[test]
452 fn a_minor_from_the_oldest_readable_is_read_and_an_older_one_or_another_major_refused() {
453 let current = Version { major: 1, minor: 0 };
454 assert_eq!(Module::read(&stamped(current, 0, |_| {})).unwrap().version(), current);
455 for version in [Version { major: 0, minor: 7 }, Version { major: 2, minor: 0 }] {
456 assert_eq!(read(&stamped(version, 0, |_| {})), Err(ModuleError::Version(version)));
457 }
458 assert_eq!(
459 ModuleError::Version(Version { major: 0, minor: 7 }).to_string(),
460 "load module format 0.7; this ironwork reads 1.x. Compile the source again"
461 );
462 assert_eq!(
463 ModuleError::Version(Version { major: 2, minor: 0 }).to_string(),
464 "load module format 2.0; this ironwork reads 1.x. Compile the source again"
465 );
466 let oldest = Version { major: 0, minor: 3 };
467 assert!(!oldest.reads(Version { major: 0, minor: 2 }));
468 assert!(oldest.reads(Version { major: 0, minor: 3 }) && oldest.reads(Version { major: 0, minor: 9 }));
469 assert!(!oldest.reads(Version { major: 1, minor: 3 }));
470 let frozen = Version { major: 1, minor: 0 };
471 assert!(frozen.reads(Version { major: 1, minor: 0 }) && frozen.reads(Version { major: 1, minor: 5 }));
472 assert!(!frozen.reads(Version { major: 2, minor: 0 }) && !frozen.reads(Version { major: 0, minor: 9 }));
473 }
474
475 #[test]
476 fn a_newer_minor_is_read_past_an_unknown_optional_section_and_refused_for_a_required_one_or_a_feature() {
477 let newer = Version { major: 1, minor: Version::CURRENT.minor + 1 };
478 let optional = stamped(newer, 0, |w| w.push(9, OPTIONAL, |w| w.string("NEXT")));
479 let module = Module::read(&optional).unwrap();
480 assert_eq!(module.version(), newer);
481 let last = module.sections()[8];
482 assert_eq!((last.id, last.name(), last.optional()), (9, None, true));
483 assert_eq!(module.strings().unwrap().get(0), Some("NEXT"));
484 assert!(crate::module::read(&optional).unwrap().programs.is_empty());
485 let extension = stamped(newer, 0, |w| w.extension(EXTENSIONS, |w| w.count(1)));
486 assert_eq!(read(&extension), Ok(vec![1, 2, 3, 4, 5, 6, 7, 8, EXTENSIONS]));
487
488 let required = stamped(newer, 0, |w| w.section(Section { id: 9, name: "NEXT" }, |w| w.count(0)));
489 assert_eq!(read(&required), Err(ModuleError::UnknownSection(9)));
490 assert_eq!(read(&stamped(newer, 4, |_| {})), Err(ModuleError::Feature(4)));
491 }
492
493 #[test]
494 fn a_changed_header_or_table_byte_is_a_bad_header_checksum() {
495 let bytes = sample(&names(&["A"]));
496 for at in (12..20).chain(HEADER_CRC..TABLE_END) {
497 let mut changed = bytes.clone();
498 changed[at] ^= 0x10;
499 assert!(matches!(read(&changed), Err(ModuleError::HeaderChecksum { .. })), "byte {at}");
500 }
501 let mut changed = bytes.clone();
502 changed[20] ^= 1;
503 assert!(matches!(read(&changed), Err(ModuleError::Truncated { .. } | ModuleError::TrailingBytes { .. })));
504 }
505
506 #[test]
507 fn a_set_feature_bit_is_refused_by_name() {
508 assert_eq!(read(&assemble(Version::CURRENT, 4, &plain())), Err(ModuleError::Feature(4)));
509 assert_eq!(
510 ModuleError::Feature(4).to_string(),
511 "load module needs features 0x00000004, which this ironwork lacks"
512 );
513 }
514
515 #[test]
516 fn a_changed_section_byte_is_found_when_the_section_is_read() {
517 let bytes = sample(&names(&["A"]));
518 let module = Module::read(&bytes).unwrap();
519 let layout = module.sections()[3];
520 let mut changed = bytes.clone();
521 changed[layout.offset as usize] ^= 0x01;
522 let module = Module::read(&changed).unwrap();
523 let err = module.body(Section::LAYOUT.id).unwrap_err();
524 let computed = crc32(&[1]);
525 assert_eq!(err, ModuleError::SectionChecksum { id: 4, computed, stored: layout.crc });
526 assert_eq!(
527 err.to_string(),
528 format!("section LAYOUT is corrupt (checksum {computed:08X}, expected {:08X})", layout.crc)
529 );
530 assert!(module.body(Section::LIR.id).is_ok());
531 assert!(module.strings().is_ok());
532 }
533
534 #[test]
535 fn an_unknown_section_is_skipped_only_if_optional() {
536 let mut sections = plain();
537 sections.push((9, 0, &[1, 2]));
538 assert_eq!(read(&assemble(Version::CURRENT, 0, §ions)), Err(ModuleError::UnknownSection(9)));
539 assert_eq!(ModuleError::UnknownSection(9).to_string(), "required section 0x9 is unknown to this ironwork");
540
541 let mut sections = plain();
542 sections.push((9, OPTIONAL, &[1, 2]));
543 sections.push((EXTENSIONS + 1, OPTIONAL, &[3]));
544 let bytes = assemble(Version::CURRENT, 0, §ions);
545 assert_eq!(read(&bytes), Ok(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, EXTENSIONS + 1]));
546 let module = Module::read(&bytes).unwrap();
547 assert_eq!(module.sections()[8].name(), None);
548 assert_eq!(module.body(9), Ok(&[1u8, 2][..]));
549
550 let mut w = ModuleWriter::new();
551 for section in &Section::ALL[1..] {
552 w.section(*section, |w| w.count(0));
553 }
554 w.extension(EXTENSIONS, |w| w.string("NOTE"));
555 let module_bytes = w.finish();
556 let module = Module::read(&module_bytes).unwrap();
557 assert!(module.sections()[8].optional());
558 assert_eq!(module.strings().unwrap().get(0), Some("NOTE"));
559 }
560
561 #[test]
562 fn flags_must_match_what_the_reader_knows() {
563 let mut sections = plain();
564 sections[3].1 = OPTIONAL;
565 assert_eq!(
566 table_malformed(read(&assemble(Version::CURRENT, 0, §ions))),
567 "required section LAYOUT is flagged optional"
568 );
569 let mut sections = plain();
570 sections[2].1 = 2;
571 assert_eq!(table_malformed(read(&assemble(Version::CURRENT, 0, §ions))), "section 0x3 has flags 0x2");
572 }
573
574 #[test]
575 fn a_missing_required_section_is_refused() {
576 let sections = &plain()[..7];
577 assert_eq!(read(&assemble(Version::CURRENT, 0, sections)), Err(ModuleError::MissingSection(8)));
578 assert_eq!(ModuleError::MissingSection(8).to_string(), "required section DEBUG is missing");
579 assert_eq!(read(&assemble(Version::CURRENT, 0, &[])), Err(ModuleError::MissingSection(1)));
580 let bytes = sample(&[]);
581 assert_eq!(Module::read(&bytes).unwrap().body(9), Err(ModuleError::MissingSection(9)));
582 }
583
584 #[test]
585 fn only_the_canonical_layout_is_read() {
586 let mut sections = plain();
587 sections.swap(4, 5);
588 assert_eq!(table_malformed(read(&assemble(Version::CURRENT, 0, §ions))), "section 0x5 follows section 0x6");
589 let mut sections = plain();
590 sections.insert(4, (4, 0, &[0]));
591 assert_eq!(table_malformed(read(&assemble(Version::CURRENT, 0, §ions))), "section 0x4 follows section 0x4");
592
593 let bytes = assemble(Version::CURRENT, 0, &plain());
594 let mut gap = bytes.clone();
595 gap[32 + ENTRY + 8] += 1;
596 reseal(&mut gap);
597 assert_eq!(
598 table_malformed(read(&gap)),
599 format!("section 0x2 begins at byte {}, not {}", TABLE_END + 2, TABLE_END + 1)
600 );
601 let mut long = bytes.clone();
602 long[32 + 7 * ENTRY + 16] += 1;
603 reseal(&mut long);
604 assert_eq!(table_malformed(read(&long)), "section 0x8 of 2 bytes runs past the end");
605 let mut short = bytes.clone();
606 short[32 + 7 * ENTRY + 16] -= 1;
607 reseal(&mut short);
608 assert_eq!(
609 table_malformed(read(&short)),
610 format!("the sections end at byte {}, before the end at {}", TABLE_END + 7, TABLE_END + 8)
611 );
612 let mut many = bytes;
613 many[16] = 200;
614 reseal(&mut many);
615 assert_eq!(
616 table_malformed(read(&many)),
617 format!("200 sections need a table to byte {}, past the end at {}", HEADER + 200 * ENTRY, TABLE_END + 8)
618 );
619 }
620
621 #[test]
622 #[should_panic(expected = "section 0x3 written after section 0x4")]
623 fn the_writer_takes_sections_in_id_order() {
624 let mut w = ModuleWriter::new();
625 w.section(Section::LAYOUT, |_| {});
626 w.section(Section::OPTIONS, |_| {});
627 }
628
629 #[test]
630 #[should_panic(expected = "section SQL was not written")]
631 fn the_writer_writes_every_required_section() {
632 let mut w = ModuleWriter::new();
633 for section in
634 [Section::DIRECTORY, Section::OPTIONS, Section::LAYOUT, Section::LIR, Section::BMS, Section::DEBUG]
635 {
636 w.section(section, |w| w.count(0));
637 }
638 w.finish();
639 }
640}