///|
fn crc32c_step(crc : UInt, byte : Byte) -> UInt {
  let mut value = crc ^ byte.to_uint()
  for bit = 0; bit < 8; bit = bit + 1 {
    value = if (value & 1U) != 0U {
      (value >> 1) ^ 0x82f63b78U
    } else {
      value >> 1
    }
  }
  value
}

///|
pub fn crc32c(data : Bytes) -> UInt {
  let mut value = 0xffffffffU
  for byte in data {
    value = crc32c_step(value, byte)
  }
  value ^ 0xffffffffU
}

///|
pub fn crc32c_reference(data : Bytes) -> UInt {
  let mut remainder = 0xffffffffU
  for byte in data {
    remainder = remainder ^ byte.to_uint()
    for bit = 0; bit < 8; bit = bit + 1 {
      if (remainder & 1U) == 1U {
        remainder = (remainder >> 1) ^ 0x82f63b78U
      } else {
        remainder = remainder >> 1
      }
    }
  }
  remainder ^ 0xffffffffU
}

///|
pub fn verify_proxy_crc32c(complete_header : Bytes) -> Result[Unit, ProxyError] {
  if complete_header.length() < 16 {
    return Err(
      proxy_error(NeedMoreData, 0, "v2 header is shorter than fixed header"),
    )
  }
  let declared = (complete_header[14].to_int() << 8) |
    complete_header[15].to_int()
  if complete_header.length() != 16 + declared {
    return Err(
      proxy_error(
        InvalidLength,
        14,
        "CRC validation requires exactly one complete v2 header",
      ),
    )
  }
  let mut at = 16
  let family = complete_header[13].to_int() >> 4
  let address_length = if family == 1 {
    12
  } else if family == 2 {
    36
  } else if family == 3 {
    216
  } else {
    0
  }
  if declared < address_length {
    return Err(proxy_error(TruncatedAddress, 16, "address block is truncated"))
  }
  at = at + address_length
  let mut crc_value : UInt? = None
  let mut crc_offset = -1
  while at < complete_header.length() {
    if complete_header.length() - at < 3 {
      return Err(proxy_error(TruncatedTlv, at, "incomplete TLV header"))
    }
    let kind = complete_header[at]
    let length = (complete_header[at + 1].to_int() << 8) |
      complete_header[at + 2].to_int()
    if length > complete_header.length() - at - 3 {
      return Err(proxy_error(TruncatedTlv, at, "truncated TLV"))
    }
    if kind == TLV_CRC32C {
      if length != 4 {
        return Err(proxy_error(InvalidTlv, at, "CRC32C TLV must be four bytes"))
      }
      if crc_value is Some(_) {
        return Err(proxy_error(DuplicateCrc32c, at, "more than one CRC32C TLV"))
      }
      crc_value = Some(
        (complete_header[at + 3].to_uint() << 24) |
        (complete_header[at + 4].to_uint() << 16) |
        (complete_header[at + 5].to_uint() << 8) |
        complete_header[at + 6].to_uint(),
      )
      crc_offset = at + 3
    }
    at = at + 3 + length
  }
  match crc_value {
    None => Err(proxy_error(MissingCrc32c, 16, "CRC32C TLV is absent"))
    Some(expected) => {
      let copy = complete_header.to_array()
      for i = 0; i < 4; i = i + 1 {
        copy[crc_offset + i] = b'\x00'
      }
      if crc32c(Bytes::from_array(copy)) == expected {
        Ok(())
      } else {
        Err(proxy_error(Crc32cMismatch, crc_offset, "CRC32C checksum differs"))
      }
    }
  }
}