///|
/// Read a bounded file range without materializing preceding file blocks.
pub fn Image::read_range(
  self : Image,
  file : Inode,
  offset : Int,
  length : Int,
) -> Bytes raise SquashError {
  guard file.content is File(start, size, fragment, fragment_offset, blocks) else {
    raise Invalid(file.number, "not a file")
  }
  guard offset >= 0 &&
    length >= 0 &&
    length <= 16777216 &&
    offset <= size &&
    length <= size - offset else {
    raise Limit("file range bounds (maximum 16 MiB)")
  }
  let out : Array[Byte] = []
  let bs = self.superblock.block_size
  let offsets = match self.offsets.get(file.reference) {
    Some(value) => value
    None => {
      let result : Array[Int] = []
      let mut at = start
      for descriptor in blocks {
        let size = (descriptor & 0xffffffU).reinterpret_as_int()
        guard (descriptor & 0xfe000000U) == 0U &&
          at >= 96 &&
          at <= self.superblock.inode_table &&
          size <= self.superblock.inode_table - at else {
          raise Invalid(at, "file block extent")
        }
        result.push(at)
        at += size
      }
      // Keep only one file's prefix index to bound memory for huge inode sets.
      self.offsets.clear()
      self.offsets[file.reference] = result
      result
    }
  }
  let first = Int::min(blocks.length(), offset / bs)
  let last = Int::min(
    blocks.length(),
    (offset + length) / bs + (if (offset + length) % bs == 0 { 0 } else { 1 }),
  )
  for i in first.. offset {
      let data = if descriptor == 0U {
        Bytes::make(expected, 0)
      } else {
        self.data_block(stored, descriptor, expected)
      }
      guard data.length() == expected else {
        raise Invalid(stored, "file block length")
      }
      let from = Int::max(offset - logical, 0)
      let until = Int::min(offset + length - logical, expected)
      append_bytes(out, region(data, from, until - from))
    }
  }
  let logical = if fragment == 0xffffffffU { size } else { size / bs * bs }
  if fragment != 0xffffffffU && offset + length > logical {
    guard fragment.to_uint64() < self.superblock.fragments.to_uint64() else {
      raise Invalid(80, "fragment ID")
    }
    let n = fragment.reinterpret_as_int()
    let b = self.metadata(
      self.pointer(self.superblock.fragment_table + n / 512 * 8),
      n % 512 * 16,
      16,
    )
    let data = self.data_block(index(u64(b, 0), 0), u32(b, 8), bs)
    let from = Int::max(offset - logical, 0)
    let until = offset + length - logical
    append_bytes(out, region(data, fragment_offset + from, until - from))
  }
  guard out.length() == length else {
    raise Invalid(offset, "file range short output")
  }
  Bytes::from_array(out)
}