wxtla 0.3.1

Wired eXploring Target Layer Accessor
Documentation
//! Sparseimage metadata parsing.

use std::sync::Arc;

use super::header::{HEADER_BLOCK_SIZE, SparseImageHeader};
use crate::{ByteSourceHandle, Error, Result};

pub(super) struct ParsedSparseImage {
  pub header: SparseImageHeader,
  pub media_size: u64,
  pub band_size: u64,
  pub guest_to_file_offsets: Arc<[Option<u64>]>,
  pub has_sparse_bands: bool,
}

pub(super) fn parse(source: ByteSourceHandle) -> Result<ParsedSparseImage> {
  let source_size = source.size()?;
  let (header, header_block) = SparseImageHeader::read(source.as_ref())?;
  let media_size = header.media_size()?;
  let band_size = header.band_size()?;
  let band_count = usize::try_from(header.band_count())
    .map_err(|_| Error::invalid_range("sparseimage band count is too large"))?;
  let array_bytes = band_count
    .checked_mul(4)
    .ok_or_else(|| Error::invalid_range("sparseimage band array size overflow"))?;
  let array_end = 64usize
    .checked_add(array_bytes)
    .ok_or_else(|| Error::invalid_range("sparseimage band array end overflow"))?;
  if array_end > HEADER_BLOCK_SIZE {
    return Err(Error::invalid_format(
      "sparseimage band array does not fit in the header block".to_string(),
    ));
  }
  if header_block[array_end..].iter().any(|&byte| byte != 0) {
    return Err(Error::invalid_format(
      "sparseimage header padding must be zero".to_string(),
    ));
  }

  let mut guest_to_file_offsets = vec![None; band_count];
  let mut has_sparse_bands = false;
  for array_index in 0..band_count {
    let start = 64 + array_index * 4;
    let band_number = u32::from_be_bytes([
      header_block[start],
      header_block[start + 1],
      header_block[start + 2],
      header_block[start + 3],
    ]);
    if band_number == 0 {
      has_sparse_bands = true;
      continue;
    }

    let guest_index = usize::try_from(band_number - 1)
      .map_err(|_| Error::invalid_range("sparseimage band index is too large"))?;
    if guest_index >= band_count {
      return Err(Error::invalid_format(
        "sparseimage band number is out of bounds".to_string(),
      ));
    }
    if guest_to_file_offsets[guest_index].is_some() {
      return Err(Error::invalid_format(
        "sparseimage band numbers must be unique".to_string(),
      ));
    }

    let file_offset = (HEADER_BLOCK_SIZE as u64)
      .checked_add(
        u64::try_from(array_index)
          .map_err(|_| Error::invalid_range("sparseimage file band index is too large"))?
          .checked_mul(band_size)
          .ok_or_else(|| Error::invalid_range("sparseimage band file offset overflow"))?,
      )
      .ok_or_else(|| Error::invalid_range("sparseimage band file offset overflow"))?;
    let band_end = file_offset
      .checked_add(band_size)
      .ok_or_else(|| Error::invalid_range("sparseimage band end overflow"))?;
    if band_end > source_size {
      return Err(Error::invalid_format(
        "sparseimage band data exceeds the source size".to_string(),
      ));
    }

    guest_to_file_offsets[guest_index] = Some(file_offset);
  }

  Ok(ParsedSparseImage {
    header,
    media_size,
    band_size,
    guest_to_file_offsets: Arc::from(guest_to_file_offsets),
    has_sparse_bands,
  })
}