///|
/// The self-identifying wrapper used when an archive crosses a trust boundary.
/// The contained payload is still a standard rkyv byte sequence: the wrapper
/// is optional and is never part of a Rust `Archived` layout.
#valtype
pub struct ArchiveEnvelope {
  version : Int
  format : Format
  payload : Bytes
}

///| Returns the validated rkyv payload owned by this envelope.
pub fn ArchiveEnvelope::payload_bytes(self : ArchiveEnvelope) -> Bytes {
  self.payload
}

///| Returns the rkyv reader format declared by this envelope.
pub fn ArchiveEnvelope::archive_format(self : ArchiveEnvelope) -> Format {
  self.format
}

///|
/// Envelope errors are intentionally separate from `RkyvError`: callers can
/// reject a bad transport blob before attempting any schema-dependent reads.
pub suberror EnvelopeError {
  InvalidMagic
  UnsupportedVersion(Byte)
  UnsupportedFormat(Byte)
  InvalidLength(Int)
  PayloadLimitExceeded(actual~ : Int, limit~ : Int)
  ChecksumMismatch(expected~ : UInt, actual~ : UInt)
} derive(Debug, Eq)

///|
/// The current envelope protocol version.
pub let envelope_version : Byte = b'\x01'

///|
/// Number of bytes before the rkyv payload starts.
pub let envelope_header_size : Int = 16

///|
/// Computes the IEEE CRC-32 used by the envelope. This small table-free form
/// keeps the JavaScript runtime dependency-free and has negligible cost for
/// the small headers and metadata archives this package targets.
pub fn crc32(bytes : Bytes) -> UInt {
  let mut crc = 0xffffffffU
  for index in 0..> 1) ^ 0xedb88320U
      } else {
        crc = crc >> 1
      }
    }
  }
  crc ^ 0xffffffffU
}

///|
/// Encodes rkyv's format settings into the v1 envelope flags byte.
fn envelope_format_flags(format : Format) -> Byte raise EnvelopeError {
  let pointer = match format.pointer_width {
    16 => 1U
    32 => 0U
    64 => 2U
    _ => raise EnvelopeError::UnsupportedFormat(b'\xff')
  }
  let endian = match format.endian {
    Endian::Little => 0U
    Endian::Big => 1U
  }
  let alignment = if format.aligned { 0U } else { 8U }
  (endian | (pointer << 1) | alignment).to_byte()
}

///|
/// Decodes the v1 envelope flags into rkyv reader settings.
fn envelope_format_from_flags(flags : Byte) -> Format raise EnvelopeError {
  let bits = flags.to_uint()
  if (bits & 0xf0U) != 0U {
    raise EnvelopeError::UnsupportedFormat(flags)
  }
  let pointer_width = match (bits >> 1) & 3U {
    0U => 32
    1U => 16
    2U => 64
    _ => raise EnvelopeError::UnsupportedFormat(flags)
  }
  let endian = if (bits & 1U) == 0U { Endian::Little } else { Endian::Big }
  Format::new(endian, pointer_width, (bits & 8U) == 0U)
}

///|
/// Writes a `u32` to an arbitrary output array in canonical little-endian.
fn envelope_append_u32(out : Array[Byte], value : UInt) -> Unit {
  out.push((value & 0xffU).to_byte())
  out.push(((value >> 8) & 0xffU).to_byte())
  out.push(((value >> 16) & 0xffU).to_byte())
  out.push(((value >> 24) & 0xffU).to_byte())
}

///|
/// Wraps a default-format rkyv payload in the versioned envelope. Use
/// `encode_envelope_with_format` when Rust was built with a non-default rkyv
/// format feature.
pub fn encode_envelope(payload : Bytes) -> Bytes {
  encode_envelope_with_format(payload, default_format()) catch {
    _ => abort("the default rkyv format must be representable in an envelope")
  }
}

///|
/// Wraps an rkyv payload with its exact reader format. The payload is copied
/// once so the returned value is a standalone transport buffer.
pub fn encode_envelope_with_format(
  payload : Bytes,
  format : Format,
) -> Bytes raise EnvelopeError {
  let length = payload.length()
  if length < 0 || length > 0x7fff_ffff {
    raise EnvelopeError::InvalidLength(length)
  }
  let flags = envelope_format_flags(format)
  let out : Array[Byte] = [
    b'R', b'M', b'B', b'T', envelope_version, flags, b'\x00', b'\x00',
  ]
  envelope_append_u32(out, length.reinterpret_as_uint())
  envelope_append_u32(out, crc32(payload))
  for index in 0.. ArchiveEnvelope raise EnvelopeError {
  decode_envelope_with_limit(bytes, 0x7fff_ffff)
}

///|
/// Parses an envelope while enforcing a caller-provided payload ceiling before
/// copying any payload bytes. Use this at network and storage boundaries to
/// keep malformed length headers from causing unbounded allocation.
pub fn decode_envelope_with_limit(
  bytes : Bytes,
  max_payload_length : Int,
) -> ArchiveEnvelope raise EnvelopeError {
  if max_payload_length < 0 {
    raise EnvelopeError::PayloadLimitExceeded(
      actual=0,
      limit=max_payload_length,
    )
  }
  if bytes.length() < envelope_header_size {
    raise EnvelopeError::InvalidLength(bytes.length())
  }
  if bytes[0] != b'R' ||
    bytes[1] != b'M' ||
    bytes[2] != b'B' ||
    bytes[3] != b'T' {
    raise EnvelopeError::InvalidMagic
  }
  if bytes[4] != envelope_version {
    raise EnvelopeError::UnsupportedVersion(bytes[4])
  }
  let format = envelope_format_from_flags(bytes[5])
  if bytes[6] != b'\x00' || bytes[7] != b'\x00' {
    raise EnvelopeError::UnsupportedFormat(bytes[5])
  }
  let declared_length = bytes[8].to_uint() |
    (bytes[9].to_uint() << 8) |
    (bytes[10].to_uint() << 16) |
    (bytes[11].to_uint() << 24)
  if declared_length > 0x7fff_ffffU {
    raise EnvelopeError::InvalidLength(declared_length.reinterpret_as_int())
  }
  let payload_length = declared_length.reinterpret_as_int()
  if payload_length != bytes.length() - envelope_header_size {
    raise EnvelopeError::InvalidLength(payload_length)
  }
  if payload_length > max_payload_length {
    raise EnvelopeError::PayloadLimitExceeded(
      actual=payload_length,
      limit=max_payload_length,
    )
  }
  let expected = bytes[12].to_uint() |
    (bytes[13].to_uint() << 8) |
    (bytes[14].to_uint() << 16) |
    (bytes[15].to_uint() << 24)
  let payload_bytes : Array[Byte] = []
  for index in 0..