blob: d52d7effcfcefc22e410889f33b79c8969cbb815 [file]
// Copyright 2024, The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Image decompression support.
// gzip [DeflateDecoder] requires heap allocation. LZ4 decompression currently uses the heap but
// could potentially be adjusted to use preallocated buffers if necessary.
extern crate alloc;
use crate::{gbl_print, gbl_println, GblOps};
use liberror::{Error, Result};
use lz4_flex::decompress_into;
use zune_inflate::DeflateDecoder;
/// Decompresses the given kernel if necessary
///
/// The possibly-compressed kernel starts in `buffer`. If it's compressed, it will be decompressed
/// using heap memory and then copied back into the end of `buffer`.
///
/// # Returns
/// The offset of the decompressed kernel in `buffer`. If the kernel was not compressed. this
/// function is a no-op and will return `kernel_start` unchanged.
pub fn decompress_kernel<'a>(
ops: &mut impl GblOps<'a>,
buffer: &mut [u8],
kernel_start: usize,
) -> Result<usize> {
if buffer[kernel_start..kernel_start + 2] == [0x1f, 0x8b] {
gbl_println!(ops, "kernel is gzip compressed");
let mut decoder = DeflateDecoder::new(&buffer[kernel_start..]);
let decompressed_data = match decoder.decode_gzip() {
Ok(decompressed_data) => decompressed_data,
_ => {
return Err(Error::InvalidInput.into());
}
};
gbl_println!(ops, "kernel decompressed size {}", decompressed_data.len());
let kernel_start = buffer.len() - decompressed_data.len();
// Move decompressed data to slice.
buffer[kernel_start..].clone_from_slice(&decompressed_data);
Ok(kernel_start)
} else if buffer[kernel_start..kernel_start + 4] == [0x02, 0x21, 0x4c, 0x18] {
gbl_println!(ops, "kernel is lz4 compressed");
let kernel_tail_buffer = &buffer[kernel_start..];
let mut contents = &kernel_tail_buffer[4..];
let mut decompressed_kernel = alloc::vec::Vec::new();
loop {
if contents.len() < 4 {
if contents.len() != 0 {
gbl_println!(ops, "Error: some leftover data in the content");
}
break;
}
let block_size: usize =
u32::from_le_bytes(contents[0..4].try_into().unwrap()).try_into().unwrap();
let block;
(block, contents) = contents.split_at(block_size + 4);
let block = &block[4..];
// extend decompressed kernel buffer by 8MB
let decompressed_kernel_len = decompressed_kernel.len();
decompressed_kernel.resize(decompressed_kernel_len + 8 * 1024 * 1024, 0);
// decompress the block
let decompressed_data_size =
decompress_into(&block, &mut decompressed_kernel[decompressed_kernel_len..])
.unwrap();
// reduce the size of decompressed kernel buffer
decompressed_kernel.resize(decompressed_kernel_len + decompressed_data_size, 0);
}
gbl_println!(ops, "kernel decompressed size {}", decompressed_kernel.len());
let kernel_start = buffer.len() - decompressed_kernel.len();
// Move decompressed data to slice
buffer[kernel_start..].clone_from_slice(&decompressed_kernel);
Ok(kernel_start)
} else {
Ok(kernel_start)
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::ops::test::FakeGblOps;
#[test]
fn decompress_kernel_lz4() {
let original_data = "Test TTTTTTTTT 123";
let compressed_data = [
0x02, 0x21, 0x4c, 0x18, 0x0f, 0x00, 0x00, 0x00, 0x63, 0x54, 0x65, 0x73, 0x74, 0x20,
0x54, 0x01, 0x00, 0x50, 0x54, 0x20, 0x31, 0x32, 0x33,
];
// Create a buffer with the compressed data at the end.
let mut buffer = vec![0u8; 8 * 1024];
let compressed_offset = buffer.len() - compressed_data.len();
buffer[compressed_offset..].clone_from_slice(&compressed_data[..]);
let offset =
decompress_kernel(&mut FakeGblOps::default(), &mut buffer[..], compressed_offset)
.unwrap();
assert_eq!(&buffer[offset..], original_data.as_bytes());
}
}