1use alloc::{boxed::Box, format};
2use binrw::{
3 binrw,
4 io::{NoSeek, Read, Seek, SeekFrom, Write},
5 BinRead, BinWrite,
6};
7
8use crate::version::OsVersionPatch;
9
10#[binrw]
49#[derive(Clone, Debug, PartialEq, Eq, Hash)]
50#[brw(little, magic = b"ANDROID!")]
51pub struct HeaderV0 {
52 pub kernel_size: u32,
54 pub kernel_addr: u32,
56 pub ramdisk_size: u32,
58 pub ramdisk_addr: u32,
60 pub second_bootloader_size: u32,
62 pub second_bootloader_addr: u32,
64 pub tags_addr: u32,
66 pub page_size: u32,
68 #[br(temp)]
70 #[bw(calc = self.header_version())]
71 header_version: u32,
72 pub osversionpatch: OsVersionPatch,
74 pub board_name: [u8; 16],
76 #[br(temp)]
77 #[bw(calc = *self.cmdline.first_chunk().unwrap())]
78 cmdline_part_1: [u8; 512],
79 pub hash_digest: [u8; 32],
86 #[br(temp)]
87 #[bw(calc = *self.cmdline.last_chunk().unwrap())]
88 cmdline_part_2: [u8; 1024],
89 #[br(calc = [cmdline_part_1.as_slice(), cmdline_part_2.as_slice()].concat().try_into().unwrap())]
91 #[bw(ignore)]
92 pub cmdline: Box<[u8; 512 + 1024]>,
93 #[br(args(header_version))]
95 pub versioned: HeaderV0Versioned,
96}
97
98impl HeaderV0 {
99 pub(crate) const fn get_padding(&self, size: usize) -> usize {
100 let page_size = self.page_size as usize;
101 (page_size - (size % page_size)) % page_size
102 }
103 #[must_use]
105 pub const fn header_version(&self) -> u32 {
106 match self.versioned {
107 HeaderV0Versioned::V0 => 0,
108 HeaderV0Versioned::V1 { .. } => 1,
109 HeaderV0Versioned::V2 { .. } => 2,
110 }
111 }
112 #[must_use]
114 pub const fn kernel_position(&self) -> usize {
115 1660 + self.get_padding(1660)
116 }
117 #[must_use]
119 pub const fn ramdisk_position(&self) -> usize {
120 self.kernel_position()
121 + self.kernel_size as usize
122 + self.get_padding(self.kernel_size as usize)
123 }
124 #[must_use]
126 pub const fn second_bootloader_position(&self) -> usize {
127 self.ramdisk_position()
128 + self.ramdisk_size as usize
129 + self.get_padding(self.ramdisk_size as usize)
130 }
131 #[must_use]
133 pub const fn recovery_dtbo_position(&self) -> usize {
134 self.second_bootloader_position()
135 + self.second_bootloader_size as usize
136 + self.get_padding(self.second_bootloader_size as usize)
137 }
138 #[must_use]
144 pub const fn dtb_position(&self) -> Option<usize> {
145 match self.versioned {
146 HeaderV0Versioned::V0 => None,
147 HeaderV0Versioned::V1 {
148 recovery_dtbo_size, ..
149 }
150 | HeaderV0Versioned::V2 {
151 recovery_dtbo_size, ..
152 } => Some(
153 self.second_bootloader_position()
154 + recovery_dtbo_size as usize
155 + self.get_padding(recovery_dtbo_size as usize),
156 ),
157 }
158 }
159 #[must_use]
161 #[expect(
162 clippy::missing_panics_doc,
163 reason = "dtb_position always returns Some on V1 and V2"
164 )]
165 pub const fn boot_image_size(&self) -> usize {
166 match self.versioned {
167 HeaderV0Versioned::V0 => self.recovery_dtbo_position(),
168 HeaderV0Versioned::V1 { .. } => self.dtb_position().unwrap(),
169 HeaderV0Versioned::V2 { dtb_size, .. } => {
170 self.dtb_position().unwrap()
171 + dtb_size as usize
172 + self.get_padding(dtb_size as usize)
173 }
174 }
175 }
176
177 #[cfg(feature = "hash")]
184 #[cfg_attr(docsrs, doc(cfg(feature = "hash")))]
185 pub fn compute_hash_digest<R: Read, D: digest::Digest>(
186 kernel: Option<&mut R>,
187 ramdisk: Option<&mut R>,
188 second_bootloader: Option<&mut R>,
189 recovery_dtbo: Option<&mut R>,
190 dtb: Option<&mut R>,
191 ) -> binrw::io::Result<[u8; 32]> {
192 let mut hasher = D::new();
193
194 for r in [kernel, ramdisk, second_bootloader, recovery_dtbo, dtb] {
195 if let Some(r) = r {
196 let mut buf = alloc::vec::Vec::new();
197 r.read_to_end(&mut buf)?;
198 hasher.update(&buf);
199 hasher.update(
200 u32::try_from(buf.len())
201 .map_err(|_| binrw::io::ErrorKind::InvalidInput)?
202 .to_le_bytes(),
203 );
204 } else {
205 hasher.update(0u32.to_le_bytes());
206 }
207 }
208
209 let digest = hasher.finalize();
210 let mut buf = [0; _];
211 buf[..digest.len()].copy_from_slice(&digest);
212 Ok(buf)
213 }
214
215 #[cfg(feature = "std")]
228 #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
229 pub fn full_write<W: Write + Seek, R: Read>(
230 &self,
231 writer: &mut W,
232 kernel: Option<&mut R>,
233 ramdisk: Option<&mut R>,
234 second_bootloader: Option<&mut R>,
235 recovery_dtbo: Option<&mut R>,
236 dtb: Option<&mut R>,
237 ) -> binrw::BinResult<()> {
238 let w = writer;
239
240 self.write(w)?;
241
242 if let Some(r) = kernel {
243 w.seek(SeekFrom::Start(self.kernel_position() as u64))?;
244 std::io::copy(r, w)?;
245 }
246
247 if let Some(r) = ramdisk {
248 w.seek(SeekFrom::Start(self.ramdisk_position() as u64))?;
249 std::io::copy(r, w)?;
250 }
251
252 if let Some(r) = second_bootloader {
253 w.seek(SeekFrom::Start(self.second_bootloader_position() as u64))?;
254 std::io::copy(r, w)?;
255 }
256
257 if let Some(r) = recovery_dtbo {
258 w.seek(SeekFrom::Start(self.recovery_dtbo_position() as u64))?;
259 std::io::copy(r, w)?;
260 }
261
262 if let Some(dtb_position) = self.dtb_position() {
263 if let Some(r) = dtb {
264 w.seek(SeekFrom::Start(dtb_position as u64))?;
265 std::io::copy(r, w)?;
266 }
267 }
268
269 w.seek(SeekFrom::Start(self.boot_image_size() as u64))?;
271
272 Ok(())
273 }
274}
275
276#[binrw]
278#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
279#[br(import(header_version: u32))]
280#[br(pre_assert([0,1,2].contains(&header_version), "invalid header version: {header_version}"))]
281pub enum HeaderV0Versioned {
282 #[br(pre_assert(header_version == 0))]
284 V0,
285 #[br(pre_assert(header_version == 1))]
287 V1 {
288 recovery_dtbo_size: u32,
290 recovery_dtbo_addr: u64,
292 #[br(temp, assert(header_size == 1648))]
293 #[bw(calc = 1648)]
294 header_size: u32,
295 },
296 #[br(pre_assert(header_version == 2))]
298 V2 {
299 recovery_dtbo_size: u32,
301 recovery_dtbo_addr: u64,
303 #[br(temp, assert(header_size == 1660))]
304 #[bw(calc = 1660)]
305 header_size: u32,
306 dtb_size: u32,
308 dtb_addr: u64,
310 },
311}
312
313#[binrw]
338#[derive(Clone, Debug, PartialEq, Eq, Hash)]
339#[brw(little, magic = b"ANDROID!")]
340#[br(assert(header_size == self.header_size(), "invalid header size: {header_size}"))]
341pub struct HeaderV3 {
342 pub kernel_size: u32,
344 pub ramdisk_size: u32,
346 pub osversionpatch: OsVersionPatch,
348 #[br(temp)]
349 #[bw(calc = self.header_size())]
350 header_size: u32,
351 #[brw(pad_before = 16)]
352 #[br(temp)]
353 #[br(assert(header_version == 3 || header_version == 4, "invalid header version: {header_version}"))]
354 #[bw(calc = self.header_version())]
355 header_version: u32,
356 pub cmdline: Box<[u8; 512 + 1024]>,
358 #[br(if(header_version == 4))]
362 pub v4_signature_size: Option<u32>,
363}
364
365impl HeaderV3 {
366 pub(crate) const PAGE_SIZE: usize = 4096;
367
368 #[must_use]
370 pub const fn header_version(&self) -> u32 {
371 if self.v4_signature_size.is_some() {
372 4
373 } else {
374 3
375 }
376 }
377 pub(crate) const fn header_size(&self) -> u32 {
378 if self.v4_signature_size.is_some() {
379 1584
380 } else {
381 1580
382 }
383 }
384 pub(crate) const fn get_padding(size: usize) -> usize {
385 (Self::PAGE_SIZE - (size % Self::PAGE_SIZE)) % Self::PAGE_SIZE
388 }
389 #[must_use]
393 pub const fn kernel_position() -> usize {
394 Self::PAGE_SIZE
395 }
396 #[must_use]
398 pub const fn ramdisk_position(&self) -> usize {
399 Self::kernel_position()
400 + self.kernel_size as usize
401 + Self::get_padding(self.kernel_size as usize)
402 }
403 #[must_use]
407 pub const fn bootsig_position(&self) -> usize {
408 self.ramdisk_position()
409 + self.ramdisk_size as usize
410 + Self::get_padding(self.ramdisk_size as usize)
411 }
412}
413
414#[derive(Clone, Debug, PartialEq, Eq, Hash)]
416pub enum Header {
417 V0(HeaderV0),
419 V3(HeaderV3),
421}
422
423impl Header {
424 pub fn parse<R: Read + Seek>(reader: &mut R) -> Result<Self, binrw::Error> {
430 reader.seek(binrw::io::SeekFrom::Start(0x28))?;
431 let mut version_buf = [0u8; 4];
432 reader.read_exact(&mut version_buf)?;
433 reader.seek(binrw::io::SeekFrom::Start(0))?;
434
435 Ok(match u32::from_le_bytes(version_buf) {
438 0..=2 => Self::V0(HeaderV0::read(reader)?),
439 3 | 4 => Self::V3(HeaderV3::read(reader)?),
440 version => {
441 return Err(binrw::Error::AssertFail {
442 pos: 0x28,
443 message: format!("Unknown header version: {version}"),
444 })
445 }
446 })
447 }
448 pub fn write<W: Write>(&self, writer: &mut W) -> Result<(), binrw::Error> {
456 let writer = &mut NoSeek::new(writer);
457 match self {
458 Self::V0(hdr) => hdr.write(writer),
459 Self::V3(hdr) => hdr.write(writer),
460 }
461 }
462 #[must_use]
464 pub const fn header_version(&self) -> u32 {
465 match self {
466 Self::V0(hdr) => hdr.header_version(),
467 Self::V3(hdr) => hdr.header_version(),
468 }
469 }
470 #[must_use]
472 pub const fn osversionpatch(&self) -> OsVersionPatch {
473 match self {
474 Self::V0(hdr) => hdr.osversionpatch,
475 Self::V3(hdr) => hdr.osversionpatch,
476 }
477 }
478 #[must_use]
480 pub const fn kernel_position(&self) -> usize {
481 match self {
482 Self::V0(hdr) => hdr.kernel_position(),
483 Self::V3(_) => HeaderV3::kernel_position(),
484 }
485 }
486 #[must_use]
488 pub const fn kernel_size(&self) -> u32 {
489 match self {
490 Self::V0(hdr) => hdr.kernel_size,
491 Self::V3(hdr) => hdr.kernel_size,
492 }
493 }
494 #[must_use]
496 pub const fn ramdisk_position(&self) -> usize {
497 match self {
498 Self::V0(hdr) => hdr.ramdisk_position(),
499 Self::V3(hdr) => hdr.ramdisk_position(),
500 }
501 }
502 #[must_use]
504 pub const fn ramdisk_size(&self) -> u32 {
505 match self {
506 Self::V0(hdr) => hdr.ramdisk_size,
507 Self::V3(hdr) => hdr.ramdisk_size,
508 }
509 }
510 #[must_use]
512 pub const fn page_size(&self) -> usize {
513 match self {
514 Self::V0(hdr) => hdr.page_size as usize,
515 Self::V3(_) => HeaderV3::PAGE_SIZE,
516 }
517 }
518 #[must_use]
520 pub const fn cmdline(&self) -> &[u8; 512 + 1024] {
521 match self {
522 Self::V0(hdr) => &hdr.cmdline,
523 Self::V3(hdr) => &hdr.cmdline,
524 }
525 }
526}
527
528#[cfg(test)]
529mod tests {
530 use alloc::vec::Vec;
531 use binrw::io::Cursor;
532 use expect_test_bytes::expect_file;
533
534 use super::*;
535
536 #[test]
537 fn simple_write_read() {
538 fn pad_slice_to_array<const N: usize>(slice: &[u8]) -> [u8; N] {
539 let mut arr = [0u8; N];
540 let len = slice.len().min(N);
541 arr[..len].copy_from_slice(&slice[..len]);
542 arr
543 }
544 let expected_header = Header::V3(HeaderV3 {
545 kernel_size: 0x7357_0001,
546 ramdisk_size: 0x7357_0002,
547 osversionpatch: OsVersionPatch(0x7357_0003),
548 cmdline: Box::new(pad_slice_to_array(b"example")),
549 v4_signature_size: None,
550 });
551
552 let mut actual_bytes = Vec::new();
553 expected_header
554 .write(&mut Cursor::new(&mut actual_bytes))
555 .unwrap();
556
557 expect_file!["test_data/standard/simple_write_read"].assert_eq(&actual_bytes);
558
559 let actual_header = Header::parse(&mut Cursor::new(&actual_bytes)).unwrap();
560
561 assert_eq!(expected_header, actual_header);
562
563 let either_header = crate::EitherHeader::read(&mut Cursor::new(&actual_bytes)).unwrap();
564
565 assert_eq!(
566 crate::EitherHeader::Standard(expected_header),
567 either_header
568 );
569 }
570}