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")]
225 #[cfg_attr(docsrs, doc(cfg(feature = "std")))]
226 pub fn full_write<W: Write + Seek, R: Read>(
227 &self,
228 writer: &mut W,
229 kernel: Option<&mut R>,
230 ramdisk: Option<&mut R>,
231 second_bootloader: Option<&mut R>,
232 recovery_dtbo: Option<&mut R>,
233 dtb: Option<&mut R>,
234 ) -> binrw::BinResult<()> {
235 let w = writer;
236
237 self.write(w)?;
238
239 if let Some(r) = kernel {
240 w.seek(SeekFrom::Start(self.kernel_position() as u64))?;
241 std::io::copy(r, w)?;
242 }
243
244 if let Some(r) = ramdisk {
245 w.seek(SeekFrom::Start(self.ramdisk_position() as u64))?;
246 std::io::copy(r, w)?;
247 }
248
249 if let Some(r) = second_bootloader {
250 w.seek(SeekFrom::Start(self.second_bootloader_position() as u64))?;
251 std::io::copy(r, w)?;
252 }
253
254 if let Some(r) = recovery_dtbo {
255 w.seek(SeekFrom::Start(self.recovery_dtbo_position() as u64))?;
256 std::io::copy(r, w)?;
257 }
258
259 if let Some(dtb_position) = self.dtb_position() {
260 if let Some(r) = dtb {
261 w.seek(SeekFrom::Start(dtb_position as u64))?;
262 std::io::copy(r, w)?;
263 }
264 }
265
266 w.seek(SeekFrom::Start(self.boot_image_size() as u64))?;
268
269 Ok(())
270 }
271}
272
273#[binrw]
275#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
276#[br(import(header_version: u32))]
277#[br(pre_assert([0,1,2].contains(&header_version), "invalid header version: {header_version}"))]
278pub enum HeaderV0Versioned {
279 #[br(pre_assert(header_version == 0))]
281 V0,
282 #[br(pre_assert(header_version == 1))]
284 V1 {
285 recovery_dtbo_size: u32,
287 recovery_dtbo_addr: u64,
289 #[br(temp, assert(header_size == 1648))]
290 #[bw(calc = 1648)]
291 header_size: u32,
292 },
293 #[br(pre_assert(header_version == 2))]
295 V2 {
296 recovery_dtbo_size: u32,
298 recovery_dtbo_addr: u64,
300 #[br(temp, assert(header_size == 1660))]
301 #[bw(calc = 1660)]
302 header_size: u32,
303 dtb_size: u32,
305 dtb_addr: u64,
307 },
308}
309
310#[binrw]
335#[derive(Clone, Debug, PartialEq, Eq, Hash)]
336#[brw(little, magic = b"ANDROID!")]
337#[br(assert(header_size == self.header_size(), "invalid header size: {header_size}"))]
338pub struct HeaderV3 {
339 pub kernel_size: u32,
341 pub ramdisk_size: u32,
343 pub osversionpatch: OsVersionPatch,
345 #[br(temp)]
346 #[bw(calc = self.header_size())]
347 header_size: u32,
348 #[brw(pad_before = 16)]
349 #[br(temp)]
350 #[br(assert(header_version == 3 || header_version == 4, "invalid header version: {header_version}"))]
351 #[bw(calc = self.header_version())]
352 header_version: u32,
353 pub cmdline: Box<[u8; 512 + 1024]>,
355 #[br(if(header_version == 4))]
359 pub v4_signature_size: Option<u32>,
360}
361
362impl HeaderV3 {
363 pub(crate) const PAGE_SIZE: usize = 4096;
364
365 #[must_use]
367 pub const fn header_version(&self) -> u32 {
368 if self.v4_signature_size.is_some() {
369 4
370 } else {
371 3
372 }
373 }
374 pub(crate) const fn header_size(&self) -> u32 {
375 if self.v4_signature_size.is_some() {
376 1584
377 } else {
378 1580
379 }
380 }
381 pub(crate) const fn get_padding(size: usize) -> usize {
382 (Self::PAGE_SIZE - (size % Self::PAGE_SIZE)) % Self::PAGE_SIZE
385 }
386 #[must_use]
390 pub const fn kernel_position() -> usize {
391 Self::PAGE_SIZE
392 }
393 #[must_use]
395 pub const fn ramdisk_position(&self) -> usize {
396 Self::kernel_position()
397 + self.kernel_size as usize
398 + Self::get_padding(self.kernel_size as usize)
399 }
400 #[must_use]
404 pub const fn bootsig_position(&self) -> usize {
405 self.ramdisk_position()
406 + self.ramdisk_size as usize
407 + Self::get_padding(self.ramdisk_size as usize)
408 }
409}
410
411#[derive(Clone, Debug, PartialEq, Eq, Hash)]
413pub enum Header {
414 V0(HeaderV0),
416 V3(HeaderV3),
418}
419
420impl Header {
421 pub fn parse<R: Read + Seek>(reader: &mut R) -> Result<Self, binrw::Error> {
427 reader.seek(binrw::io::SeekFrom::Start(0x28))?;
428 let mut version_buf = [0u8; 4];
429 reader.read_exact(&mut version_buf)?;
430 reader.seek(binrw::io::SeekFrom::Start(0))?;
431
432 Ok(match u32::from_le_bytes(version_buf) {
435 0..=2 => Self::V0(HeaderV0::read(reader)?),
436 3 | 4 => Self::V3(HeaderV3::read(reader)?),
437 version => {
438 return Err(binrw::Error::AssertFail {
439 pos: 0x28,
440 message: format!("Unknown header version: {version}"),
441 })
442 }
443 })
444 }
445 pub fn write<W: Write>(&self, writer: &mut W) -> Result<(), binrw::Error> {
453 let writer = &mut NoSeek::new(writer);
454 match self {
455 Self::V0(hdr) => hdr.write(writer),
456 Self::V3(hdr) => hdr.write(writer),
457 }
458 }
459 #[must_use]
461 pub const fn header_version(&self) -> u32 {
462 match self {
463 Self::V0(hdr) => hdr.header_version(),
464 Self::V3(hdr) => hdr.header_version(),
465 }
466 }
467 #[must_use]
469 pub const fn osversionpatch(&self) -> OsVersionPatch {
470 match self {
471 Self::V0(hdr) => hdr.osversionpatch,
472 Self::V3(hdr) => hdr.osversionpatch,
473 }
474 }
475 #[must_use]
477 pub const fn kernel_position(&self) -> usize {
478 match self {
479 Self::V0(hdr) => hdr.kernel_position(),
480 Self::V3(_) => HeaderV3::kernel_position(),
481 }
482 }
483 #[must_use]
485 pub const fn kernel_size(&self) -> u32 {
486 match self {
487 Self::V0(hdr) => hdr.kernel_size,
488 Self::V3(hdr) => hdr.kernel_size,
489 }
490 }
491 #[must_use]
493 pub const fn ramdisk_position(&self) -> usize {
494 match self {
495 Self::V0(hdr) => hdr.ramdisk_position(),
496 Self::V3(hdr) => hdr.ramdisk_position(),
497 }
498 }
499 #[must_use]
501 pub const fn ramdisk_size(&self) -> u32 {
502 match self {
503 Self::V0(hdr) => hdr.ramdisk_size,
504 Self::V3(hdr) => hdr.ramdisk_size,
505 }
506 }
507 #[must_use]
509 pub const fn page_size(&self) -> usize {
510 match self {
511 Self::V0(hdr) => hdr.page_size as usize,
512 Self::V3(_) => HeaderV3::PAGE_SIZE,
513 }
514 }
515 #[must_use]
517 pub const fn cmdline(&self) -> &[u8; 512 + 1024] {
518 match self {
519 Self::V0(hdr) => &hdr.cmdline,
520 Self::V3(hdr) => &hdr.cmdline,
521 }
522 }
523}
524
525#[cfg(test)]
526mod tests {
527 use alloc::vec::Vec;
528 use binrw::io::Cursor;
529 use expect_test_bytes::expect_file;
530
531 use super::*;
532
533 #[test]
534 fn simple_write_read() {
535 fn pad_slice_to_array<const N: usize>(slice: &[u8]) -> [u8; N] {
536 let mut arr = [0u8; N];
537 let len = slice.len().min(N);
538 arr[..len].copy_from_slice(&slice[..len]);
539 arr
540 }
541 let expected_header = Header::V3(HeaderV3 {
542 kernel_size: 0x7357_0001,
543 ramdisk_size: 0x7357_0002,
544 osversionpatch: OsVersionPatch(0x7357_0003),
545 cmdline: Box::new(pad_slice_to_array(b"example")),
546 v4_signature_size: None,
547 });
548
549 let mut actual_bytes = Vec::new();
550 expected_header
551 .write(&mut Cursor::new(&mut actual_bytes))
552 .unwrap();
553
554 expect_file!["test_data/standard/simple_write_read"].assert_eq(&actual_bytes);
555
556 let actual_header = Header::parse(&mut Cursor::new(&actual_bytes)).unwrap();
557
558 assert_eq!(expected_header, actual_header);
559
560 let either_header = crate::EitherHeader::read(&mut Cursor::new(&actual_bytes)).unwrap();
561
562 assert_eq!(
563 crate::EitherHeader::Standard(expected_header),
564 either_header
565 );
566 }
567}