wxtla 0.3.1

Wired eXploring Target Layer Accessor
Documentation
//! Validation of parsed GPT layouts.

use super::{entry::GptPartitionInfo, header::GptHeader};
use crate::{Error, Result};

pub(super) fn validate_layout(
  source_size: u64, block_size: u32, expected_current_lba: u64, header: &GptHeader,
  partitions: &[GptPartitionInfo],
) -> Result<()> {
  validate_header(source_size, block_size, expected_current_lba, header)?;
  validate_partitions(source_size, block_size, header, partitions)?;
  Ok(())
}

fn validate_header(
  source_size: u64, block_size: u32, expected_current_lba: u64, header: &GptHeader,
) -> Result<()> {
  if header.current_lba != expected_current_lba {
    return Err(Error::invalid_format(format!(
      "gpt header is at unexpected lba {}",
      header.current_lba
    )));
  }
  if header.first_usable_lba > header.last_usable_lba {
    return Err(Error::invalid_format(
      "gpt usable lba range is invalid".to_string(),
    ));
  }

  let total_blocks = source_size / u64::from(block_size);
  if total_blocks <= header.current_lba || header.backup_lba >= total_blocks {
    return Err(Error::invalid_format(
      "gpt backup header lba is out of bounds".to_string(),
    ));
  }

  let entry_bytes = u64::from(header.entry_count)
    .checked_mul(u64::from(header.entry_size))
    .ok_or_else(|| Error::invalid_range("gpt entry array size overflow"))?;
  let entry_offset = header
    .entry_array_start_lba
    .checked_mul(u64::from(block_size))
    .ok_or_else(|| Error::invalid_range("gpt entry array offset overflow"))?;
  let entry_end = entry_offset
    .checked_add(entry_bytes)
    .ok_or_else(|| Error::invalid_range("gpt entry array end overflow"))?;
  if entry_end > source_size {
    return Err(Error::invalid_format(
      "gpt entry array exceeds source size".to_string(),
    ));
  }

  Ok(())
}

fn validate_partitions(
  source_size: u64, _block_size: u32, header: &GptHeader, partitions: &[GptPartitionInfo],
) -> Result<()> {
  for partition in partitions {
    if partition.first_lba < header.first_usable_lba || partition.last_lba > header.last_usable_lba
    {
      return Err(Error::invalid_format(format!(
        "gpt partition {} lies outside the usable area",
        partition.record.index
      )));
    }
    let Some(end_offset) = partition.record.span.end_offset() else {
      return Err(Error::invalid_range(
        "gpt partition end offset overflow".to_string(),
      ));
    };
    if end_offset > source_size {
      return Err(Error::invalid_format(format!(
        "gpt partition {} exceeds source size",
        partition.record.index
      )));
    }
  }

  let mut spans = partitions
    .iter()
    .map(|partition| {
      Ok((
        partition.record.index,
        partition.record.span.byte_offset,
        partition
          .record
          .span
          .end_offset()
          .ok_or_else(|| Error::invalid_range("gpt partition end offset overflow"))?,
      ))
    })
    .collect::<Result<Vec<_>>>()?;
  spans.sort_unstable_by_key(|(_, start, _)| *start);
  for pair in spans.windows(2) {
    if pair[1].1 < pair[0].2 {
      return Err(Error::invalid_format(format!(
        "gpt partitions {} and {} overlap",
        pair[0].0, pair[1].0
      )));
    }
  }

  Ok(())
}