1use crc::{CRC_32_ISO_HDLC, Crc};
41
42pub const VERSION: u8 = 2;
47
48const HEADER_LEN: usize = 8;
50
51const TRAILER_LEN: usize = 4;
53
54const MIN_LEN: usize = HEADER_LEN + TRAILER_LEN;
56
57static CRC32: Crc<u32> = Crc::<u32>::new(&CRC_32_ISO_HDLC);
63
64pub fn checksum(data: &[u8]) -> u32 {
71 CRC32.checksum(data)
72}
73
74pub struct Checksum(crc::Digest<'static, u32>);
81
82impl Checksum {
83 pub fn new() -> Checksum {
85 Checksum(CRC32.digest())
86 }
87
88 pub fn update(&mut self, data: &[u8]) {
90 self.0.update(data);
91 }
92
93 pub fn finish(self) -> u32 {
96 self.0.finalize()
97 }
98}
99
100impl Default for Checksum {
101 fn default() -> Checksum {
102 Checksum::new()
103 }
104}
105
106impl core::fmt::Debug for Checksum {
107 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
108 f.write_str("Checksum(..)")
111 }
112}
113
114#[derive(Clone, Copy, Debug, PartialEq, Eq)]
118pub struct Format {
119 pub magic: [u8; 4],
129 pub max_entries: usize,
134}
135
136#[derive(Clone, Copy, Debug, PartialEq, Eq)]
141pub enum Role {
142 File,
144 Kernel,
147 Firmware,
150 Config,
153}
154
155impl Role {
156 pub fn as_byte(self) -> u8 {
158 match self {
159 Role::File => 0,
160 Role::Kernel => 1,
161 Role::Firmware => 2,
162 Role::Config => 3,
163 }
164 }
165
166 pub fn from_byte(byte: u8) -> Option<Role> {
168 match byte {
169 0 => Some(Role::File),
170 1 => Some(Role::Kernel),
171 2 => Some(Role::Firmware),
172 3 => Some(Role::Config),
173 _ => None,
174 }
175 }
176}
177
178#[derive(Clone, Copy, Debug, PartialEq, Eq)]
180pub struct Entry<'a> {
181 pub role: Role,
183 pub path: &'a str,
186 pub data: &'a [u8],
188}
189
190#[derive(Clone, Copy, Debug, PartialEq, Eq)]
197pub enum Error {
198 TooShort,
200 BadMagic,
203 BadVersion(u8),
206 BadChecksum,
208 BadEntryCount(usize),
210 Truncated,
213 BadRole(u8),
216 PathNotUtf8,
219 PathTooLong,
222 DataTooLarge,
224 BadPath,
227 DuplicatePath,
229 DuplicateRole(Role),
231 UnpairedFirmware,
233}
234
235impl Error {
236 pub fn http_status(&self) -> u16 {
245 400
246 }
247}
248
249impl core::fmt::Display for Error {
250 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
251 let text = match self {
252 Error::TooShort => "bundle too short",
253 Error::BadMagic => "not a bundle for this device",
254 Error::BadVersion(_) => "unsupported bundle version",
255 Error::BadChecksum => "checksum mismatch",
256 Error::BadEntryCount(_) => "bad entry count",
257 Error::Truncated => "bundle truncated",
258 Error::BadRole(_) => "unknown entry role",
259 Error::PathNotUtf8 => "bad file path",
260 Error::PathTooLong => "file path too long",
261 Error::DataTooLarge => "file too large",
262 Error::BadPath => "unsafe file path",
263 Error::DuplicatePath => "two entries with the same path",
264 Error::DuplicateRole(_) => "two entries with the same role",
265 Error::UnpairedFirmware => "firmware file without its pair",
266 };
267 f.write_str(text)
268 }
269}
270
271impl core::error::Error for Error {}
272
273#[derive(Clone, Copy, Debug)]
278pub struct Bundle<'a> {
279 entries: &'a [u8],
281 count: usize,
282}
283
284impl<'a> Bundle<'a> {
285 pub fn parse(format: &Format, bytes: &'a [u8]) -> Result<Bundle<'a>, Error> {
296 if bytes.len() < MIN_LEN {
297 return Err(Error::TooShort);
298 }
299 if bytes[..4] != format.magic {
300 return Err(Error::BadMagic);
301 }
302 if bytes[4] != VERSION {
303 return Err(Error::BadVersion(bytes[4]));
304 }
305
306 let split = bytes.len() - TRAILER_LEN;
307 let stored = u32::from_le_bytes([
308 bytes[split],
309 bytes[split + 1],
310 bytes[split + 2],
311 bytes[split + 3],
312 ]);
313 if checksum(&bytes[..split]) != stored {
314 return Err(Error::BadChecksum);
315 }
316
317 let count = u16::from_le_bytes([bytes[6], bytes[7]]) as usize;
318 if count == 0 || count > format.max_entries {
319 return Err(Error::BadEntryCount(count));
320 }
321
322 let entries = &bytes[HEADER_LEN..split];
327 let mut rest = entries;
328 for _ in 0..count {
329 let (_, next) = split_entry(rest)?;
330 rest = next;
331 }
332 if !rest.is_empty() {
335 return Err(Error::Truncated);
336 }
337
338 let bundle = Bundle { entries, count };
344 check_entries(bundle.iter())?;
345 Ok(bundle)
346 }
347
348 pub fn count(&self) -> usize {
350 self.count
351 }
352
353 pub fn iter(&self) -> Iter<'a> {
357 Iter {
358 rest: self.entries,
359 left: self.count,
360 }
361 }
362
363 pub fn kernel(&self) -> Option<Entry<'a>> {
368 self.iter().find(|entry| entry.role == Role::Kernel)
369 }
370}
371
372fn check_entries<'a, I>(entries: I) -> Result<(), Error>
384where
385 I: Iterator<Item = Entry<'a>> + Clone,
386{
387 for entry in entries.clone() {
388 check_path(entry.path)?;
389 }
390 check_unique_paths(entries.clone())?;
391 check_single(entries.clone(), Role::Kernel)?;
392 check_single(entries.clone(), Role::Config)?;
393 check_firmware_pairs(entries)
394}
395
396fn check_unique_paths<'a, I>(entries: I) -> Result<(), Error>
402where
403 I: Iterator<Item = Entry<'a>> + Clone,
404{
405 for (index, entry) in entries.clone().enumerate() {
406 if entries
407 .clone()
408 .skip(index + 1)
409 .any(|other| other.path == entry.path)
410 {
411 return Err(Error::DuplicatePath);
412 }
413 }
414 Ok(())
415}
416
417fn check_single<'a, I>(entries: I, role: Role) -> Result<(), Error>
419where
420 I: Iterator<Item = Entry<'a>>,
421{
422 if entries.filter(|entry| entry.role == role).count() > 1 {
423 return Err(Error::DuplicateRole(role));
424 }
425 Ok(())
426}
427
428fn check_firmware_pairs<'a, I>(entries: I) -> Result<(), Error>
440where
441 I: Iterator<Item = Entry<'a>> + Clone,
442{
443 for entry in entries.clone().filter(|entry| entry.role == Role::Firmware) {
444 let name = basename(entry.path);
445 let paired = if let Some(suffix) = strip_around(name, "start", ".elf") {
446 has_firmware(entries.clone(), "fixup", suffix, ".dat")
447 } else if let Some(suffix) = strip_around(name, "fixup", ".dat") {
448 has_firmware(entries.clone(), "start", suffix, ".elf")
449 } else {
450 true
452 };
453 if !paired {
454 return Err(Error::UnpairedFirmware);
455 }
456 }
457 Ok(())
458}
459
460fn has_firmware<'a, I>(entries: I, prefix: &str, middle: &str, extension: &str) -> bool
463where
464 I: Iterator<Item = Entry<'a>>,
465{
466 entries
467 .filter(|entry| entry.role == Role::Firmware)
468 .any(|entry| {
469 strip_around(basename(entry.path), prefix, extension)
470 .is_some_and(|found| found.eq_ignore_ascii_case(middle))
471 })
472}
473
474#[cfg(feature = "alloc")]
488pub fn encode(format: &Format, entries: &[Entry<'_>]) -> Result<alloc::vec::Vec<u8>, Error> {
489 if entries.is_empty() || entries.len() > format.max_entries || entries.len() > u16::MAX as usize
493 {
494 return Err(Error::BadEntryCount(entries.len()));
495 }
496 check_entries(entries.iter().copied())?;
497
498 let mut size = HEADER_LEN + TRAILER_LEN;
499 for entry in entries {
500 if entry.path.len() > u8::MAX as usize {
501 return Err(Error::PathTooLong);
502 }
503 if entry.data.len() as u64 > u32::MAX as u64 {
508 return Err(Error::DataTooLarge);
509 }
510 size += 1 + 1 + entry.path.len() + 4 + entry.data.len();
511 }
512
513 let mut out = alloc::vec::Vec::with_capacity(size);
517 out.extend_from_slice(&format.magic);
518 out.push(VERSION);
519 out.push(0);
520 out.extend_from_slice(&(entries.len() as u16).to_le_bytes());
521 for entry in entries {
522 out.push(entry.role.as_byte());
523 out.push(entry.path.len() as u8);
524 out.extend_from_slice(entry.path.as_bytes());
525 out.extend_from_slice(&(entry.data.len() as u32).to_le_bytes());
526 out.extend_from_slice(entry.data);
527 }
528 out.extend_from_slice(&checksum(&out).to_le_bytes());
529 Ok(out)
530}
531
532#[derive(Clone, Debug)]
534pub struct Iter<'a> {
535 rest: &'a [u8],
536 left: usize,
537}
538
539impl<'a> Iterator for Iter<'a> {
540 type Item = Entry<'a>;
541
542 fn next(&mut self) -> Option<Entry<'a>> {
543 if self.left == 0 {
544 return None;
545 }
546 let (entry, rest) = split_entry(self.rest).ok()?;
552 self.rest = rest;
553 self.left -= 1;
554 Some(entry)
555 }
556
557 fn size_hint(&self) -> (usize, Option<usize>) {
558 (0, Some(self.left))
560 }
561}
562
563fn split_entry(rest: &[u8]) -> Result<(Entry<'_>, &[u8]), Error> {
565 let (&role, rest) = rest.split_first().ok_or(Error::Truncated)?;
566 let role = Role::from_byte(role).ok_or(Error::BadRole(role))?;
567
568 let (&path_len, rest) = rest.split_first().ok_or(Error::Truncated)?;
569 let (path, rest) = rest
570 .split_at_checked(path_len as usize)
571 .ok_or(Error::Truncated)?;
572 let path = core::str::from_utf8(path).map_err(|_| Error::PathNotUtf8)?;
573
574 let (size, rest) = rest.split_at_checked(4).ok_or(Error::Truncated)?;
575 let size = u32::from_le_bytes([size[0], size[1], size[2], size[3]]) as usize;
576 let (data, rest) = rest.split_at_checked(size).ok_or(Error::Truncated)?;
577
578 Ok((Entry { role, path, data }, rest))
579}
580
581fn check_path(path: &str) -> Result<(), Error> {
588 if path.is_empty() {
589 return Err(Error::BadPath);
590 }
591 if path.starts_with('/') {
595 return Err(Error::BadPath);
596 }
597 if path.contains('\\') {
601 return Err(Error::BadPath);
602 }
603 for component in path.split('/') {
604 if component.is_empty() || component == "." || component == ".." {
605 return Err(Error::BadPath);
606 }
607 }
608 Ok(())
609}
610
611fn basename(path: &str) -> &str {
613 path.rsplit_once('/').map_or(path, |(_, name)| name)
614}
615
616fn strip_around<'a>(name: &'a str, prefix: &str, suffix: &str) -> Option<&'a str> {
619 if name.len() < prefix.len() + suffix.len() {
620 return None;
621 }
622 let (head, rest) = name.split_at_checked(prefix.len())?;
623 if !head.eq_ignore_ascii_case(prefix) {
624 return None;
625 }
626 let (middle, tail) = rest.split_at_checked(rest.len() - suffix.len())?;
627 if !tail.eq_ignore_ascii_case(suffix) {
628 return None;
629 }
630 Some(middle)
631}
632
633#[cfg(test)]
634mod tests {
635 use super::*;
636
637 const FORMAT: Format = Format {
638 magic: *b"TEST",
639 max_entries: 8,
640 };
641
642 struct Builder {
649 magic: [u8; 4],
650 version: u8,
651 count: Option<u16>,
652 entries: Vec<(u8, Vec<u8>, Vec<u8>)>,
653 trailing: Vec<u8>,
654 corrupt: bool,
655 }
656
657 impl Builder {
658 fn new() -> Self {
659 Builder {
660 magic: *b"TEST",
661 version: VERSION,
662 count: None,
663 entries: Vec::new(),
664 trailing: Vec::new(),
665 corrupt: false,
666 }
667 }
668
669 fn entry(mut self, role: u8, path: &str, data: &[u8]) -> Self {
670 self.entries
671 .push((role, path.as_bytes().to_vec(), data.to_vec()));
672 self
673 }
674
675 fn raw_path_entry(mut self, role: u8, path: &[u8], data: &[u8]) -> Self {
676 self.entries.push((role, path.to_vec(), data.to_vec()));
677 self
678 }
679
680 fn build(self) -> Vec<u8> {
681 let mut out = Vec::new();
682 out.extend_from_slice(&self.magic);
683 out.push(self.version);
684 out.push(0);
685 let count = self.count.unwrap_or(self.entries.len() as u16);
686 out.extend_from_slice(&count.to_le_bytes());
687 for (role, path, data) in &self.entries {
688 out.push(*role);
689 out.push(path.len() as u8);
690 out.extend_from_slice(path);
691 out.extend_from_slice(&(data.len() as u32).to_le_bytes());
692 out.extend_from_slice(data);
693 }
694 out.extend_from_slice(&self.trailing);
695 let crc = checksum(&out) ^ if self.corrupt { 1 } else { 0 };
696 out.extend_from_slice(&crc.to_le_bytes());
697 out
698 }
699 }
700
701 fn one_file() -> Vec<u8> {
702 Builder::new().entry(0, "WWW/INDEX.HTM", b"hello").build()
703 }
704
705 fn parse(bytes: &[u8]) -> Result<Bundle<'_>, Error> {
706 Bundle::parse(&FORMAT, bytes)
707 }
708
709 fn error(bytes: &[u8]) -> Error {
710 parse(bytes).expect_err("expected this bundle to be rejected")
711 }
712
713 #[test]
714 fn checksum_matches_the_standard_vector() {
715 assert_eq!(checksum(b"123456789"), 0xCBF4_3926);
719 }
720
721 #[test]
722 fn a_well_formed_bundle_parses() {
723 let bytes = Builder::new()
724 .entry(1, "KERNEL7.IMG", b"kernel bytes")
725 .entry(0, "WWW/INDEX.HTM", b"<html>")
726 .entry(3, "CONFIG.TXT", b"arm_64bit=0\n")
727 .build();
728 let bundle = parse(&bytes).unwrap();
729
730 assert_eq!(bundle.count(), 3);
731 let entries: Vec<_> = bundle.iter().collect();
732 assert_eq!(entries.len(), 3);
733 assert_eq!(entries[0].role, Role::Kernel);
734 assert_eq!(entries[0].path, "KERNEL7.IMG");
735 assert_eq!(entries[0].data, b"kernel bytes");
736 assert_eq!(entries[1].role, Role::File);
737 assert_eq!(entries[1].path, "WWW/INDEX.HTM");
738 assert_eq!(entries[2].role, Role::Config);
739 assert_eq!(bundle.kernel().unwrap().data, b"kernel bytes");
740 }
741
742 #[test]
743 fn a_bundle_with_no_kernel_is_valid() {
744 let bytes = Builder::new().entry(0, "WATER.CFG", b"zone=1").build();
745 let bundle = parse(&bytes).unwrap();
746 assert_eq!(bundle.count(), 1);
747 assert!(bundle.kernel().is_none());
748 }
749
750 #[test]
751 fn an_empty_file_is_valid() {
752 let bytes = Builder::new().entry(0, "EMPTY.TXT", b"").build();
753 assert_eq!(parse(&bytes).unwrap().iter().next().unwrap().data, b"");
754 }
755
756 #[test]
757 fn too_short() {
758 assert_eq!(error(&[]), Error::TooShort);
759 assert_eq!(error(&one_file()[..MIN_LEN - 1]), Error::TooShort);
760 }
761
762 #[test]
763 fn bad_magic() {
764 let mut bytes = one_file();
765 bytes[0] = b'X';
766 assert_eq!(error(&bytes), Error::BadMagic);
767 }
768
769 #[test]
770 fn bad_version() {
771 let mut builder = Builder::new().entry(0, "A.TXT", b"a");
774 builder.version = 1;
775 assert_eq!(error(&builder.build()), Error::BadVersion(1));
776 }
777
778 #[test]
779 fn bad_checksum() {
780 let mut bytes = one_file();
781 let last = bytes.len() - TRAILER_LEN - 1;
782 bytes[last] ^= 0xFF;
783 assert_eq!(error(&bytes), Error::BadChecksum);
784 }
785
786 #[test]
787 fn the_checksum_is_checked_before_the_entries() {
788 let mut builder = Builder::new().entry(0, "A.TXT", b"a");
792 builder.count = Some(0);
793 builder.corrupt = true;
794 assert_eq!(error(&builder.build()), Error::BadChecksum);
795 }
796
797 #[test]
798 fn bad_entry_count() {
799 let mut builder = Builder::new().entry(0, "A.TXT", b"a");
800 builder.count = Some(0);
801 assert_eq!(error(&builder.build()), Error::BadEntryCount(0));
802
803 let mut builder = Builder::new();
804 for index in 0..9 {
805 builder = builder.entry(0, &format!("F{index}.TXT"), b"x");
806 }
807 assert_eq!(error(&builder.build()), Error::BadEntryCount(9));
808 }
809
810 #[test]
811 fn an_entry_running_past_the_end_is_truncated() {
812 let mut builder = Builder::new().entry(0, "A.TXT", b"abc");
813 builder.count = Some(2);
814 assert_eq!(error(&builder.build()), Error::Truncated);
815 }
816
817 #[test]
818 fn slack_between_the_last_entry_and_the_checksum_is_truncated() {
819 let mut builder = Builder::new().entry(0, "A.TXT", b"abc");
820 builder.trailing = vec![0; 3];
821 assert_eq!(error(&builder.build()), Error::Truncated);
822 }
823
824 #[test]
825 fn bad_role() {
826 let bytes = Builder::new().entry(4, "A.TXT", b"a").build();
827 assert_eq!(error(&bytes), Error::BadRole(4));
828 }
829
830 #[test]
831 fn path_not_utf8() {
832 let bytes = Builder::new()
833 .raw_path_entry(0, &[0xFF, 0xFE], b"a")
834 .build();
835 assert_eq!(error(&bytes), Error::PathNotUtf8);
836 }
837
838 #[test]
839 fn unsafe_paths() {
840 for path in [
841 "",
842 "/ABS.TXT",
843 "../ESCAPE.TXT",
844 "WWW/../ESCAPE.TXT",
845 "./HERE.TXT",
846 "WWW//DOUBLE.TXT",
847 "TRAILING/",
848 "WWW\\INDEX.HTM",
849 ] {
850 let bytes = Builder::new().entry(0, path, b"x").build();
851 assert_eq!(error(&bytes), Error::BadPath, "path {path:?} was accepted");
852 }
853 }
854
855 #[test]
856 fn nested_paths_are_fine() {
857 let bytes = Builder::new().entry(0, "CERTS/ROOTS/CA.PEM", b"x").build();
858 assert_eq!(parse(&bytes).unwrap().count(), 1);
859 }
860
861 #[test]
862 fn duplicate_path() {
863 let bytes = Builder::new()
864 .entry(0, "WWW/INDEX.HTM", b"one")
865 .entry(0, "WWW/INDEX.HTM", b"two")
866 .build();
867 assert_eq!(error(&bytes), Error::DuplicatePath);
868 }
869
870 #[test]
871 fn two_kernels() {
872 let bytes = Builder::new()
873 .entry(1, "KERNEL7.IMG", b"a")
874 .entry(1, "KERNEL8.IMG", b"b")
875 .build();
876 assert_eq!(error(&bytes), Error::DuplicateRole(Role::Kernel));
877 }
878
879 #[test]
880 fn two_configs() {
881 let bytes = Builder::new()
882 .entry(3, "CONFIG.TXT", b"a")
883 .entry(3, "BOOT/CONFIG.TXT", b"b")
884 .build();
885 assert_eq!(error(&bytes), Error::DuplicateRole(Role::Config));
886 }
887
888 #[test]
889 fn a_start_without_its_fixup_is_unpaired() {
890 let bytes = Builder::new().entry(2, "START.ELF", b"a").build();
891 assert_eq!(error(&bytes), Error::UnpairedFirmware);
892 }
893
894 #[test]
895 fn a_fixup_without_its_start_is_unpaired() {
896 let bytes = Builder::new().entry(2, "FIXUP.DAT", b"a").build();
897 assert_eq!(error(&bytes), Error::UnpairedFirmware);
898 }
899
900 #[test]
901 fn a_pair_with_mismatched_suffixes_is_unpaired() {
902 let bytes = Builder::new()
903 .entry(2, "START4.ELF", b"a")
904 .entry(2, "FIXUP.DAT", b"b")
905 .build();
906 assert_eq!(error(&bytes), Error::UnpairedFirmware);
907 }
908
909 #[test]
910 fn a_matched_pair_is_accepted_whatever_the_case_and_suffix() {
911 for (start, fixup) in [
912 ("START.ELF", "FIXUP.DAT"),
913 ("start.elf", "fixup.dat"),
914 ("Start4.Elf", "fixup4.DAT"),
915 ("BOOT/start_x.elf", "FIXUP_X.DAT"),
916 ] {
917 let bytes = Builder::new()
918 .entry(2, start, b"a")
919 .entry(2, fixup, b"b")
920 .build();
921 assert!(parse(&bytes).is_ok(), "{start} + {fixup} was rejected");
922 }
923 }
924
925 #[test]
926 fn firmware_with_no_partner_needs_none() {
927 let bytes = Builder::new().entry(2, "BOOTCODE.BIN", b"a").build();
928 assert_eq!(parse(&bytes).unwrap().count(), 1);
929 }
930
931 #[test]
932 fn every_rejection_is_the_senders_fault() {
933 assert_eq!(Error::TooShort.http_status(), 400);
934 assert_eq!(Error::BadChecksum.http_status(), 400);
935 assert_eq!(Error::UnpairedFirmware.http_status(), 400);
936 }
937
938 #[test]
939 fn roles_round_trip_through_their_wire_bytes() {
940 for role in [Role::File, Role::Kernel, Role::Firmware, Role::Config] {
941 assert_eq!(Role::from_byte(role.as_byte()), Some(role));
942 }
943 assert_eq!(Role::from_byte(4), None);
944 }
945
946 const GOLDEN_ONE_FILE: &[u8] = &[
960 b'T', b'E', b'S', b'T', 2, 0, 1, 0, 0, 5, b'A', b'.', b'T', b'X', b'T', 3, 0, 0, 0, b'a', b'b', b'c', 0xE9, 0xB1, 0x6D, 0xB9, ];
971
972 const GOLDEN_FULL: &[u8] = &[
975 b'T', b'E', b'S', b'T', 2, 0, 3, 0, 1, 11, b'K', b'E', b'R', b'N', b'E', b'L', b'7', b'.', b'I', b'M', b'G', 4, 0, 0,
982 0, b'k', b'r', b'n', b'l', 0, 13, b'W', b'W', b'W', b'/', b'I', b'N', b'D', b'E', b'X', b'.', b'H', b'T', b'M', 6, 0, 0,
986 0, b'<', b'h', b't', b'm', b'l', b'>', 3, 10, b'C', b'O', b'N', b'F', b'I', b'G', b'.', b'T', b'X', b'T', 12, 0, 0, 0, b'a', b'r', b'm', b'_', b'6', b'4', b'b', b'i', b't', b'=', b'0', b'\n', 0xB0, 0xBD, 0xAA,
991 0xA1, ];
993
994 const GOLDEN_FIRMWARE: &[u8] = &[
997 b'T', b'E', b'S', b'T', 2, 0, 2, 0, 2, 9, b'S', b'T', b'A', b'R', b'T', b'.', b'E', b'L', b'F', 2, 0, 0, 0, b's', b'e', 2, 9, b'F', b'I', b'X', b'U', b'P', b'.', b'D', b'A', b'T', 2, 0, 0, 0, b'f', b'd', 0x4F, 0xA4, 0x7B, 0xD1, ];
1009
1010 fn check_golden(bytes: &[u8], entries: &[Entry<'_>]) {
1012 let bundle = parse(bytes).expect("the golden vector should parse");
1013 assert_eq!(bundle.iter().collect::<Vec<_>>(), entries);
1014 assert_eq!(encode(&FORMAT, entries).unwrap(), bytes);
1015 }
1016
1017 #[test]
1018 fn golden_one_file() {
1019 check_golden(
1020 GOLDEN_ONE_FILE,
1021 &[Entry {
1022 role: Role::File,
1023 path: "A.TXT",
1024 data: b"abc",
1025 }],
1026 );
1027 }
1028
1029 #[test]
1030 fn golden_full() {
1031 check_golden(
1032 GOLDEN_FULL,
1033 &[
1034 Entry {
1035 role: Role::Kernel,
1036 path: "KERNEL7.IMG",
1037 data: b"krnl",
1038 },
1039 Entry {
1040 role: Role::File,
1041 path: "WWW/INDEX.HTM",
1042 data: b"<html>",
1043 },
1044 Entry {
1045 role: Role::Config,
1046 path: "CONFIG.TXT",
1047 data: b"arm_64bit=0\n",
1048 },
1049 ],
1050 );
1051 }
1052
1053 #[test]
1054 fn golden_firmware() {
1055 check_golden(
1056 GOLDEN_FIRMWARE,
1057 &[
1058 Entry {
1059 role: Role::Firmware,
1060 path: "START.ELF",
1061 data: b"se",
1062 },
1063 Entry {
1064 role: Role::Firmware,
1065 path: "FIXUP.DAT",
1066 data: b"fd",
1067 },
1068 ],
1069 );
1070 }
1071
1072 #[test]
1075 fn what_is_encoded_parses_back() {
1076 let big = vec![0xA5; 100_000];
1077 let entries = [
1078 Entry {
1079 role: Role::Kernel,
1080 path: "KERNEL8.IMG",
1081 data: &big,
1082 },
1083 Entry {
1084 role: Role::File,
1085 path: "CERTS/ROOTS/CA.PEM",
1086 data: b"-----BEGIN",
1087 },
1088 Entry {
1089 role: Role::File,
1090 path: "EMPTY.BIN",
1091 data: b"",
1092 },
1093 ];
1094 let bytes = encode(&FORMAT, &entries).unwrap();
1095 let bundle = parse(&bytes).unwrap();
1096 assert_eq!(bundle.iter().collect::<Vec<_>>(), entries);
1097 assert_eq!(bundle.kernel().unwrap().data.len(), 100_000);
1098 }
1099
1100 #[test]
1101 fn encoding_reserves_exactly_what_it_writes() {
1102 let entries = [Entry {
1105 role: Role::File,
1106 path: "A.TXT",
1107 data: b"abc",
1108 }];
1109 let bytes = encode(&FORMAT, &entries).unwrap();
1110 assert_eq!(bytes.len(), bytes.capacity());
1111 }
1112
1113 #[test]
1114 fn another_applications_bundle_is_rejected() {
1115 let entries = [Entry {
1116 role: Role::File,
1117 path: "A.TXT",
1118 data: b"abc",
1119 }];
1120 let bytes = encode(
1121 &Format {
1122 magic: *b"OTHR",
1123 max_entries: 8,
1124 },
1125 &entries,
1126 )
1127 .unwrap();
1128 assert_eq!(error(&bytes), Error::BadMagic);
1129 }
1130
1131 #[test]
1132 fn encoding_applies_every_rule_the_parser_would() {
1133 let file = |path| Entry {
1134 role: Role::File,
1135 path,
1136 data: b"x" as &[u8],
1137 };
1138
1139 assert_eq!(
1140 encode(&FORMAT, &[]).unwrap_err(),
1141 Error::BadEntryCount(0),
1142 "an empty bundle"
1143 );
1144
1145 let many: Vec<_> = ["A", "B", "C", "D", "E", "F", "G", "H", "I"]
1146 .iter()
1147 .map(|path| file(path))
1148 .collect();
1149 assert_eq!(
1150 encode(&FORMAT, &many).unwrap_err(),
1151 Error::BadEntryCount(9),
1152 "more entries than the format allows"
1153 );
1154
1155 assert_eq!(
1156 encode(&FORMAT, &[file("../ESCAPE.TXT")]).unwrap_err(),
1157 Error::BadPath
1158 );
1159 assert_eq!(
1160 encode(&FORMAT, &[file("A.TXT"), file("A.TXT")]).unwrap_err(),
1161 Error::DuplicatePath
1162 );
1163 assert_eq!(
1164 encode(
1165 &FORMAT,
1166 &[
1167 Entry {
1168 role: Role::Kernel,
1169 path: "KERNEL7.IMG",
1170 data: b"a"
1171 },
1172 Entry {
1173 role: Role::Kernel,
1174 path: "KERNEL8.IMG",
1175 data: b"b"
1176 },
1177 ]
1178 )
1179 .unwrap_err(),
1180 Error::DuplicateRole(Role::Kernel)
1181 );
1182 assert_eq!(
1183 encode(
1184 &FORMAT,
1185 &[Entry {
1186 role: Role::Firmware,
1187 path: "START.ELF",
1188 data: b"a"
1189 }]
1190 )
1191 .unwrap_err(),
1192 Error::UnpairedFirmware
1193 );
1194 }
1195
1196 #[test]
1197 fn a_path_longer_than_its_length_field() {
1198 let long = "A".repeat(256);
1199 assert_eq!(
1200 encode(
1201 &FORMAT,
1202 &[Entry {
1203 role: Role::File,
1204 path: &long,
1205 data: b"x"
1206 }]
1207 )
1208 .unwrap_err(),
1209 Error::PathTooLong
1210 );
1211
1212 let limit = "A".repeat(255);
1215 assert!(
1216 encode(
1217 &FORMAT,
1218 &[Entry {
1219 role: Role::File,
1220 path: &limit,
1221 data: b"x"
1222 }]
1223 )
1224 .is_ok()
1225 );
1226 }
1227}