xref: /aosp_15_r20/bootable/libbootloader/gbl/libdttable/src/lib.rs (revision 5225e6b173e52d2efc6bcf950c27374fd72adabc)
1 // Copyright 2024, The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 //! This library provides a wrapper APIs for libdttable_c
16 //! https://source.android.com/docs/core/architecture/dto/partitions
17 
18 #![cfg_attr(not(test), no_std)]
19 
20 use core::mem::size_of;
21 use libdttable_bindgen::{dt_table_entry, dt_table_header, DT_TABLE_MAGIC};
22 use liberror::{Error, Result};
23 use safemath::SafeNum;
24 use zerocopy::{AsBytes, FromBytes, FromZeroes, LayoutVerified};
25 
26 /// Rust wrapper for the dt table header
27 #[repr(transparent)]
28 #[derive(Debug, Copy, Clone, AsBytes, FromBytes, FromZeroes, PartialEq)]
29 struct DtTableHeader(dt_table_header);
30 
31 impl DtTableHeader {
32     /// Get magic handling the bytes order
magic(self) -> u3233     fn magic(self) -> u32 {
34         u32::from_be(self.0.magic)
35     }
36 
37     /// Get dt_entry_count handling the bytes order
dt_entry_count(self) -> u3238     fn dt_entry_count(self) -> u32 {
39         u32::from_be(self.0.dt_entry_count)
40     }
41 
42     /// Get dt_entry_size handling the bytes order
dt_entry_size(self) -> u3243     fn dt_entry_size(self) -> u32 {
44         u32::from_be(self.0.dt_entry_size)
45     }
46 
47     /// Get dt_entries_offset handling the bytes order
dt_entries_offset(self) -> u3248     fn dt_entries_offset(self) -> u32 {
49         u32::from_be(self.0.dt_entries_offset)
50     }
51 }
52 
53 /// Rust wrapper for the dt table entry
54 #[repr(transparent)]
55 #[derive(Debug, Copy, Clone, AsBytes, FromBytes, FromZeroes, PartialEq)]
56 struct DtTableHeaderEntry(dt_table_entry);
57 
58 impl DtTableHeaderEntry {
59     /// Get id handling the bytes order
id(self) -> u3260     fn id(self) -> u32 {
61         u32::from_be(self.0.id)
62     }
63 
64     /// Get rev handling the bytes order
rev(self) -> u3265     fn rev(self) -> u32 {
66         u32::from_be(self.0.rev)
67     }
68 
69     /// Get dt_size handling the bytes order
dt_size(self) -> u3270     fn dt_size(self) -> u32 {
71         u32::from_be(self.0.dt_size)
72     }
73 
74     /// Get dt_offset handling the bytes order
dt_offset(self) -> u3275     fn dt_offset(self) -> u32 {
76         u32::from_be(self.0.dt_offset)
77     }
78 }
79 
80 /// Metadata provided by entry header
81 #[derive(Copy, Default, Clone, Eq, PartialEq, Debug)]
82 pub struct DtTableMetadata {
83     /// id field from corresponding entry header
84     pub id: u32,
85     /// rev field from corresponding entry header
86     pub rev: u32,
87     /// custom field from corresponding entry header
88     pub custom: [u32; 4],
89 }
90 
91 /// Device tree blob obtained from multidt table image
92 #[derive(Copy, Clone, Eq, PartialEq, Debug)]
93 pub struct DtTableEntry<'a> {
94     /// dtb payload extracted from image
95     pub dtb: &'a [u8],
96     /// Metadata provided by corresponding entry header
97     pub metadata: DtTableMetadata,
98 }
99 
100 /// Represents entier multidt table image
101 pub struct DtTableImage<'a> {
102     buffer: &'a [u8],
103     header: LayoutVerified<&'a [u8], DtTableHeader>,
104     entries: LayoutVerified<&'a [u8], [DtTableHeaderEntry]>,
105 }
106 
107 /// To iterate over entries.
108 pub struct DtTableImageIterator<'a> {
109     table_image: &'a DtTableImage<'a>,
110     current_index: usize,
111 }
112 
113 impl<'a> Iterator for DtTableImageIterator<'a> {
114     type Item = DtTableEntry<'a>;
115 
next(&mut self) -> Option<Self::Item>116     fn next(&mut self) -> Option<Self::Item> {
117         if self.current_index < self.table_image.entries_count() {
118             let result = self.table_image.nth_entry(self.current_index).unwrap();
119             self.current_index += 1;
120             Some(result)
121         } else {
122             None
123         }
124     }
125 }
126 
127 impl<'a> DtTableImage<'a> {
128     /// Verify and parse passed buffer following multidt table structure
from_bytes(buffer: &'a [u8]) -> Result<DtTableImage<'a>>129     pub fn from_bytes(buffer: &'a [u8]) -> Result<DtTableImage<'a>> {
130         let (header_layout, _) = LayoutVerified::new_from_prefix(buffer)
131             .ok_or(Error::BufferTooSmall(Some(size_of::<DtTableHeader>())))?;
132 
133         let header: &DtTableHeader = &header_layout;
134         if header.magic() != DT_TABLE_MAGIC {
135             return Err(Error::BadMagic);
136         }
137 
138         let entries_offset: SafeNum = header.dt_entries_offset().into();
139         let entry_size: SafeNum = header.dt_entry_size().into();
140         let entries_count: SafeNum = header.dt_entry_count().into();
141 
142         let entries_start = entries_offset.try_into()?;
143         let entries_end = (entries_offset + entry_size * entries_count).try_into()?;
144 
145         let entries_buffer = buffer
146             .get(entries_start..entries_end)
147             .ok_or(Error::BufferTooSmall(Some(entries_end)))?;
148         let entries_layout =
149             LayoutVerified::new_slice(entries_buffer).ok_or(Error::InvalidInput)?;
150 
151         Ok(DtTableImage { buffer: buffer, header: header_layout, entries: entries_layout })
152     }
153 
154     /// Get amount of presented dt entries in the multidt table image
entries_count(&self) -> usize155     pub fn entries_count(&self) -> usize {
156         self.header.dt_entry_count().try_into().unwrap()
157     }
158 
159     /// Returns an iterator over the entries in the DT table image
entries(&'a self) -> DtTableImageIterator<'a>160     pub fn entries(&'a self) -> DtTableImageIterator<'a> {
161         DtTableImageIterator { table_image: self, current_index: 0 }
162     }
163 
164     /// Get nth dtb buffer with multidt table structure metadata
nth_entry(&self, n: usize) -> Result<DtTableEntry<'a>>165     pub fn nth_entry(&self, n: usize) -> Result<DtTableEntry<'a>> {
166         let entry = self.entries.get(n).ok_or(Error::BadIndex(n))?;
167 
168         let dtb_offset: SafeNum = entry.dt_offset().into();
169         let dtb_size: SafeNum = entry.dt_size().into();
170 
171         let dtb_start: usize = dtb_offset.try_into()?;
172         let dtb_end: usize = (dtb_offset + dtb_size).try_into()?;
173 
174         let dtb_buffer =
175             self.buffer.get(dtb_start..dtb_end).ok_or(Error::BufferTooSmall(Some(dtb_end)))?;
176 
177         Ok(DtTableEntry {
178             dtb: dtb_buffer,
179             metadata: DtTableMetadata { id: entry.id(), rev: entry.rev(), custom: entry.0.custom },
180         })
181     }
182 }
183 
184 #[cfg(test)]
185 mod test {
186     use super::*;
187     use fdt::Fdt;
188 
189     #[test]
test_dt_table_is_parsed()190     fn test_dt_table_is_parsed() {
191         let dttable = include_bytes!("../test/data/dttable.img").to_vec();
192         let table = DtTableImage::from_bytes(&dttable[..]).unwrap();
193 
194         assert_eq!(table.entries_count(), 2, "Test data dttable image must have 2 dtb entries");
195 
196         let first_entry = table.nth_entry(0).unwrap();
197         let second_entry = table.nth_entry(1).unwrap();
198 
199         assert_eq!(
200             first_entry.metadata,
201             DtTableMetadata { id: 1, rev: 0, custom: Default::default() },
202             "First dttable entry is incorrect"
203         );
204         assert_eq!(
205             second_entry.metadata,
206             DtTableMetadata { id: 2, rev: 0, custom: Default::default() },
207             "Second dttable entry is incorrect"
208         );
209 
210         // verify fdt headers are properly parsed
211         let _ = Fdt::new(first_entry.dtb).unwrap();
212         let _ = Fdt::new(second_entry.dtb).unwrap();
213     }
214 
215     #[test]
test_dt_table_is_parsed_iterator()216     fn test_dt_table_is_parsed_iterator() {
217         let dttable = include_bytes!("../test/data/dttable.img").to_vec();
218         let table = DtTableImage::from_bytes(&dttable[..]).unwrap();
219 
220         // Collect entries from the iterator
221         let entries: Vec<_> = table.entries().collect();
222 
223         // Verify that the iterator yields the correct number of entries
224         assert_eq!(entries.len(), 2, "Iterator should yield 2 entries");
225 
226         // Unwrap the entries from Result
227         let first_entry = &entries[0];
228         let second_entry = &entries[1];
229 
230         assert_eq!(
231             first_entry.metadata,
232             DtTableMetadata { id: 1, rev: 0, custom: Default::default() },
233             "First dttable entry metadata is incorrect"
234         );
235         assert_eq!(
236             second_entry.metadata,
237             DtTableMetadata { id: 2, rev: 0, custom: Default::default() },
238             "Second dttable entry metadata is incorrect"
239         );
240 
241         // Verify FDT headers are properly parsed
242         let _ = Fdt::new(first_entry.dtb).unwrap();
243         let _ = Fdt::new(second_entry.dtb).unwrap();
244     }
245 
246     #[test]
test_failed_to_parse_corrupted_dt_table()247     fn test_failed_to_parse_corrupted_dt_table() {
248         let dttable = include_bytes!("../test/data/corrupted_dttable.img").to_vec();
249 
250         assert!(
251             DtTableImage::from_bytes(&dttable[..]).is_err(),
252             "Must fail when trying to parse corrupted dt table image"
253         );
254     }
255 }
256