///|
/// Single-file archive: 24-byte bundle header, 32-byte manifest, then a
/// length-prefixed sequence of complete shard frames. Individual frames retain
/// their own checksums so they can also be extracted and stored independently.
pub fn export_bundle(
  object : EncodedObject,
  max_output_bytes? : Int = 134_217_728,
) -> Bytes raise ErasureError {
  if max_output_bytes < 56 || max_output_bytes > 268_435_456 {
    raise InvalidConfiguration(
      "bundle byte budget must fit headers and be <= 256 MiB",
    )
  }
  let manifest = object.manifest()
  let expected = manifest.stripe_count() *
    (manifest.data_count() + manifest.parity_count())
  if object.frame_count() != expected {
    raise InvalidManifest("object frame count disagrees with manifest")
  }
  let frames = object.frames()
  let output : Array[Byte] = [b'M', b'E', b'R', b'B', b'\x01', b'\x00']
  write_u16(output, 24)
  write_u32(output, 32U)
  write_u32(output, frames.length().reinterpret_as_uint())
  write_u32(output, 0U)
  write_u32(output, crc32c(Bytes::from_array(output)))
  for byte in manifest.to_bytes() {
    output.push(byte)
  }
  for frame in frames {
    let bytes = frame.to_bytes()
    if bytes.length() + 4 > max_output_bytes - output.length() {
      raise ResourceLimit("bundle output exceeds byte budget")
    }
    write_u32(output, bytes.length().reinterpret_as_uint())
    for byte in bytes {
      output.push(byte)
    }
  }
  Bytes::from_array(output)
}

///|
/// Strictly import one complete bundle and validate every stripe. This
/// archive path refuses damage; individual frame recovery remains available
/// through `recover_serialized_stripe` when some bytes are unavailable.
pub fn import_bundle(
  input : Bytes,
  max_input_bytes? : Int = 134_217_728,
  max_object_bytes? : Int = 67_108_864,
  max_encoded_bytes? : Int = 16_777_216,
) -> EncodedObject raise ErasureError {
  if max_input_bytes < 56 || max_input_bytes > 268_435_456 {
    raise InvalidConfiguration("bundle input byte budget must be <= 256 MiB")
  }
  if input.length() > max_input_bytes {
    raise ResourceLimit("bundle input exceeds byte budget")
  }
  if input.length() < 56 {
    raise InvalidEnvelope("bundle is shorter than both headers")
  }
  if input[0] != b'M' ||
    input[1] != b'E' ||
    input[2] != b'R' ||
    input[3] != b'B' {
    raise InvalidEnvelope("bad bundle magic")
  }
  let version = input[4].to_int()
  if version != 1 {
    raise UnsupportedVersion(version)
  }
  if input[5] != b'\x00' ||
    read_u16(input, 6) != 24 ||
    read_u32(input, 8) != 32U ||
    read_u32(input, 16) != 0U {
    raise InvalidEnvelope("bundle header fields are invalid")
  }
  let expected_checksum = crc32c(Bytes::from_array(input[0:20].to_array()))
  if read_u32(input, 20) != expected_checksum {
    raise ChecksumMismatch(-1)
  }
  let frame_count = read_bounded_int(input, 12)
  let manifest = Manifest::from_bytes(
    Bytes::from_array(input[24:56].to_array()),
    max_object_bytes~,
    max_encoded_bytes~,
  )
  let expected_frames = manifest.stripe_count() *
    (manifest.data_count() + manifest.parity_count())
  if frame_count != expected_frames {
    raise InvalidManifest("bundle frame count disagrees with manifest")
  }
  let frames : Array[ShardEnvelope] = []
  let mut offset = 56
  for _ in 0.. input.length() - offset {
      raise InvalidEnvelope("invalid or truncated frame payload")
    }
    frames.push(
      ShardEnvelope::from_bytes(
        Bytes::from_array(input[offset:offset + length].to_array()),
        max_encoded_bytes~,
      ),
    )
    offset = offset + length
  }
  if offset != input.length() {
    raise InvalidEnvelope("trailing bytes after final frame")
  }
  ignore(recover_object(manifest, frames, max_encoded_bytes~))
  { manifest, frames, }
}