1#![doc = include_str!("../README.md")]
6#![no_std]
7
8pub mod entries;
9
10use crate::entries::Void;
11use bitflags::bitflags;
12use core::{
13 cell::Cell,
14 cmp::Ordering,
15 ffi::c_void,
16 mem::offset_of,
17 ops::{Deref, DerefMut, Range},
18};
19use num_enum::TryFromPrimitive;
20use thiserror::Error;
21use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
22
23const BASE_ALIGNMENT: usize = 8;
24
25const TL_SIGNATURE: u32 = 0x4a0f_b10b;
26const TL_VERSION: u8 = 0x1;
27
28const TL_ASSUMED_HEADER_SIZE: usize = 0x18;
29const TE_HEADER_SIZE: usize = 0x8;
30
31const REGISTER_CONVENTION: u8 = 1;
32
33#[derive(Debug, Clone, Copy, Error, PartialEq, Eq)]
34pub enum ValidationError {
35 #[error("Invalid signature")]
36 InvalidSignature,
37 #[error("Size mismatch")]
38 SizeMismatch,
39 #[error("Checksum mismatch")]
40 ChecksumMismatch,
41 #[error("TL version is too old")]
42 VersionTooOld,
43 #[error("TL version is invalid")]
44 InvalidVersion,
45}
46
47#[derive(Debug, Clone, Copy, Error, PartialEq, Eq)]
48pub enum Error {
49 #[error("TL is read-only")]
50 ReadOnly,
51 #[error("Invalid Transfer List: {0}")]
52 Validation(#[from] ValidationError),
53 #[error("Needs reallocation")]
54 NeedsReallocation {
62 required_size: u32,
65 },
66 #[error("Not enough memory")]
67 NotEnoughMemory,
68 #[error("Could not convert byte sequence to data")]
69 FromBytes,
70}
71
72#[derive(Debug, Clone, Copy, Error, PartialEq, Eq)]
73pub enum GetError<'a, T>
74where
75 T: FromTLEPayload<'a>,
76{
77 #[error("Payload could not be accessed")]
78 Payload(T::Error),
79 #[error("No entry with the requested tag found")]
80 NotFound,
81 #[error("Other: {0}")]
82 Other(#[from] Error),
83}
84
85#[derive(Debug, Clone, Copy, PartialEq, Eq)]
87pub enum TLETag {
88 Standard(StandardTLETag),
89 NonStandard(NonStandardTLETag),
90}
91
92impl From<StandardTLETag> for TLETag {
93 fn from(value: StandardTLETag) -> Self {
94 Self::Standard(value)
95 }
96}
97
98impl From<NonStandardTLETag> for TLETag {
99 fn from(value: NonStandardTLETag) -> Self {
100 Self::NonStandard(value)
101 }
102}
103
104impl TLETag {
105 const STANDARD_RANGE: Range<u32> = 0x0..0x80_0000;
106 const NON_STANDARD_RANGE: Range<u32> = 0xff_f000..0x100_0000;
107}
108
109#[derive(Debug, Clone, Copy, PartialEq, Eq)]
110pub enum TLETagError {
111 Reserved,
112 InvalidStandardValue(u32),
113 InvalidNonStandardValue(u32),
114}
115
116impl TryFrom<[u8; 3]> for TLETag {
117 type Error = TLETagError;
118
119 fn try_from(value: [u8; 3]) -> Result<Self, Self::Error> {
120 let value = u32::from_le_bytes([value[0], value[1], value[2], 0]);
121
122 if Self::STANDARD_RANGE.contains(&value) {
123 match value.try_into() {
124 Ok(v) => Ok(Self::Standard(v)),
125 Err(_) => Err(Self::Error::InvalidStandardValue(value)),
126 }
127 } else {
128 value.try_into().map(Self::NonStandard)
129 }
130 }
131}
132
133impl From<TLETag> for [u8; 3] {
134 fn from(value: TLETag) -> Self {
135 let value = match value {
136 TLETag::Standard(t) => t as u32,
137 TLETag::NonStandard(NonStandardTLETag(t)) => {
138 assert!(TLETag::NON_STANDARD_RANGE.contains(&t));
139 t
140 }
141 };
142
143 let bytes = value.to_le_bytes();
144 assert_eq!(bytes[3], 0);
145
146 [bytes[0], bytes[1], bytes[2]]
147 }
148}
149
150#[derive(Debug, Clone, Copy, PartialEq, Eq, TryFromPrimitive)]
152#[repr(u32)]
153pub enum StandardTLETag {
154 Void = 0x0,
155 Fdt = 0x1,
156 HobB = 0x2,
157 HobL = 0x3,
158 AcpiAggr = 0x4,
159 EventLog = 0x5,
160 TpmCrbBase = 0x6,
161 OpteePageablePartAddress = 0x100,
163 DtFormattedSpmcManifest = 0x101,
164 Aarch64EntrypointInfo = 0x102,
165 FfaSpBinary = 0x103,
166 MbedTlsHeapInfo = 0x105,
167 DtFormattedFfaManifest = 0x106,
168 RwMemoryLayout64 = 0x107,
169 Aarch32EntrypointInfo = 0x108,
170 GptErrorInfo = 0x109,
171}
172
173#[derive(Debug, Clone, Copy, PartialEq, Eq)]
174pub struct NonStandardTLETag(u32);
175
176impl TryFrom<u32> for NonStandardTLETag {
177 type Error = TLETagError;
178
179 fn try_from(value: u32) -> Result<Self, Self::Error> {
180 if TLETag::NON_STANDARD_RANGE.contains(&value) {
181 Ok(Self(value))
182 } else {
183 Err(Self::Error::InvalidNonStandardValue(value))
184 }
185 }
186}
187
188impl NonStandardTLETag {
189 pub fn tag(&self) -> u32 {
190 self.0
191 }
192}
193
194#[inline(always)]
195fn slice_checksum(slice: &[u8]) -> u8 {
196 slice.iter().fold(0, |a, v| a.wrapping_add(*v))
197}
198
199#[derive(Debug, PartialEq, Eq, Clone, Copy)]
200enum OperationMode {
201 RW,
202 RO,
203}
204
205#[derive(Debug, PartialEq, Eq)]
206#[repr(C)]
207pub struct TransferList<'a> {
251 header: &'a mut TransferListHeader,
252 payload: &'a mut [u8],
253 operation_mode: OperationMode,
254}
255
256#[derive(Debug, PartialEq, Eq, IntoBytes, FromBytes, KnownLayout)]
257#[repr(C)]
258struct TransferListHeader {
259 signature: u32,
260 checksum: Cell<u8>,
261 version: u8,
262 header_size: u8,
263 alignment: u8,
264 used_size: u32,
265 total_size: u32,
266 flags: TransferListFlags,
267 reserved: u32,
268}
269
270#[derive(Debug, PartialEq, Eq, Immutable, IntoBytes, FromBytes, KnownLayout)]
271struct TransferListFlags(u32);
272
273bitflags! {
274 impl TransferListFlags: u32 {
275 const HAS_CHECKSUM = 1 << 0;
276 }
277
278}
279
280#[derive(Debug, PartialEq, Eq)]
281#[repr(C)]
282pub struct TransferListEntry<'a> {
284 header: &'a TransferListEntryHeader,
285 payload: &'a [u8],
286}
287
288#[derive(Debug, PartialEq, Eq)]
289#[repr(C)]
290pub struct TransferListEntryMut<'a> {
292 header: &'a TransferListEntryHeader,
293 payload: &'a mut [u8],
294 tl_cs: Option<&'a Cell<u8>>,
295}
296
297#[derive(Debug, PartialEq, Eq, Immutable, IntoBytes, FromBytes, KnownLayout)]
298#[repr(C)]
299pub struct TransferListEntryHeader {
300 tag: [u8; 3],
301 pub header_size: u8,
302 pub data_size: u32,
303}
304
305impl TransferListEntryHeader {
306 pub fn tag(&self) -> Result<TLETag, TLETagError> {
307 self.tag.try_into()
308 }
309}
310
311impl<'a> TransferList<'a> {
312 pub fn new(data: &'a mut [u8]) -> Result<Self, Error> {
315 if data.len() < TL_ASSUMED_HEADER_SIZE {
316 return Err(Error::Validation(ValidationError::SizeMismatch));
317 }
318
319 let header_size = (data[offset_of!(TransferListHeader, header_size)] as usize)
320 .next_multiple_of(BASE_ALIGNMENT);
321
322 if data.len() < header_size {
323 return Err(Error::Validation(ValidationError::SizeMismatch));
324 }
325
326 let (header, payload) = data.split_at_mut(header_size);
327
328 let Ok(header) = TransferListHeader::mut_from_bytes(header) else {
329 return Err(Error::Validation(ValidationError::SizeMismatch));
330 };
331
332 let mut s = Self {
333 header,
334 payload,
335 operation_mode: OperationMode::RO,
336 };
337
338 s.validate()?;
339
340 Ok(s)
341 }
342
343 pub fn create(data: &'a mut [u8], use_checksum: bool) -> Result<Self, Error> {
346 let len = data.len();
347 if len < TL_ASSUMED_HEADER_SIZE {
348 return Err(Error::NotEnoughMemory);
349 }
350
351 let (header, payload) = data.split_at_mut(TL_ASSUMED_HEADER_SIZE);
352
353 payload.fill(0);
355
356 let header = TransferListHeader::mut_from_bytes(header).map_err(|_| Error::FromBytes)?;
357
358 header.signature = TL_SIGNATURE;
359 header.checksum = Cell::new(0);
360 header.version = TL_VERSION;
361 header.header_size = TL_ASSUMED_HEADER_SIZE as u8;
362 header.alignment = 0x3;
363 header.used_size = TL_ASSUMED_HEADER_SIZE as u32;
364 header.total_size = len as u32;
365 header.flags = if use_checksum {
366 TransferListFlags::HAS_CHECKSUM
367 } else {
368 TransferListFlags::empty()
369 };
370
371 let mut s = Self {
372 header,
373 payload,
374 operation_mode: OperationMode::RW,
375 };
376
377 if use_checksum {
378 let cs = s.compute_checksum();
379 s.header.checksum.set(cs.wrapping_neg());
380 }
381
382 Ok(s)
383 }
384
385 pub unsafe fn from_raw_ptr(ptr: *mut u8) -> Result<Self, Error> {
399 let size = unsafe { *(ptr.add(offset_of!(TransferListHeader, total_size)) as *const u32) }
400 as usize;
401
402 let slice = core::ptr::slice_from_raw_parts_mut(ptr, size);
403 let slice = unsafe { &mut *slice };
404
405 Self::new(slice)
406 }
407
408 fn requires_rw(&self) -> Result<(), Error> {
409 match self.operation_mode {
410 OperationMode::RW => Ok(()),
411 OperationMode::RO => Err(Error::ReadOnly),
412 }
413 }
414
415 pub fn relocate<'b>(&mut self, data: &'b mut [u8]) -> Result<TransferList<'b>, Error> {
420 self.requires_rw()?;
421
422 let tl_base_addr = self.header.as_mut_bytes().as_ptr() as usize;
425 let target_base = data.as_ptr() as usize;
426 let target_size = data.len();
427
428 let alignment_mask = (1 << self.header.alignment) - 1;
429 let alignment_offset = tl_base_addr & alignment_mask;
430
431 let mut new_tl_base = (target_base & !alignment_mask) + alignment_offset;
432 if new_tl_base < target_base {
433 new_tl_base += alignment_mask + 1;
434 }
435
436 let new_tl_base_index = new_tl_base - target_base;
437 if new_tl_base_index + self.header.used_size as usize > target_size {
438 return Err(Error::NotEnoughMemory);
439 }
440
441 let new_slice = &mut data[new_tl_base_index..];
443 let new_size = new_slice.len();
444 let (header, payload) = new_slice.split_at_mut(self.header.header_size as usize);
445 let header = TransferListHeader::mut_from_bytes(header).map_err(|_| Error::FromBytes)?;
446
447 let old_payload_size = self.header.total_size as usize - self.header.header_size as usize;
449 header
450 .as_mut_bytes()
451 .copy_from_slice(self.header.as_mut_bytes());
452 payload[..old_payload_size].copy_from_slice(self.payload);
453
454 header.total_size = new_size as u32;
455 if self.is_checksum_enabled() {
456 header.checksum.update(|cs| {
457 cs.wrapping_add(slice_checksum(self.header.total_size.as_bytes()))
458 .wrapping_sub(slice_checksum(header.total_size.as_bytes()))
459 });
460 }
461
462 Ok(TransferList {
463 header,
464 payload,
465 operation_mode: self.operation_mode,
466 })
467 }
468
469 fn validate(&mut self) -> Result<(), Error> {
479 if self.header.signature != TL_SIGNATURE {
481 return Err(Error::Validation(ValidationError::InvalidSignature));
482 }
483
484 if self.header.version == 0 {
486 return Err(Error::Validation(ValidationError::InvalidVersion));
487 }
488 match self.header.version.cmp(&TL_VERSION) {
489 Ordering::Less => {
490 return Err(Error::Validation(ValidationError::VersionTooOld));
491 }
492 Ordering::Equal => self.operation_mode = OperationMode::RW,
493 Ordering::Greater => self.operation_mode = OperationMode::RO,
494 }
495
496 if self.header.used_size < self.header.header_size as u32
498 || !self.header.used_size.is_multiple_of(BASE_ALIGNMENT as u32)
499 || !self.header.total_size.is_multiple_of(BASE_ALIGNMENT as u32)
500 || !self.header.header_size.is_multiple_of(BASE_ALIGNMENT as u8)
501 || self.header.used_size > self.header.total_size
502 || (self.header.total_size as usize)
503 > (self.payload.len() + self.header.header_size as usize)
504 {
505 return Err(Error::Validation(ValidationError::SizeMismatch));
506 }
507
508 if self.is_checksum_enabled() {
510 let cs = self.compute_checksum();
511 if cs != 0 {
512 return Err(Error::Validation(ValidationError::ChecksumMismatch));
513 }
514 }
515
516 Ok(())
517 }
518
519 fn compute_checksum(&mut self) -> u8 {
520 slice_checksum(self.header.as_mut_bytes()).wrapping_add(slice_checksum(
521 &self.payload[..(self.header.used_size - self.header.header_size as u32) as usize],
522 ))
523 }
524
525 fn update_checksum(&mut self, old: u8, new: u8) {
526 assert!(self.is_checksum_enabled());
527
528 self.header
529 .checksum
530 .update(|cs| cs.wrapping_add(old).wrapping_sub(new));
531 }
532
533 fn is_checksum_enabled(&self) -> bool {
534 self.header.flags.contains(TransferListFlags::HAS_CHECKSUM)
535 }
536
537 pub fn add_uninitialized_entry(&mut self, tag: TLETag, len: usize) -> Result<(), Error> {
540 self.requires_rw()?;
541
542 let index = self.allocate_entry(len)?;
543 self.create_entry_header(tag, len, index)?;
544 Ok(())
545 }
546
547 pub fn add_entry(&mut self, tag: TLETag, data: &[u8]) -> Result<(), Error> {
550 self.requires_rw()?;
551
552 let index = self.allocate_entry(data.len())?;
553 self.create_entry_with_data(tag, data, index)?;
554 Ok(())
555 }
556
557 fn allocate_entry(&mut self, len: usize) -> Result<usize, Error> {
560 let len = len.next_multiple_of(BASE_ALIGNMENT);
561 let base = self.payload.as_ptr() as usize;
562
563 let Some(e) = self.entries_mut()?.find(|e| {
565 matches!(e.header.tag(), Ok(TLETag::Standard(StandardTLETag::Void)))
566 && len <= (e.header.data_size as usize).next_multiple_of(BASE_ALIGNMENT)
567 }) else {
568 return self.allocate_entry_end(len);
570 };
571
572 let data_size = (e.header.data_size as usize).next_multiple_of(BASE_ALIGNMENT);
573 let entry_index = e.payload.as_ptr() as usize - e.header.header_size as usize - base;
574
575 if e.header.data_size as usize != len {
579 let pad_index = entry_index + (TE_HEADER_SIZE + len).next_multiple_of(BASE_ALIGNMENT);
580 self.create_entry_header(
581 Void::TAG,
582 data_size - len.next_multiple_of(BASE_ALIGNMENT) - TE_HEADER_SIZE,
583 pad_index,
584 )?;
585 }
586
587 Ok(entry_index)
588 }
589
590 fn allocate_entry_end(&mut self, len: usize) -> Result<usize, Error> {
594 let total_required_len = (len + TE_HEADER_SIZE).next_multiple_of(BASE_ALIGNMENT);
595 let index = self.header.used_size as usize - self.header.header_size as usize;
596
597 if (self.header.total_size as usize - self.header.header_size as usize)
599 < index + total_required_len
600 {
601 let alignment_mask = (1 << self.header.alignment) - 1;
602 let base_addr = self.header.as_mut_bytes().as_ptr() as usize;
603 let alignment_offset = base_addr & alignment_mask;
604
605 return Err(Error::NeedsReallocation {
606 required_size: (alignment_offset
607 + self.header.used_size as usize
608 + total_required_len) as u32,
609 });
610 }
611
612 let removed_cs = if self.is_checksum_enabled() {
614 slice_checksum(self.header.used_size.as_bytes())
615 } else {
616 0
617 };
618
619 self.header.used_size += total_required_len as u32;
620
621 if self.is_checksum_enabled() {
623 self.update_checksum(removed_cs, slice_checksum(self.header.used_size.as_bytes()));
624 }
625
626 Ok(index)
627 }
628
629 fn create_entry_with_data(
631 &mut self,
632 tag: TLETag,
633 data: &[u8],
634 index: usize,
635 ) -> Result<(), Error> {
636 let te_payload_range = (index + TE_HEADER_SIZE)..(index + TE_HEADER_SIZE + data.len());
639
640 if self.is_checksum_enabled() {
642 let old_cs = slice_checksum(&self.payload[te_payload_range.clone()]);
643 let new_cs = slice_checksum(data);
644 self.update_checksum(old_cs, new_cs);
645 }
646
647 self.create_entry_header(tag, data.len(), index)?;
649
650 self.payload[te_payload_range].copy_from_slice(data);
652
653 Ok(())
654 }
655
656 fn create_entry_header(&mut self, tag: TLETag, len: usize, index: usize) -> Result<(), Error> {
658 let is_checksum_enabled = self.is_checksum_enabled(); let location = &mut self.payload[index..(index + len + TE_HEADER_SIZE)];
660
661 let (header, _) = TransferListEntryHeader::mut_from_prefix(location).unwrap();
662
663 let old_cs = if is_checksum_enabled {
664 slice_checksum(header.as_bytes())
665 } else {
666 0
667 };
668
669 header.tag = tag.into();
670 header.header_size = TE_HEADER_SIZE as u8;
671 header.data_size = len as u32;
672
673 if is_checksum_enabled {
674 let new_cs = slice_checksum(header.as_bytes());
675 self.update_checksum(old_cs, new_cs);
676 }
677
678 Ok(())
679 }
680
681 pub fn add_uninitialized_aligned_entry(
685 &mut self,
686 tag: TLETag,
687 len: usize,
688 alignment: u8,
689 ) -> Result<(), Error> {
690 self.requires_rw()?;
691
692 self.align_end(alignment).map_err(|e| match e {
694 Error::NeedsReallocation { required_size } => Error::NeedsReallocation {
695 required_size: required_size
696 + TE_HEADER_SIZE as u32
697 + len.next_multiple_of(BASE_ALIGNMENT) as u32,
698 },
699 e => e,
700 })?;
701
702 let index = self.allocate_entry_end(len)?;
704 self.create_entry_header(tag, len, index)?;
705
706 self.update_alignment_header(alignment);
708
709 Ok(())
710 }
711
712 pub fn add_aligned_entry(
716 &mut self,
717 tag: TLETag,
718 data: &[u8],
719 alignment: u8,
720 ) -> Result<(), Error> {
721 self.requires_rw()?;
722
723 self.align_end(alignment).map_err(|e| match e {
725 Error::NeedsReallocation { required_size } => Error::NeedsReallocation {
726 required_size: required_size
727 + TE_HEADER_SIZE as u32
728 + data.len().next_multiple_of(BASE_ALIGNMENT) as u32,
729 },
730 e => e,
731 })?;
732
733 let index = self.allocate_entry_end(data.len())?;
735 self.create_entry_with_data(tag, data, index)?;
736
737 self.update_alignment_header(alignment);
739
740 Ok(())
741 }
742
743 fn align_end(&mut self, alignment: u8) -> Result<(), Error> {
746 let base_addr = self.header.as_mut_bytes().as_ptr() as usize;
749 let mask = (1 << alignment) - 1;
750 let tl_end = base_addr + self.header.used_size as usize + TE_HEADER_SIZE;
751
752 if tl_end & mask != 0 {
754 let padding_len = ((1 << alignment) - (tl_end & mask)) - TE_HEADER_SIZE;
756 let padding_index = self.allocate_entry_end(padding_len)?;
757 self.create_entry_header(entries::Void::TAG, padding_len, padding_index)?;
758 }
759
760 Ok(())
761 }
762
763 fn update_alignment_header(&mut self, new_alignment: u8) {
766 if self.header.alignment < new_alignment {
767 if self.is_checksum_enabled() {
768 self.update_checksum(self.header.alignment, new_alignment);
769 }
770
771 self.header.alignment = new_alignment
772 }
773 }
774
775 pub fn entries<'b>(&'b self) -> impl Iterator<Item = TransferListEntry<'b>>
778 where
779 'a: 'b,
780 {
781 TransferListEntryIterator::<'b> {
782 slice: Cell::new(
783 &self.payload
784 [..(self.header.used_size as usize - self.header.header_size as usize)],
785 ),
786 }
787 }
788
789 pub fn entries_mut<'b>(
792 &'b mut self,
793 ) -> Result<impl Iterator<Item = TransferListEntryMut<'b>>, Error>
794 where
795 'a: 'b,
796 {
797 self.requires_rw()?;
798
799 Ok(TransferListEntryIteratorMut::<'b> {
800 tl_cs: self.is_checksum_enabled().then_some(&self.header.checksum),
801 slice: Cell::new(
802 &mut self.payload
803 [..(self.header.used_size as usize - self.header.header_size as usize)],
804 ),
805 })
806 }
807
808 pub fn get<'b, T>(&'b self) -> Result<TLEWrapperRef<'b, T>, GetError<'b, T>>
812 where
813 T: TLETagged + FromTLEPayload<'b>,
814 'a: 'b,
815 {
816 match self.entries().find(|t| t.header.tag() == Ok(T::TAG)) {
817 Some(some) => some.get().map_err(GetError::Payload),
818 None => Err(GetError::NotFound),
819 }
820 }
821
822 pub fn get_mut<'b, T>(&'b mut self) -> Result<TLEWrapperMut<'b, T>, GetError<'b, T>>
829 where
830 T: TLETagged + FromTLEPayload<'b>,
831 'a: 'b,
832 {
833 match self.entries_mut()?.find(|t| t.header.tag() == Ok(T::TAG)) {
834 Some(some) => some.get_mut().map_err(GetError::Payload),
835 None => Err(GetError::NotFound),
836 }
837 }
838
839 pub fn aarch32_regs<T>(&self, translation: T) -> [u32; 4]
843 where
844 T: FnOnce(*const c_void) -> u32,
845 {
846 const AARCH32_SIGNATURE_BITS: u32 = 24;
847
848 [
849 0,
850 ((REGISTER_CONVENTION as u32) << AARCH32_SIGNATURE_BITS)
851 | (self.header.signature & ((1 << AARCH32_SIGNATURE_BITS) - 1)),
852 self.entries()
854 .find(|e| e.header.tag() == Ok(StandardTLETag::Fdt.into()))
855 .map(|e| e.payload.as_ptr() as u32)
856 .unwrap_or(0),
857 translation(self.header as *const TransferListHeader as *const c_void),
858 ]
859 }
860
861 pub fn aarch64_regs<T>(&self, translation: T) -> [u64; 4]
865 where
866 T: FnOnce(*const c_void) -> u64,
867 {
868 const AARCH64_SIGNATURE_BITS: u32 = 32;
869
870 [
871 self.entries()
873 .find(|e| e.header.tag() == Ok(StandardTLETag::Fdt.into()))
874 .map(|e| e.payload.as_ptr() as u64)
875 .unwrap_or(0),
876 ((REGISTER_CONVENTION as u64) << AARCH64_SIGNATURE_BITS) | self.header.signature as u64,
877 0,
878 translation(self.header as *const TransferListHeader as *const c_void),
879 ]
880 }
881}
882
883struct TransferListEntryIterator<'a> {
884 slice: Cell<&'a [u8]>,
885}
886
887impl<'a> Iterator for TransferListEntryIterator<'a> {
888 type Item = TransferListEntry<'a>;
889
890 fn next(&mut self) -> Option<Self::Item> {
891 let slice = self.slice.take();
892
893 if slice.len() < TE_HEADER_SIZE {
895 return None;
896 }
897
898 let header_size = slice[0x3] as usize;
899 let data_size = *u32::ref_from_bytes(&slice[0x4..0x8]).unwrap() as usize;
900
901 let entry_size = header_size.checked_add(data_size)?;
902 let next_entry_size = entry_size.next_multiple_of(BASE_ALIGNMENT);
903
904 if next_entry_size > slice.len()
905 || next_entry_size < entry_size
906 || header_size > slice.len()
907 || data_size > slice.len()
908 {
909 return None;
910 }
911
912 let (payload, remaining_data) = slice.split_at(entry_size);
914
915 let remaining_data = &remaining_data[(next_entry_size - entry_size)..];
917
918 self.slice.set(remaining_data);
919
920 let (header, payload) = payload.split_at(header_size);
922 let header = TransferListEntryHeader::ref_from_bytes(header).ok()?;
923
924 Some(TransferListEntry { header, payload })
925 }
926}
927
928impl<'a> TransferListEntry<'a> {
929 pub fn get<'b, T>(self) -> Result<TLEWrapperRef<'b, T>, T::Error>
930 where
931 T: FromTLEPayload<'b>,
932 'a: 'b,
933 {
934 T::from_bytes(self.payload).map(|e| TLEWrapperRef {
935 header: self.header,
936 tle: e,
937 })
938 }
939}
940
941struct TransferListEntryIteratorMut<'a> {
942 tl_cs: Option<&'a Cell<u8>>,
943 slice: Cell<&'a mut [u8]>,
944}
945
946impl<'a> Iterator for TransferListEntryIteratorMut<'a> {
947 type Item = TransferListEntryMut<'a>;
948
949 fn next(&mut self) -> Option<Self::Item> {
950 let slice = self.slice.take();
951
952 if slice.len() < TE_HEADER_SIZE {
954 return None;
955 }
956
957 let header_size = slice[offset_of!(TransferListEntryHeader, header_size)] as usize;
958 let data_size = *u32::ref_from_bytes(&slice[0x4..0x8]).unwrap() as usize;
959
960 let entry_size = header_size.checked_add(data_size)?;
961 let next_entry_size = entry_size.next_multiple_of(BASE_ALIGNMENT);
962
963 if next_entry_size > slice.len()
964 || next_entry_size < entry_size
965 || header_size > slice.len()
966 || data_size > slice.len()
967 {
968 return None;
969 }
970
971 let (payload, remaining_data) = slice.split_at_mut(entry_size);
973
974 let remaining_data =
976 &mut remaining_data[(entry_size.next_multiple_of(BASE_ALIGNMENT) - entry_size)..];
977
978 self.slice.set(remaining_data);
979
980 let (header, payload) = payload.split_at_mut(header_size);
982 let header = TransferListEntryHeader::mut_from_bytes(header).ok()?;
983
984 Some(TransferListEntryMut {
985 header,
986 payload,
987 tl_cs: self.tl_cs,
988 })
989 }
990}
991
992impl<'a> TransferListEntryMut<'a> {
993 pub fn get<'b, T>(self) -> Result<TLEWrapperRef<'b, T>, T::Error>
995 where
996 T: FromTLEPayload<'b>,
997 'a: 'b,
998 {
999 T::from_bytes(self.payload).map(|e| TLEWrapperRef {
1000 header: self.header,
1001 tle: e,
1002 })
1003 }
1004
1005 pub fn get_mut<'b, T>(self) -> Result<TLEWrapperMut<'b, T>, T::Error>
1010 where
1011 T: FromTLEPayload<'b>,
1012 'a: 'b,
1013 {
1014 let cs = self
1015 .tl_cs
1016 .map(|tl_cs| (tl_cs, slice_checksum(self.payload)));
1017
1018 T::from_bytes_mut(self.payload).map(|e| TLEWrapperMut {
1019 header: self.header,
1020 tle: e,
1021 cs,
1022 dirty: false,
1023 })
1024 }
1025}
1026
1027pub trait FromTLEPayload<'a> {
1068 type OutputRef;
1070 type OutputMut;
1075 type Error;
1076
1077 fn from_bytes(payload: &'a [u8]) -> Result<Self::OutputRef, Self::Error>;
1080 fn from_bytes_mut(payload: &'a mut [u8]) -> Result<Self::OutputMut, Self::Error>;
1082
1083 fn as_bytes(val: &Self::OutputRef) -> impl Iterator<Item = u8>;
1085 fn mut_as_bytes(val: &mut Self::OutputMut) -> impl Iterator<Item = u8>;
1087}
1088
1089impl<'a, T> FromTLEPayload<'a> for T
1090where
1091 T: FromBytes + IntoBytes + Immutable + KnownLayout + 'a,
1092{
1093 type OutputRef = &'a T;
1094 type OutputMut = &'a mut T;
1095 type Error = Error;
1096
1097 fn from_bytes(payload: &'a [u8]) -> Result<Self::OutputRef, Self::Error> {
1098 T::try_ref_from_bytes(payload).map_err(|_| Error::FromBytes)
1099 }
1100
1101 fn from_bytes_mut(payload: &'a mut [u8]) -> Result<Self::OutputMut, Self::Error> {
1102 T::try_mut_from_bytes(payload).map_err(|_| Error::FromBytes)
1103 }
1104
1105 fn as_bytes(val: &Self::OutputRef) -> impl Iterator<Item = u8> {
1106 val.as_bytes().iter().copied()
1107 }
1108
1109 fn mut_as_bytes(val: &mut Self::OutputMut) -> impl Iterator<Item = u8> {
1110 val.as_bytes().iter().copied()
1111 }
1112}
1113
1114pub trait TLETagged {
1117 const TAG: TLETag;
1119}
1120
1121#[derive(Debug)]
1122pub struct TLEWrapperRef<'a, T>
1123where
1124 T: FromTLEPayload<'a>,
1125{
1126 header: &'a TransferListEntryHeader,
1127 tle: T::OutputRef,
1128}
1129
1130impl<'a, T> Deref for TLEWrapperRef<'a, T>
1131where
1132 T: FromTLEPayload<'a>,
1133{
1134 type Target = T::OutputRef;
1135
1136 fn deref(&self) -> &Self::Target {
1137 &self.tle
1138 }
1139}
1140
1141impl<'a, T> TLEWrapperRef<'a, T>
1142where
1143 T: FromTLEPayload<'a>,
1144{
1145 pub fn header(&self) -> &'a TransferListEntryHeader {
1146 self.header
1147 }
1148}
1149
1150#[derive(Debug)]
1151pub struct TLEWrapperMut<'a, T>
1152where
1153 T: FromTLEPayload<'a>,
1154{
1155 header: &'a TransferListEntryHeader,
1156 tle: T::OutputMut,
1157 cs: Option<(&'a Cell<u8>, u8)>,
1158 dirty: bool,
1159}
1160
1161impl<'a, T> TLEWrapperMut<'a, T>
1162where
1163 T: FromTLEPayload<'a>,
1164{
1165 pub fn header(&self) -> &'a TransferListEntryHeader {
1166 self.header
1167 }
1168}
1169
1170impl<'a, T> Deref for TLEWrapperMut<'a, T>
1171where
1172 T: FromTLEPayload<'a>,
1173{
1174 type Target = T::OutputMut;
1175
1176 fn deref(&self) -> &Self::Target {
1177 &self.tle
1178 }
1179}
1180
1181impl<'a, T> DerefMut for TLEWrapperMut<'a, T>
1182where
1183 T: FromTLEPayload<'a>,
1184{
1185 fn deref_mut(&mut self) -> &mut Self::Target {
1186 self.dirty = true;
1187 &mut self.tle
1188 }
1189}
1190
1191impl<'a, T> Drop for TLEWrapperMut<'a, T>
1192where
1193 T: FromTLEPayload<'a>,
1194{
1195 fn drop(&mut self) {
1196 if self.dirty
1197 && let Some((tl_cs, orig_cs)) = self.cs
1198 {
1199 let cs = T::mut_as_bytes(&mut self.tle).fold(0u8, |a, v| a.wrapping_add(v));
1200 tl_cs.update(|v| v.wrapping_add(orig_cs).wrapping_sub(cs));
1201 }
1202 }
1203}
1204
1205#[cfg(test)]
1206mod tests {
1207 use core::cell::Cell;
1208 use zerocopy::IntoBytes;
1209
1210 use crate::{
1211 Error, GetError, OperationMode, StandardTLETag, TLETag, TransferList,
1212 TransferListEntryHeader, TransferListFlags, TransferListHeader, ValidationError,
1213 entries::{ExecEpInfo32, ExecEpInfo64, ParamHeader},
1214 };
1215
1216 #[test]
1217 fn standard_tag_from_u8() {
1218 assert_eq!(
1219 TLETag::try_from([0; 3]),
1220 Ok(TLETag::Standard(StandardTLETag::Void))
1221 );
1222 assert_eq!(
1223 TLETag::try_from([1, 0, 0]),
1224 Ok(TLETag::Standard(StandardTLETag::Fdt))
1225 );
1226 assert_eq!(
1227 TLETag::try_from([9, 1, 0]),
1228 Ok(TLETag::Standard(StandardTLETag::GptErrorInfo))
1229 );
1230 }
1231
1232 #[test]
1233 fn tag_to_u8() {
1234 assert_eq!(
1235 <TLETag as Into<[u8; 3]>>::into(TLETag::Standard(StandardTLETag::Void)),
1236 [0; 3]
1237 );
1238 assert_eq!(
1239 <TLETag as Into<[u8; 3]>>::into(TLETag::Standard(StandardTLETag::Fdt)),
1240 [1, 0, 0]
1241 );
1242 assert_eq!(
1243 <TLETag as Into<[u8; 3]>>::into(TLETag::Standard(StandardTLETag::GptErrorInfo)),
1244 [9, 1, 0]
1245 );
1246 }
1247
1248 macro_rules! open_tl_and_buf {
1249 ($tl:ident, $buf:ident, $path:expr, $size:expr) => {
1250 let mut $buf = [0; $size];
1251 let input = include_bytes!(concat!("../test_data/", $path));
1252 $buf[..input.len()].copy_from_slice(input);
1253 #[allow(unused_mut)]
1254 let mut $tl = crate::TransferList::new(&mut $buf).unwrap();
1255 };
1256 }
1257
1258 macro_rules! open_tl {
1259 ($tl:ident, $path:expr, $size:expr) => {
1260 open_tl_and_buf!($tl, buf, $path, $size)
1261 };
1262 }
1263
1264 #[test]
1265 fn tl_nocs_is_valid() {
1266 open_tl!(tl, "tl-nocs.bin", 4096);
1267 assert_eq!(tl.validate(), Ok(()))
1268 }
1269
1270 #[test]
1271 fn tl_nocs() {
1272 open_tl!(tl, "tl-nocs.bin", 4096);
1273
1274 assert_eq!(tl.header.signature, 0x4a0f_b10b);
1275 assert_eq!(tl.header.checksum.get(), 0);
1276 assert_eq!(tl.header.version, 1);
1277 assert_eq!(tl.header.header_size, 0x18);
1278 assert_eq!(tl.header.alignment, 3);
1279 assert_eq!(tl.header.used_size, 120);
1280 assert_eq!(tl.header.total_size, 4096);
1281 assert_eq!(tl.header.flags, TransferListFlags::empty());
1282 assert_eq!(tl.header.reserved, 0);
1283
1284 let mut iter = tl.entries();
1285
1286 let entry = iter.next().unwrap();
1287 assert_eq!(iter.next(), None);
1288
1289 let entry = entry.get::<ExecEpInfo64>().unwrap();
1290
1291 assert_eq!(
1292 **entry,
1293 ExecEpInfo64 {
1294 ep_info_hdr: ParamHeader {
1295 ty: 1,
1296 version: 2,
1297 size: 0x58,
1298 attr: 8
1299 },
1300 pc: 0x4020000,
1301 spsr: 467,
1302 pad0: 0,
1303 x: [0x4001008, 0x4001000, 0, 0, 0, 0, 0, 0]
1304 }
1305 )
1306 }
1307
1308 #[test]
1309 fn tl_cs() {
1310 open_tl!(tl, "tl-cs.bin", 4096);
1311
1312 assert_eq!(tl.header.signature, 0x4a0f_b10b);
1313 assert_eq!(tl.header.checksum.get(), 118);
1314 assert_eq!(tl.header.version, 1);
1315 assert_eq!(tl.header.header_size, 0x18);
1316 assert_eq!(tl.header.alignment, 3);
1317 assert_eq!(tl.header.used_size, 120);
1318 assert_eq!(tl.header.total_size, 4096);
1319 assert_eq!(tl.header.flags, TransferListFlags::HAS_CHECKSUM);
1320 assert_eq!(tl.header.reserved, 0);
1321
1322 let mut iter = tl.entries();
1323
1324 let entry = iter.next().unwrap();
1325 assert_eq!(iter.next(), None);
1326
1327 let entry = entry.get::<ExecEpInfo64>().unwrap();
1328
1329 assert_eq!(
1330 **entry,
1331 ExecEpInfo64 {
1332 ep_info_hdr: ParamHeader {
1333 ty: 1,
1334 version: 2,
1335 size: 0x58,
1336 attr: 8
1337 },
1338 pc: 0x4020000,
1339 spsr: 467,
1340 pad0: 0,
1341 x: [0x4001008, 0x4001000, 0, 0, 0, 0, 0, 0]
1342 }
1343 )
1344 }
1345
1346 #[test]
1347 fn tl_cs_from_ptr() {
1348 let mut buf = [0; 4096];
1349 let input = include_bytes!("../test_data/tl-cs.bin");
1350 buf[..input.len()].copy_from_slice(input);
1351 let tl = unsafe { TransferList::from_raw_ptr(buf.as_mut_ptr()).unwrap() };
1352
1353 assert_eq!(tl.header.signature, 0x4a0f_b10b);
1354 assert_eq!(tl.header.checksum.get(), 118);
1355 assert_eq!(tl.header.version, 1);
1356 assert_eq!(tl.header.header_size, 0x18);
1357 assert_eq!(tl.header.alignment, 3);
1358 assert_eq!(tl.header.used_size, 120);
1359 assert_eq!(tl.header.total_size, 4096);
1360 assert_eq!(tl.header.flags, TransferListFlags::HAS_CHECKSUM);
1361 assert_eq!(tl.header.reserved, 0);
1362
1363 let mut iter = tl.entries();
1364
1365 let entry = iter.next().unwrap();
1366 assert_eq!(iter.next(), None);
1367
1368 let entry = entry.get::<ExecEpInfo64>().unwrap();
1369
1370 assert_eq!(
1371 **entry,
1372 ExecEpInfo64 {
1373 ep_info_hdr: ParamHeader {
1374 ty: 1,
1375 version: 2,
1376 size: 0x58,
1377 attr: 8
1378 },
1379 pc: 0x4020000,
1380 spsr: 467,
1381 pad0: 0,
1382 x: [0x4001008, 0x4001000, 0, 0, 0, 0, 0, 0]
1383 }
1384 )
1385 }
1386
1387 #[test]
1388 fn create_tl_nocs() {
1389 let mut buf = [0; 4096];
1390 let mut tl = TransferList::create(&mut buf, false).unwrap();
1391
1392 assert_eq!(
1393 *tl.header,
1394 TransferListHeader {
1395 signature: 0x4a0f_b10b,
1396 checksum: Cell::new(0),
1397 version: 1,
1398 header_size: 0x18,
1399 alignment: 3,
1400 used_size: 0x18,
1401 total_size: 0x1000,
1402 flags: TransferListFlags::empty(),
1403 reserved: 0
1404 }
1405 );
1406
1407 assert_eq!(tl.entries().count(), 0);
1408 assert_eq!(tl.validate(), Ok(()));
1409 }
1410
1411 #[test]
1412 fn create_tl_cs() {
1413 let mut buf = [0; 4096];
1414 let mut tl = TransferList::create(&mut buf, true).unwrap();
1415
1416 assert_eq!(
1417 *tl.header,
1418 TransferListHeader {
1419 signature: 0x4a0f_b10b,
1420 checksum: Cell::new(0xa6),
1421 version: 1,
1422 header_size: 0x18,
1423 alignment: 3,
1424 used_size: 0x18,
1425 total_size: 0x1000,
1426 flags: TransferListFlags::HAS_CHECKSUM,
1427 reserved: 0
1428 }
1429 );
1430
1431 assert_eq!(tl.entries().count(), 0);
1432 assert_eq!(tl.validate(), Ok(()));
1433 }
1434
1435 #[test]
1436 fn create_too_small() {
1437 let mut buf = [0; 0x10];
1438 assert_eq!(
1439 TransferList::create(&mut buf, true),
1440 Err(Error::NotEnoughMemory)
1441 );
1442 }
1443
1444 #[test]
1445 fn tl_invalid_sig() {
1446 let mut buf = [0; 4096];
1447 let input = include_bytes!("../test_data/tl-nocs.bin");
1448 buf[..input.len()].copy_from_slice(input);
1449 buf[0x0] = 0;
1450 assert_eq!(
1451 TransferList::new(&mut buf),
1452 Err(Error::Validation(ValidationError::InvalidSignature)),
1453 );
1454 }
1455
1456 #[test]
1457 fn tl_invalid_version() {
1458 let mut buf = [0; 4096];
1459 let input = include_bytes!("../test_data/tl-nocs.bin");
1460 buf[..input.len()].copy_from_slice(input);
1461 buf[0x5] = 0;
1462 assert_eq!(
1463 TransferList::new(&mut buf),
1464 Err(Error::Validation(ValidationError::InvalidVersion)),
1465 );
1466 }
1467
1468 #[test]
1469 fn tl_invalid_checksum() {
1470 let mut buf = [0; 4096];
1471 let input = include_bytes!("../test_data/tl-cs.bin");
1472 buf[..input.len()].copy_from_slice(input);
1473 buf[0x4] += 5;
1474 assert_eq!(
1475 TransferList::new(&mut buf),
1476 Err(Error::Validation(ValidationError::ChecksumMismatch)),
1477 );
1478 }
1479
1480 #[test]
1481 fn tl_buffer_too_small() {
1482 let mut buf = [0; 2048];
1483 let input = include_bytes!("../test_data/tl-nocs.bin");
1484 buf[..input.len()].copy_from_slice(input);
1485 assert_eq!(
1486 TransferList::new(&mut buf),
1487 Err(Error::Validation(ValidationError::SizeMismatch)),
1488 );
1489 }
1490
1491 #[test]
1492 fn tl_too_recent_is_ro() {
1493 let mut buf = [0; 4096];
1494 let input = include_bytes!("../test_data/tl-nocs.bin");
1495 buf[..input.len()].copy_from_slice(input);
1496 buf[0x5] = 2;
1497
1498 let tl = TransferList::new(&mut buf).unwrap();
1499 assert_eq!(tl.operation_mode, OperationMode::RO);
1500 }
1501
1502 macro_rules! mut_test {
1503 ($fn:ident,$path:literal,$checksum:literal) => {
1504 let mut buf = [0; 4096];
1505 let input = include_bytes!($path);
1506 buf[..input.len()].copy_from_slice(input);
1507 $fn(&mut buf, $checksum);
1508 };
1509 }
1510
1511 #[test]
1512 fn push_back_with_data_nocs() {
1513 mut_test!(push_back_with_data, "../test_data/tl-nocs.bin", false);
1514 }
1515 #[test]
1516 fn push_back_with_data_cs() {
1517 mut_test!(push_back_with_data, "../test_data/tl-cs.bin", true);
1518 }
1519
1520 fn push_back_with_data(buf: &mut [u8], checksum: bool) {
1521 let mut tl = TransferList::new(buf).unwrap();
1522
1523 assert_eq!(
1524 tl.add_entry(StandardTLETag::DtFormattedFfaManifest.into(), &[4; 8]),
1525 Ok(())
1526 );
1527
1528 let entry = tl.entries().nth(1).unwrap();
1529 assert_eq!(
1530 *entry.header,
1531 TransferListEntryHeader {
1532 tag: [6, 1, 0],
1533 header_size: 8,
1534 data_size: 8
1535 }
1536 );
1537 assert_eq!(entry.payload, &[4; 8]);
1538 assert_eq!(tl.header.used_size, 136);
1539
1540 if checksum {
1541 assert_eq!(tl.validate(), Ok(()));
1542 }
1543
1544 assert_eq!(
1545 buf[120..136],
1546 [6, 1, 0, 8, 8, 0, 0, 0, 4, 4, 4, 4, 4, 4, 4, 4]
1547 );
1548 }
1549
1550 #[test]
1551 fn push_back_uninitialized_nocs() {
1552 mut_test!(push_back_uninitialized, "../test_data/tl-nocs.bin", false);
1553 }
1554 #[test]
1555 fn push_back_uninitialized_cs() {
1556 mut_test!(push_back_uninitialized, "../test_data/tl-cs.bin", true);
1557 }
1558
1559 fn push_back_uninitialized(buf: &mut [u8], checksum: bool) {
1560 let mut tl = TransferList::new(buf).unwrap();
1561
1562 assert_eq!(
1563 tl.add_uninitialized_entry(StandardTLETag::DtFormattedFfaManifest.into(), 32),
1564 Ok(())
1565 );
1566
1567 if checksum {
1568 assert_eq!(tl.validate(), Ok(()));
1569 }
1570
1571 let entry = tl.entries().nth(1).unwrap();
1572 assert_eq!(
1573 *entry.header,
1574 TransferListEntryHeader {
1575 tag: [6, 1, 0],
1576 header_size: 8,
1577 data_size: 32
1578 }
1579 );
1580 assert_eq!(tl.header.used_size, 160);
1581
1582 if checksum {
1583 assert_eq!(tl.validate(), Ok(()));
1584 }
1585
1586 assert_eq!(buf[120..128], [6, 1, 0, 8, 32, 0, 0, 0]);
1587 assert_eq!(buf[0x8..0xc], [160, 0, 0, 0]);
1588 }
1589
1590 #[test]
1591 fn push_back_uninitialized_oom() {
1592 open_tl_and_buf!(tl, buf, "tl-nocs.bin", 4096);
1593
1594 assert_eq!(
1595 tl.add_uninitialized_entry(
1596 TLETag::Standard(StandardTLETag::DtFormattedFfaManifest),
1597 4000
1598 ),
1599 Err(Error::NeedsReallocation {
1600 required_size: 4128 })
1602 );
1603 }
1604
1605 #[test]
1606 fn push_back_with_data_oom() {
1607 open_tl_and_buf!(tl, buf, "tl-nocs.bin", 4096);
1608
1609 assert_eq!(
1610 tl.add_entry(StandardTLETag::DtFormattedFfaManifest.into(), &[1; 4000]),
1611 Err(Error::NeedsReallocation {
1612 required_size: 4128 })
1614 );
1615 }
1616
1617 #[test]
1618 fn add_uninitialized_aligned_entry_nocs() {
1619 mut_test!(
1620 add_uninitialized_aligned_entry,
1621 "../test_data/tl-nocs.bin",
1622 false
1623 );
1624 }
1625 #[test]
1626 fn add_uninitialized_aligned_entry_cs() {
1627 mut_test!(
1628 add_uninitialized_aligned_entry,
1629 "../test_data/tl-cs.bin",
1630 true
1631 );
1632 }
1633
1634 fn add_uninitialized_aligned_entry(buf: &mut [u8], checksum: bool) {
1635 let align = 10;
1636
1637 let base = buf.as_ptr() as usize;
1638 let mut tl = TransferList::new(buf).unwrap();
1639
1640 let end = base + 128; let offset = ((1 << align) - (end & ((1 << align) - 1))) % (1 << align);
1643 let pad_size = offset - 8;
1644
1645 assert_eq!(
1646 tl.add_uninitialized_aligned_entry(
1647 StandardTLETag::DtFormattedFfaManifest.into(),
1648 128,
1649 align
1650 ),
1651 Ok(())
1652 );
1653
1654 {
1655 let mut iter = tl.entries().skip(1);
1656
1657 if pad_size != 0 {
1658 let void = iter.next().unwrap();
1659 assert_eq!(
1660 *void.header,
1661 TransferListEntryHeader {
1662 tag: [0, 0, 0],
1663 header_size: 8,
1664 data_size: pad_size as u32
1665 }
1666 );
1667 }
1668
1669 let entry = iter.next().unwrap();
1670 assert_eq!(
1671 *entry.header,
1672 TransferListEntryHeader {
1673 tag: [6, 1, 0],
1674 header_size: 8,
1675 data_size: 128
1676 }
1677 );
1678 }
1679
1680 assert_eq!(tl.header.alignment, align);
1681 assert_eq!(tl.header.used_size, 256 + offset as u32);
1682
1683 if checksum {
1684 assert_eq!(tl.validate(), Ok(()));
1685 }
1686
1687 if pad_size != 0 {
1688 assert_eq!(buf[120..124], [0, 0, 0, 8]);
1689 assert_eq!(&buf[124..128], (pad_size as u32).as_bytes());
1690 }
1691 assert_eq!(
1692 buf[(120 + offset)..(128 + offset)],
1693 [6, 1, 0, 8, 128, 0, 0, 0]
1694 );
1695 assert_eq!(&buf[0x8..0xc], (120 + offset as u32 + 8 + 128).as_bytes());
1696 assert_eq!(buf[0x7], align);
1697 }
1698
1699 #[test]
1700 fn add_small_entries_nocs() {
1701 mut_test!(add_small_entries, "../test_data/tl-nocs.bin", false);
1702 }
1703 #[test]
1704 fn add_small_entries_cs() {
1705 mut_test!(add_small_entries, "../test_data/tl-cs.bin", true);
1706 }
1707
1708 fn add_small_entries(buf: &mut [u8], checksum: bool) {
1709 let mut tl = TransferList::new(buf).unwrap();
1710 tl.add_entry(StandardTLETag::Fdt.into(), &[1; 3]).unwrap();
1711 tl.add_entry(StandardTLETag::HobB.into(), &[2; 3]).unwrap();
1712 tl.add_entry(StandardTLETag::HobL.into(), &[3; 3]).unwrap();
1713 tl.add_entry(StandardTLETag::AcpiAggr.into(), &[4; 3])
1714 .unwrap();
1715
1716 assert_eq!(tl.header.used_size, 184);
1717
1718 for (i, e) in tl.entries().skip(1).enumerate() {
1719 let i = (i as u8) + 1;
1720
1721 assert_eq!(
1722 *e.header,
1723 TransferListEntryHeader {
1724 tag: [i, 0, 0],
1725 header_size: 8,
1726 data_size: 3
1727 }
1728 );
1729
1730 assert_eq!(e.payload, &[i; 3]);
1731 }
1732
1733 if checksum {
1734 assert_eq!(tl.validate(), Ok(()));
1735 }
1736 }
1737
1738 #[test]
1739 fn add_small_aligned_entry_nocs() {
1740 mut_test!(add_small_aligned_entry, "../test_data/tl-nocs.bin", false);
1741 }
1742 #[test]
1743 fn add_small_aligned_entry_cs() {
1744 mut_test!(add_small_aligned_entry, "../test_data/tl-cs.bin", true);
1745 }
1746
1747 fn add_small_aligned_entry(buf: &mut [u8], checksum: bool) {
1748 let mut tl = TransferList::new(buf).unwrap();
1749 tl.add_aligned_entry(StandardTLETag::DtFormattedFfaManifest.into(), &[1; 32], 1)
1750 .unwrap();
1751
1752 assert_eq!(tl.entries().count(), 2); let entry = tl.entries().nth(1).unwrap();
1755 assert_eq!(
1756 *entry.header,
1757 TransferListEntryHeader {
1758 tag: [6, 1, 0],
1759 header_size: 8,
1760 data_size: 32
1761 }
1762 );
1763
1764 assert_eq!(entry.payload, &[1; 32]);
1765
1766 assert_eq!(tl.header.used_size, 160);
1767 assert_eq!(tl.header.alignment, 3);
1768
1769 if checksum {
1770 assert_eq!(tl.validate(), Ok(()));
1771 }
1772 }
1773
1774 #[test]
1775 fn add_aligned_entry_nocs() {
1776 mut_test!(add_aligned_entry, "../test_data/tl-nocs.bin", false);
1777 }
1778 #[test]
1779 fn add_aligned_entry_cs() {
1780 mut_test!(add_aligned_entry, "../test_data/tl-cs.bin", true);
1781 }
1782
1783 fn add_aligned_entry(buf: &mut [u8], checksum: bool) {
1784 let align = 10;
1785
1786 let base = buf.as_ptr() as usize;
1787
1788 let mut tl = TransferList::new(buf).unwrap();
1789
1790 let end = base + 128; let offset = ((1 << align) - (end & ((1 << align) - 1))) % (1 << align);
1793 let pad_size = offset - 8;
1794
1795 assert_eq!(
1796 tl.add_aligned_entry(
1797 StandardTLETag::DtFormattedFfaManifest.into(),
1798 &[4; 128],
1799 align
1800 ),
1801 Ok(())
1802 );
1803
1804 if checksum {
1805 assert_eq!(tl.validate(), Ok(()));
1806 }
1807
1808 {
1809 let mut iter = tl.entries().skip(1);
1810
1811 if pad_size != 0 {
1812 let void = iter.next().unwrap();
1813 assert_eq!(
1814 *void.header,
1815 TransferListEntryHeader {
1816 tag: [0, 0, 0],
1817 header_size: 8,
1818 data_size: pad_size as u32
1819 }
1820 );
1821 }
1822
1823 let entry = iter.next().unwrap();
1824 assert_eq!(
1825 *entry.header,
1826 TransferListEntryHeader {
1827 tag: [6, 1, 0],
1828 header_size: 8,
1829 data_size: 128
1830 }
1831 );
1832 }
1833
1834 assert_eq!(tl.header.alignment, align);
1835 assert_eq!(tl.header.used_size, 256 + offset as u32);
1836
1837 if checksum {
1838 assert_eq!(tl.validate(), Ok(()));
1839 }
1840
1841 if pad_size != 0 {
1842 assert_eq!(buf[120..124], [0, 0, 0, 8]);
1843 assert_eq!(&buf[124..128], (pad_size as u32).as_bytes());
1844 }
1845 assert_eq!(
1846 buf[(120 + offset)..(128 + offset)],
1847 [6, 1, 0, 8, 128, 0, 0, 0]
1848 );
1849 assert_eq!(buf[(128 + offset)..(256 + offset)], [4u8; 128]);
1850 assert_eq!(&buf[0x8..0xc], (120 + offset as u32 + 8 + 128).as_bytes());
1851 assert_eq!(buf[0x7], align);
1852 }
1853
1854 #[test]
1855 fn add_reclaim_nocs() {
1856 mut_test!(add_reclaim, "../test_data/tl-nocs.bin", false);
1857 }
1858 #[test]
1859 fn add_reclaim_cs() {
1860 mut_test!(add_reclaim, "../test_data/tl-cs.bin", true);
1861 }
1862
1863 fn add_reclaim(buf: &mut [u8], checksum: bool) {
1864 let align = 10;
1865
1866 let base = buf.as_ptr() as usize;
1867 let mut tl = TransferList::new(buf).unwrap();
1868
1869 let end = base + 128; let offset = ((1 << align) - (end & ((1 << align) - 1))) % (1 << align);
1872 let pad_size = offset - 8;
1873
1874 assert_eq!(
1875 tl.add_uninitialized_aligned_entry(
1876 StandardTLETag::DtFormattedFfaManifest.into(),
1877 128,
1878 align
1879 ),
1880 Ok(())
1881 );
1882 assert_eq!(tl.header.used_size, 256 + offset as u32);
1883
1884 if pad_size < 40 {
1888 add_reclaim(buf, checksum);
1889 return;
1890 }
1891
1892 assert_eq!(
1893 Ok(()),
1894 tl.add_uninitialized_entry(StandardTLETag::AcpiAggr.into(), 32)
1895 );
1896 assert_eq!(tl.header.used_size, 256 + offset as u32); assert_eq!(tl.entries().count(), 4);
1899
1900 let tags = [
1901 StandardTLETag::Aarch64EntrypointInfo.into(),
1902 StandardTLETag::AcpiAggr.into(),
1903 StandardTLETag::Void.into(),
1904 StandardTLETag::DtFormattedFfaManifest.into(),
1905 ];
1906 let sizes = [88, 32, pad_size - 40, 128];
1907
1908 for (i, e) in tl.entries().enumerate() {
1909 assert_eq!(e.header.tag(), Ok(tags[i]));
1910 assert_eq!(e.header.data_size as usize, sizes[i]);
1911 }
1912
1913 if checksum {
1914 assert_eq!(tl.validate(), Ok(()));
1915 }
1916 }
1917
1918 #[test]
1919 fn relocate() {
1920 open_tl!(tl, "tl-cs.bin", 4096);
1921
1922 let mut new_buf = [0; 5120];
1923 let mut new_tl = tl.relocate(&mut new_buf).unwrap();
1924
1925 assert_eq!(new_tl.header.signature, tl.header.signature);
1926 assert_eq!(new_tl.header.checksum.get(), 114);
1927 assert_eq!(new_tl.header.version, tl.header.version);
1928 assert_eq!(new_tl.header.header_size, tl.header.header_size);
1929 assert_eq!(new_tl.header.alignment, tl.header.alignment);
1930 assert_eq!(new_tl.header.used_size, tl.header.used_size);
1931 assert_eq!(new_tl.header.total_size, 5120);
1932 assert_eq!(new_tl.header.flags, tl.header.flags);
1933 assert_eq!(new_tl.header.reserved, tl.header.reserved);
1934
1935 assert_eq!(new_tl.validate(), Ok(()));
1936
1937 assert_eq!(tl.entries().count(), new_tl.entries().count());
1938 for (entry, new_entry) in tl.entries().zip(new_tl.entries()) {
1939 assert_eq!(entry, new_entry);
1940 }
1941
1942 assert_eq!(
1943 new_buf[0..0x18],
1944 [
1945 0x0b, 0xb1, 0x0f, 0x4a, 114, 0x1, 0x18, 0x3, 0x78, 0x0, 0x0, 0x0, 0x0, 0x14, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 ]
1955 )
1956 }
1957
1958 #[test]
1959 fn relocate_preserves_alignment() {
1960 open_tl!(tl, "tl-cs.bin", 4096);
1961 tl.add_uninitialized_aligned_entry(StandardTLETag::DtFormattedFfaManifest.into(), 128, 9)
1962 .unwrap();
1963
1964 let mut new_buf = [0; 5120];
1965 let new_tl = tl.relocate(&mut new_buf).unwrap();
1966
1967 assert_eq!(tl.entries().count(), 3);
1968
1969 let entry = new_tl
1970 .entries()
1971 .find(|e| e.header.tag() == Ok(StandardTLETag::DtFormattedFfaManifest.into()))
1972 .unwrap();
1973
1974 assert!((entry.payload.as_ptr() as usize).is_multiple_of(1 << 9));
1975 }
1976
1977 #[test]
1978 fn relocate_then_alloc() {
1979 open_tl!(tl, "tl-cs.bin", 4096);
1980
1981 let err = tl
1982 .add_uninitialized_entry(StandardTLETag::DtFormattedFfaManifest.into(), 4096)
1983 .unwrap_err();
1984 let Error::NeedsReallocation { required_size } = err else {
1985 panic!()
1986 };
1987
1988 assert_eq!(required_size, 4224);
1989 let mut new_buf = [0; 4224];
1990
1991 let mut new_tl = tl.relocate(&mut new_buf).unwrap();
1992
1993 assert_eq!(
1994 new_tl.add_uninitialized_entry(StandardTLETag::DtFormattedFfaManifest.into(), 4096),
1995 Ok(())
1996 );
1997 }
1998
1999 #[test]
2000 fn relocate_too_small() {
2001 open_tl!(tl, "tl-cs.bin", 4096);
2002 let mut new_buf = [0; 119];
2003
2004 assert_eq!(tl.relocate(&mut new_buf), Err(Error::NotEnoughMemory));
2005 }
2006
2007 #[test]
2008 fn get_valid() {
2009 open_tl!(tl, "tl-cs.bin", 4096);
2010 assert!(tl.get::<ExecEpInfo64>().is_ok());
2011 assert!(tl.get_mut::<ExecEpInfo64>().is_ok());
2012 }
2013
2014 #[test]
2015 fn get_not_found() {
2016 open_tl!(tl, "tl-cs.bin", 4096);
2017 assert!(matches!(tl.get::<ExecEpInfo32>(), Err(GetError::NotFound)));
2018 }
2019
2020 #[test]
2021 fn get_then_mut_updates_checksum() {
2022 open_tl!(tl, "tl-cs.bin", 4096);
2023
2024 let mut entry = tl.get_mut::<ExecEpInfo64>().unwrap();
2025 entry.x.fill(0x1234_abcd);
2026 drop(entry);
2027
2028 assert_eq!(Ok(()), tl.validate());
2029 }
2030
2031 #[test]
2032 fn readonly_tl() {
2033 open_tl!(tl, "tl-cs.bin", 4096);
2034 tl.operation_mode = OperationMode::RO;
2035
2036 let mut buf = [0; 4096];
2037 assert_eq!(tl.relocate(&mut buf), Err(Error::ReadOnly));
2038
2039 assert_eq!(
2040 tl.add_aligned_entry(StandardTLETag::Aarch64EntrypointInfo.into(), &[0; 8], 1),
2041 Err(Error::ReadOnly)
2042 );
2043 assert_eq!(
2044 tl.add_uninitialized_aligned_entry(StandardTLETag::Aarch32EntrypointInfo.into(), 32, 1),
2045 Err(Error::ReadOnly)
2046 );
2047 assert_eq!(
2048 tl.add_entry(StandardTLETag::AcpiAggr.into(), &[0; 8],),
2049 Err(Error::ReadOnly)
2050 );
2051 assert_eq!(
2052 tl.add_uninitialized_entry(StandardTLETag::AcpiAggr.into(), 32),
2053 Err(Error::ReadOnly)
2054 );
2055 }
2056
2057 #[test]
2058 fn aarch32_regs_no_fdt() {
2059 let mut buf = [0; 4096];
2060 let input = include_bytes!("../test_data/tl-cs.bin");
2061 buf[..input.len()].copy_from_slice(input);
2062 let tl = TransferList::new(&mut buf).unwrap();
2063
2064 assert_eq!(
2065 tl.aarch32_regs(|addr| addr as u32),
2066 [0, 0x010f_b10b, 0, buf.as_ptr() as u32]
2067 )
2068 }
2069
2070 #[test]
2071 fn aarch64_regs_no_fdt() {
2072 let mut buf = [0; 4096];
2073 let input = include_bytes!("../test_data/tl-cs.bin");
2074 buf[..input.len()].copy_from_slice(input);
2075 let tl = TransferList::new(&mut buf).unwrap();
2076
2077 assert_eq!(
2078 tl.aarch64_regs(|addr| addr as u64),
2079 [0, 0x0000_0001_4a0f_b10b, 0, buf.as_ptr() as u64]
2080 )
2081 }
2082
2083 #[test]
2084 fn aarch32_regs_fdt() {
2085 let mut buf = [0; 4096];
2086 let input = include_bytes!("../test_data/tl-fdt.bin");
2087 buf[..input.len()].copy_from_slice(input);
2088 let base = buf.as_ptr() as u32;
2089 let tl = TransferList::new(&mut buf).unwrap();
2090
2091 assert_eq!(
2092 tl.aarch32_regs(|addr| addr as u32),
2093 [0, 0x010f_b10b, base + 0x20, base]
2094 )
2095 }
2096
2097 #[test]
2098 fn aarch64_regs_fdt() {
2099 let mut buf = [0; 4096];
2100 let input = include_bytes!("../test_data/tl-fdt.bin");
2101 buf[..input.len()].copy_from_slice(input);
2102 let base = buf.as_ptr() as u64;
2103 let tl = TransferList::new(&mut buf).unwrap();
2104
2105 assert_eq!(
2106 tl.aarch64_regs(|addr| addr as u64),
2107 [base + 0x20, 0x0000_0001_4a0f_b10b, 0, base]
2108 )
2109 }
2110
2111 macro_rules! add_or_reloc {
2112 ($buf:ident, $tl:ident, $tag:expr, $len:expr) => {
2113 match $tl.add_uninitialized_entry($tag, $len) {
2114 Ok(_) => {}
2115 Err(Error::NeedsReallocation { required_size }) => {
2116 let buf = &mut $buf[0..required_size as usize];
2117 $tl = $tl.relocate(buf).unwrap();
2118 $tl.add_uninitialized_entry($tag, $len).unwrap();
2119 }
2120 Err(e) => panic!("unexpected error: {e:?}"),
2121 }
2122 };
2123 ($buf:ident, $tl:ident, $tag:expr, $len:expr, $align:expr) => {
2124 match $tl.add_uninitialized_aligned_entry($tag, $len, $align) {
2125 Ok(_) => {}
2126 Err(Error::NeedsReallocation { required_size }) => {
2127 let buf = &mut $buf[0..required_size as usize];
2128 $tl = $tl.relocate(buf).unwrap();
2129 $tl.add_uninitialized_aligned_entry($tag, $len, $align)
2130 .unwrap();
2131 }
2132 Err(e) => panic!("unexpected error: {e:?}"),
2133 }
2134 };
2135 }
2136
2137 macro_rules! assert_entry_matches {
2138 ($tl:ident, $tag:expr, $len:expr) => {
2139 assert_eq!(
2140 $tl.entries()
2141 .find(|e| e.header.tag == $tag)
2142 .unwrap()
2143 .payload
2144 .len(),
2145 $len
2146 );
2147 };
2148 ($tl:ident, $tag:expr, $len:expr, $align:expr) => {
2149 let tl = $tl.entries().find(|e| e.header.tag == $tag).unwrap();
2150 let len = tl.payload.len();
2151 let ptr = (tl.payload.as_ptr() as usize);
2152 let align = ptr.trailing_zeros();
2153
2154 assert_eq!(
2155 len, $len,
2156 "For entry with tag {:?}, expected len of {:x} but got {len:x}",
2157 $tag, $len
2158 );
2159 assert!(
2160 align >= $align,
2161 "For entry with tag {:?}, expected alignment of at least {:x} but got {align:x} (0x{ptr:x})",
2162 $tag,
2163 $align
2164 );
2165 };
2166 }
2167
2168 #[test]
2169 fn add_reloc_seq1() {
2170 let mut buf1 = [0; 4096];
2171 let mut buf2 = [0; 4096];
2172 let mut buf3 = [0; 4096];
2173 let mut buf4 = [0; 4096];
2174 let mut buf5 = [0; 4096];
2175 let mut buf6 = [0; 4096];
2176
2177 let mut tl = TransferList::create(&mut buf1[0..256], true).unwrap();
2178
2179 add_or_reloc!(buf2, tl, StandardTLETag::Fdt.into(), 128);
2180 add_or_reloc!(buf3, tl, StandardTLETag::HobB.into(), 53);
2181 add_or_reloc!(buf4, tl, StandardTLETag::HobL.into(), 200, 7);
2182 add_or_reloc!(buf5, tl, StandardTLETag::AcpiAggr.into(), 192, 6);
2183 add_or_reloc!(buf6, tl, StandardTLETag::EventLog.into(), 48);
2184
2185 assert_entry_matches!(tl, [1, 0, 0], 128);
2186 assert_entry_matches!(tl, [2, 0, 0], 53);
2187 assert_entry_matches!(tl, [3, 0, 0], 200, 7);
2188 assert_entry_matches!(tl, [4, 0, 0], 192, 6);
2189 assert_entry_matches!(tl, [5, 0, 0], 48);
2190 }
2191
2192 #[test]
2193 fn add_reloc_seq2() {
2194 let mut buf1 = [0; 4096];
2195 let mut buf2 = [0; 4096];
2196 let mut buf3 = [0; 4096];
2197 let mut buf4 = [0; 4096];
2198 let mut buf5 = [0; 4096];
2199 let mut buf6 = [0; 4096];
2200
2201 let mut tl = TransferList::create(&mut buf1[0..256], true).unwrap();
2202
2203 add_or_reloc!(buf2, tl, StandardTLETag::Fdt.into(), 36, 2);
2204 add_or_reloc!(buf3, tl, StandardTLETag::HobB.into(), 64, 8);
2205 add_or_reloc!(buf4, tl, StandardTLETag::HobL.into(), 150);
2206 add_or_reloc!(buf5, tl, StandardTLETag::AcpiAggr.into(), 133, 1);
2207 add_or_reloc!(buf6, tl, StandardTLETag::EventLog.into(), 267);
2208
2209 assert_entry_matches!(tl, [1, 0, 0], 36, 2);
2210 assert_entry_matches!(tl, [2, 0, 0], 64, 8);
2211 assert_entry_matches!(tl, [3, 0, 0], 150);
2212 assert_entry_matches!(tl, [4, 0, 0], 133, 1);
2213 assert_entry_matches!(tl, [5, 0, 0], 267);
2214 }
2215
2216 #[test]
2217 fn add_reloc_seq3() {
2218 let mut buf1 = [0; 4096];
2219 let mut buf2 = [0; 4096];
2220 let mut buf3 = [0; 4096];
2221 let mut buf4 = [0; 4096];
2222
2223 let mut tl = TransferList::create(&mut buf1[0..256], true).unwrap();
2224
2225 add_or_reloc!(buf2, tl, StandardTLETag::Fdt.into(), 48);
2226 add_or_reloc!(buf3, tl, StandardTLETag::HobB.into(), 96, 4);
2227 add_or_reloc!(buf4, tl, StandardTLETag::HobL.into(), 200, 8);
2228
2229 assert_entry_matches!(tl, [1, 0, 0], 48);
2230 assert_entry_matches!(tl, [2, 0, 0], 96, 4);
2231 assert_entry_matches!(tl, [3, 0, 0], 200, 8);
2232 }
2233
2234 #[test]
2235 fn add_reloc_seq4() {
2236 let mut buf1 = [0; 4096];
2237 let mut buf2 = [0; 4096];
2238 let mut buf3 = [0; 4096];
2239 let mut buf4 = [0; 4096];
2240 let mut buf5 = [0; 4096];
2241 let mut buf6 = [0; 4096];
2242 let mut buf7 = [0; 4096];
2243
2244 let mut tl = TransferList::create(&mut buf1[0..256], true).unwrap();
2245
2246 add_or_reloc!(buf3, tl, StandardTLETag::Fdt.into(), 32, 2);
2247 add_or_reloc!(buf4, tl, StandardTLETag::HobB.into(), 24, 4);
2248 add_or_reloc!(buf5, tl, StandardTLETag::HobL.into(), 20, 8);
2249 add_or_reloc!(buf6, tl, StandardTLETag::AcpiAggr.into(), 12);
2250 add_or_reloc!(buf7, tl, StandardTLETag::EventLog.into(), 10, 2);
2251 add_or_reloc!(buf2, tl, StandardTLETag::TpmCrbBase.into(), 16, 1);
2252
2253 assert_entry_matches!(tl, [1, 0, 0], 32, 2);
2254 assert_entry_matches!(tl, [2, 0, 0], 24, 4);
2255 assert_entry_matches!(tl, [3, 0, 0], 20, 8);
2256 assert_entry_matches!(tl, [4, 0, 0], 12);
2257 assert_entry_matches!(tl, [5, 0, 0], 10, 2);
2258 assert_entry_matches!(tl, [6, 0, 0], 16, 1);
2259 }
2260
2261 #[test]
2262 fn add_reloc_seq5() {
2263 let mut buf1 = [0; 4096];
2264 let mut buf2 = [0; 4096];
2265 let mut buf3 = [0; 4096];
2266 let mut buf4 = [0; 4096];
2267 let mut buf5 = [0; 4096];
2268
2269 let mut tl = TransferList::create(&mut buf1[0..256], true).unwrap();
2270
2271 add_or_reloc!(buf2, tl, StandardTLETag::Fdt.into(), 100);
2272 add_or_reloc!(buf3, tl, StandardTLETag::HobB.into(), 200);
2273 add_or_reloc!(buf4, tl, StandardTLETag::HobL.into(), 300);
2274 add_or_reloc!(buf5, tl, StandardTLETag::AcpiAggr.into(), 400);
2275
2276 assert_entry_matches!(tl, [1, 0, 0], 100);
2277 assert_entry_matches!(tl, [2, 0, 0], 200);
2278 assert_entry_matches!(tl, [3, 0, 0], 300);
2279 assert_entry_matches!(tl, [4, 0, 0], 400);
2280 }
2281
2282 #[test]
2283 fn add_reloc_seq6() {
2284 let mut buf1 = [0; 4096];
2285 let mut buf2 = [0; 4096];
2286 let mut buf3 = [0; 4096];
2287 let mut buf4 = [0; 4096];
2288 let mut buf5 = [0; 4096];
2289 let mut buf6 = [0; 4096];
2290
2291 let mut tl = TransferList::create(&mut buf1[0..256], true).unwrap();
2292
2293 add_or_reloc!(buf2, tl, StandardTLETag::Fdt.into(), 60, 2);
2294 add_or_reloc!(buf3, tl, StandardTLETag::HobB.into(), 128, 4);
2295 add_or_reloc!(buf4, tl, StandardTLETag::HobL.into(), 256, 1);
2296 add_or_reloc!(buf5, tl, StandardTLETag::AcpiAggr.into(), 512, 8);
2297 add_or_reloc!(buf6, tl, StandardTLETag::EventLog.into(), 1024, 2);
2298
2299 assert_entry_matches!(tl, [1, 0, 0], 60, 2);
2300 assert_entry_matches!(tl, [2, 0, 0], 128, 4);
2301 assert_entry_matches!(tl, [3, 0, 0], 256, 1);
2302 assert_entry_matches!(tl, [4, 0, 0], 512, 8);
2303 assert_entry_matches!(tl, [5, 0, 0], 1024, 2);
2304 }
2305}