xref: /aosp_15_r20/bootable/libbootloader/gbl/libgbl/src/decompress.rs (revision 5225e6b173e52d2efc6bcf950c27374fd72adabc)
1*5225e6b1SAndroid Build Coastguard Worker // Copyright 2024, The Android Open Source Project
2*5225e6b1SAndroid Build Coastguard Worker //
3*5225e6b1SAndroid Build Coastguard Worker // Licensed under the Apache License, Version 2.0 (the "License");
4*5225e6b1SAndroid Build Coastguard Worker // you may not use this file except in compliance with the License.
5*5225e6b1SAndroid Build Coastguard Worker // You may obtain a copy of the License at
6*5225e6b1SAndroid Build Coastguard Worker //
7*5225e6b1SAndroid Build Coastguard Worker //     http://www.apache.org/licenses/LICENSE-2.0
8*5225e6b1SAndroid Build Coastguard Worker //
9*5225e6b1SAndroid Build Coastguard Worker // Unless required by applicable law or agreed to in writing, software
10*5225e6b1SAndroid Build Coastguard Worker // distributed under the License is distributed on an "AS IS" BASIS,
11*5225e6b1SAndroid Build Coastguard Worker // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12*5225e6b1SAndroid Build Coastguard Worker // See the License for the specific language governing permissions and
13*5225e6b1SAndroid Build Coastguard Worker // limitations under the License.
14*5225e6b1SAndroid Build Coastguard Worker 
15*5225e6b1SAndroid Build Coastguard Worker //! Image decompression support.
16*5225e6b1SAndroid Build Coastguard Worker 
17*5225e6b1SAndroid Build Coastguard Worker // gzip [DeflateDecoder] requires heap allocation. LZ4 decompression currently uses the heap but
18*5225e6b1SAndroid Build Coastguard Worker // could potentially be adjusted to use preallocated buffers if necessary.
19*5225e6b1SAndroid Build Coastguard Worker extern crate alloc;
20*5225e6b1SAndroid Build Coastguard Worker 
21*5225e6b1SAndroid Build Coastguard Worker use crate::{gbl_print, gbl_println, GblOps};
22*5225e6b1SAndroid Build Coastguard Worker use liberror::{Error, Result};
23*5225e6b1SAndroid Build Coastguard Worker use lz4_flex::decompress_into;
24*5225e6b1SAndroid Build Coastguard Worker use zune_inflate::DeflateDecoder;
25*5225e6b1SAndroid Build Coastguard Worker 
26*5225e6b1SAndroid Build Coastguard Worker /// Decompresses the given kernel if necessary
27*5225e6b1SAndroid Build Coastguard Worker ///
28*5225e6b1SAndroid Build Coastguard Worker /// The possibly-compressed kernel starts in `buffer`. If it's compressed, it will be decompressed
29*5225e6b1SAndroid Build Coastguard Worker /// using heap memory and then copied back into the end of `buffer`.
30*5225e6b1SAndroid Build Coastguard Worker ///
31*5225e6b1SAndroid Build Coastguard Worker /// # Returns
32*5225e6b1SAndroid Build Coastguard Worker /// The offset of the decompressed kernel in `buffer`. If the kernel was not compressed. this
33*5225e6b1SAndroid Build Coastguard Worker /// function is a no-op and will return `kernel_start` unchanged.
decompress_kernel<'a, 'b>( ops: &mut impl GblOps<'a, 'b>, buffer: &mut [u8], kernel_start: usize, ) -> Result<usize>34*5225e6b1SAndroid Build Coastguard Worker pub fn decompress_kernel<'a, 'b>(
35*5225e6b1SAndroid Build Coastguard Worker     ops: &mut impl GblOps<'a, 'b>,
36*5225e6b1SAndroid Build Coastguard Worker     buffer: &mut [u8],
37*5225e6b1SAndroid Build Coastguard Worker     kernel_start: usize,
38*5225e6b1SAndroid Build Coastguard Worker ) -> Result<usize> {
39*5225e6b1SAndroid Build Coastguard Worker     if buffer[kernel_start..kernel_start + 2] == [0x1f, 0x8b] {
40*5225e6b1SAndroid Build Coastguard Worker         gbl_println!(ops, "kernel is gzip compressed");
41*5225e6b1SAndroid Build Coastguard Worker         let mut decoder = DeflateDecoder::new(&buffer[kernel_start..]);
42*5225e6b1SAndroid Build Coastguard Worker         let decompressed_data = match decoder.decode_gzip() {
43*5225e6b1SAndroid Build Coastguard Worker             Ok(decompressed_data) => decompressed_data,
44*5225e6b1SAndroid Build Coastguard Worker             _ => {
45*5225e6b1SAndroid Build Coastguard Worker                 return Err(Error::InvalidInput.into());
46*5225e6b1SAndroid Build Coastguard Worker             }
47*5225e6b1SAndroid Build Coastguard Worker         };
48*5225e6b1SAndroid Build Coastguard Worker         gbl_println!(ops, "kernel decompressed size {}", decompressed_data.len());
49*5225e6b1SAndroid Build Coastguard Worker         let kernel_start = buffer.len() - decompressed_data.len();
50*5225e6b1SAndroid Build Coastguard Worker         // Move decompressed data to slice.
51*5225e6b1SAndroid Build Coastguard Worker         buffer[kernel_start..].clone_from_slice(&decompressed_data);
52*5225e6b1SAndroid Build Coastguard Worker         Ok(kernel_start)
53*5225e6b1SAndroid Build Coastguard Worker     } else if buffer[kernel_start..kernel_start + 4] == [0x02, 0x21, 0x4c, 0x18] {
54*5225e6b1SAndroid Build Coastguard Worker         gbl_println!(ops, "kernel is lz4 compressed");
55*5225e6b1SAndroid Build Coastguard Worker         let kernel_tail_buffer = &buffer[kernel_start..];
56*5225e6b1SAndroid Build Coastguard Worker         let mut contents = &kernel_tail_buffer[4..];
57*5225e6b1SAndroid Build Coastguard Worker         let mut decompressed_kernel = alloc::vec::Vec::new();
58*5225e6b1SAndroid Build Coastguard Worker         loop {
59*5225e6b1SAndroid Build Coastguard Worker             if contents.len() < 4 {
60*5225e6b1SAndroid Build Coastguard Worker                 if contents.len() != 0 {
61*5225e6b1SAndroid Build Coastguard Worker                     gbl_println!(ops, "Error: some leftover data in the content");
62*5225e6b1SAndroid Build Coastguard Worker                 }
63*5225e6b1SAndroid Build Coastguard Worker                 break;
64*5225e6b1SAndroid Build Coastguard Worker             }
65*5225e6b1SAndroid Build Coastguard Worker             let block_size: usize =
66*5225e6b1SAndroid Build Coastguard Worker                 u32::from_le_bytes(contents[0..4].try_into().unwrap()).try_into().unwrap();
67*5225e6b1SAndroid Build Coastguard Worker             let block;
68*5225e6b1SAndroid Build Coastguard Worker             (block, contents) = contents.split_at(block_size + 4);
69*5225e6b1SAndroid Build Coastguard Worker             let block = &block[4..];
70*5225e6b1SAndroid Build Coastguard Worker             // extend decompressed kernel buffer by 8MB
71*5225e6b1SAndroid Build Coastguard Worker             let decompressed_kernel_len = decompressed_kernel.len();
72*5225e6b1SAndroid Build Coastguard Worker             decompressed_kernel.resize(decompressed_kernel_len + 8 * 1024 * 1024, 0);
73*5225e6b1SAndroid Build Coastguard Worker             // decompress the block
74*5225e6b1SAndroid Build Coastguard Worker             let decompressed_data_size =
75*5225e6b1SAndroid Build Coastguard Worker                 decompress_into(&block, &mut decompressed_kernel[decompressed_kernel_len..])
76*5225e6b1SAndroid Build Coastguard Worker                     .unwrap();
77*5225e6b1SAndroid Build Coastguard Worker             // reduce the size of decompressed kernel buffer
78*5225e6b1SAndroid Build Coastguard Worker             decompressed_kernel.resize(decompressed_kernel_len + decompressed_data_size, 0);
79*5225e6b1SAndroid Build Coastguard Worker         }
80*5225e6b1SAndroid Build Coastguard Worker         gbl_println!(ops, "kernel decompressed size {}", decompressed_kernel.len());
81*5225e6b1SAndroid Build Coastguard Worker         let kernel_start = buffer.len() - decompressed_kernel.len();
82*5225e6b1SAndroid Build Coastguard Worker         // Move decompressed data to slice
83*5225e6b1SAndroid Build Coastguard Worker         buffer[kernel_start..].clone_from_slice(&decompressed_kernel);
84*5225e6b1SAndroid Build Coastguard Worker         Ok(kernel_start)
85*5225e6b1SAndroid Build Coastguard Worker     } else {
86*5225e6b1SAndroid Build Coastguard Worker         Ok(kernel_start)
87*5225e6b1SAndroid Build Coastguard Worker     }
88*5225e6b1SAndroid Build Coastguard Worker }
89*5225e6b1SAndroid Build Coastguard Worker 
90*5225e6b1SAndroid Build Coastguard Worker #[cfg(test)]
91*5225e6b1SAndroid Build Coastguard Worker mod test {
92*5225e6b1SAndroid Build Coastguard Worker     use super::*;
93*5225e6b1SAndroid Build Coastguard Worker     use crate::ops::test::FakeGblOps;
94*5225e6b1SAndroid Build Coastguard Worker 
95*5225e6b1SAndroid Build Coastguard Worker     #[test]
decompress_kernel_lz4()96*5225e6b1SAndroid Build Coastguard Worker     fn decompress_kernel_lz4() {
97*5225e6b1SAndroid Build Coastguard Worker         let original_data = "Test TTTTTTTTT 123";
98*5225e6b1SAndroid Build Coastguard Worker         let compressed_data = [
99*5225e6b1SAndroid Build Coastguard Worker             0x02, 0x21, 0x4c, 0x18, 0x0f, 0x00, 0x00, 0x00, 0x63, 0x54, 0x65, 0x73, 0x74, 0x20,
100*5225e6b1SAndroid Build Coastguard Worker             0x54, 0x01, 0x00, 0x50, 0x54, 0x20, 0x31, 0x32, 0x33,
101*5225e6b1SAndroid Build Coastguard Worker         ];
102*5225e6b1SAndroid Build Coastguard Worker 
103*5225e6b1SAndroid Build Coastguard Worker         // Create a buffer with the compressed data at the end.
104*5225e6b1SAndroid Build Coastguard Worker         let mut buffer = vec![0u8; 8 * 1024];
105*5225e6b1SAndroid Build Coastguard Worker         let compressed_offset = buffer.len() - compressed_data.len();
106*5225e6b1SAndroid Build Coastguard Worker         buffer[compressed_offset..].clone_from_slice(&compressed_data[..]);
107*5225e6b1SAndroid Build Coastguard Worker 
108*5225e6b1SAndroid Build Coastguard Worker         let offset =
109*5225e6b1SAndroid Build Coastguard Worker             decompress_kernel(&mut FakeGblOps::default(), &mut buffer[..], compressed_offset)
110*5225e6b1SAndroid Build Coastguard Worker                 .unwrap();
111*5225e6b1SAndroid Build Coastguard Worker         assert_eq!(&buffer[offset..], original_data.as_bytes());
112*5225e6b1SAndroid Build Coastguard Worker     }
113*5225e6b1SAndroid Build Coastguard Worker }
114