///|
pub fn read_cab(
  data : Bytes,
  limits? : Limits = default_limits,
) -> ArchiveListing raise MspackError {
  let r = Reader::new(data)
  let (r, m0) = r.u8()
  let (r, m1) = r.u8()
  let (r, m2) = r.u8()
  let (r, _) = r.u8()
  if m0 != 0x4D || m1 != 0x53 || m2 != 0x43 {
    raise MspackError("invalid CAB magic")
  }
  let (r, _) = r.u32le()
  let (r, cb_cabinet) = r.u32le()
  let (r, _) = r.u32le()
  let (r, coff_files) = r.u32le()
  let (r, _) = r.u32le()
  let (r, _) = r.u8()
  let (r, _) = r.u8()
  let (r, c_folders) = r.u16le()
  let (r, c_files) = r.u16le()
  let (r, flags) = r.u16le()
  let (r, _) = r.u16le()
  let (r, _) = r.u16le()
  if c_files > limits.max_entries {
    raise MspackError("archive limit exceeded")
  }
  if cb_cabinet.to_int() > data.length() {
    raise MspackError("archive bounds error")
  }
  let mut rr = r
  if (flags & 0x0001) != 0 {
    let (x, cb_prev) = rr.u16le()
    let (x, _) = x.u16le()
    let (x, _) = x.u16le()
    let (x, _) = x.cstring(cb_prev)
    rr = x
  }
  if (flags & 0x0002) != 0 {
    let (x, cb_next) = rr.u16le()
    let (x, _) = x.u16le()
    let (x, _) = x.u16le()
    let (x, _) = x.cstring(cb_next)
    rr = x
  }
  if (flags & 0x0004) != 0 {
    let (x, _) = rr.u16le()
    let (x, _) = x.u16le()
    rr = x
  }
  let folder_start = rr.at()
  let folders = []
  for _ in 0.. Array[ArchiveEntry] raise MspackError {
  read_cab(data).entries
}

///|
fn cab_folders(data : Bytes) -> Array[(Int, Int, Int)] raise MspackError {
  let r = Reader::new(data)
  let (r, m0) = r.u8()
  let (r, m1) = r.u8()
  let (r, m2) = r.u8()
  let (r, _) = r.u8()
  if m0 != 0x4D || m1 != 0x53 || m2 != 0x43 {
    raise MspackError("invalid CAB magic")
  }
  let (r, _) = r.u32le()
  let (r, _) = r.u32le()
  let (r, _) = r.u32le()
  let (r, _) = r.u32le()
  let (r, _) = r.u32le()
  let (r, _) = r.u8()
  let (r, _) = r.u8()
  let (r, count) = r.u16le()
  let (r, _) = r.u16le()
  let (r, flags) = r.u16le()
  let (r, _) = r.u16le()
  let (r, _) = r.u16le()
  let mut rr = r
  if (flags & 1) != 0 {
    let (x, n) = rr.u16le()
    let (x, _) = x.u16le()
    let (x, _) = x.u16le()
    let x = x.skip(n)
    rr = x
  }
  if (flags & 2) != 0 {
    let (x, n) = rr.u16le()
    let (x, _) = x.u16le()
    let (x, _) = x.u16le()
    let x = x.skip(n)
    rr = x
  }
  if (flags & 4) != 0 {
    let (x, _) = rr.u16le()
    let (x, _) = x.u16le()
    rr = x
  }
  let result : Array[(Int, Int, Int)] = []
  for _ in 0.. Bytes raise MspackError {
  let folders = cab_folders(data)
  if entry.folder < 0 || entry.folder >= folders.length() {
    raise MspackError("CAB folder index outside table")
  }
  let (offset, block_count, compression) = folders[entry.folder]
  println(
    "DBG " +
    offset.to_string() +
    " " +
    block_count.to_string() +
    " " +
    entry.offset.to_string(),
  )
  let mut cursor = offset
  let output : Array[Byte] = []
  for _ in 0.. limits.max_output {
      raise MspackError("CAB output exceeds limit")
    }
    for i in 0.. output.length() {
    raise MspackError("CAB entry range exceeds decoded folder")
  }
  let selected : Array[Byte] = []
  for i in begin..<(begin + entry.size) {
    selected.push(output[i])
  }
  Bytes::from_array(selected)
}

///|
/// Metadata for one CAB CFDATA block.
pub struct CabBlock {
  pub offset : Int
  pub checksum : UInt
  pub compressed_size : Int
  pub uncompressed_size : Int
  pub compression : Int
} derive(Show, Eq)

///|
/// Inspect CFDATA blocks without decompressing them.
pub fn inspect_cab_blocks(
  data : Bytes,
  entry : ArchiveEntry,
  limits? : Limits = default_limits,
) -> Array[CabBlock] raise MspackError {
  let folders = cab_folders(data)
  if entry.folder < 0 || entry.folder >= folders.length() {
    raise MspackError("CAB folder index outside table")
  }
  let (offset, block_count, compression) = folders[entry.folder]
  if block_count > limits.max_entries {
    raise MspackError("CAB block count exceeds limit")
  }
  let result : Array[CabBlock] = []
  let mut cursor = offset
  for _ in 0.. limits.max_output || compressed > limits.max_output {
      raise MspackError("CAB block exceeds limit")
    }
    let (r, _) = r.bytes(compressed)
    cursor = r.at()
    result.push({
      offset: block_offset,
      checksum,
      compressed_size: compressed,
      uncompressed_size: uncompressed,
      compression: compression & 0x000F,
    })
  }
  result
}

///|
/// Validate the checksum field of a CAB CFDATA block.
pub fn verify_cab_block_checksum(
  data : Bytes,
  block : CabBlock,
) -> Bool raise MspackError {
  let header_size = 8
  checked_range(
    data.length(),
    block.offset,
    header_size + block.compressed_size,
  )
  let payload = copy_range(
    data,
    offset=block.offset + header_size,
    size=block.compressed_size,
  )
  checksum32(payload) == block.checksum || block.checksum == 0
}

///|
/// Inspect and validate every block belonging to a CAB entry.
pub fn validate_cab_entry_blocks(
  data : Bytes,
  entry : ArchiveEntry,
  limits? : Limits = default_limits,
) -> Array[CabBlock] raise MspackError {
  let blocks = inspect_cab_blocks(data, entry, limits~)
  for block in blocks {
    if !verify_cab_block_checksum(data, block) {
      raise MspackError("CAB CFDATA checksum mismatch")
    }
  }
  blocks
}