///|
/// IEEE CRC-32 for optional corruption detection on framed transports.
pub fn crc32(data : Bytes) -> UInt {
  let mut crc = 0xffffffffU
  for byte in data {
    crc = crc ^ byte.to_uint()
    for _bit = 0; _bit < 8; _bit = _bit + 1 {
      crc = if (crc & 1U) != 0U { (crc >> 1) ^ 0xedb88320U } else { crc >> 1 }
    }
  }
  crc.lnot()
}

///|
/// Encode payload plus a big-endian CRC-32 inside the length envelope.
pub fn encode_checked_frame(payload : Bytes) -> Bytes {
  let checksum = crc32(payload)
  let body : Array[Byte] = []
  for byte in payload {
    body.push(byte)
  }
  body.push((checksum >> 24).to_byte())
  body.push((checksum >> 16).to_byte())
  body.push((checksum >> 8).to_byte())
  body.push(checksum.to_byte())
  encode_frame(Bytes::from_array(body))
}

///|
/// Decode and verify one checksummed frame.
pub fn decode_checked_frame(
  data : Bytes,
  max_frame_size? : Int = 1048576,
) -> Result[(Bytes, Int), FrameError] {
  match decode_frame(data, max_frame_size=max_frame_size + 4) {
    Err(error) => Err(error)
    Ok((body, consumed)) => {
      if body.length() < 4 {
        return Err(MissingChecksum)
      }
      let end = body.length()
      let payload = body[:end - 4].to_owned()
      let expected = (body[end - 4].to_uint() << 24) |
        (body[end - 3].to_uint() << 16) |
        (body[end - 2].to_uint() << 8) |
        body[end - 1].to_uint()
      let actual = crc32(payload)
      if actual != expected {
        return Err(ChecksumMismatch(expected, actual))
      }
      Ok((payload, consumed))
    }
  }
}