///|
/// Decodes a strict lowercase-or-uppercase hexadecimal string into bytes.
///
/// Strict means: an even number of digits, every digit in `[0-9a-fA-F]`, and no
/// whitespace, separators, or prefix. Key material is written once and read by
/// a security check, so a lenient decoder would only widen the set of inputs
/// that silently decode to something other than what the author wrote.
fn decode_hex(text : String) -> Bytes raise KeyError {
  let digits = text.to_array()
  guard digits.length() % 2 == 0 else {
    raise Malformed(detail="hexadecimal length must be even")
  }
  guard digits.length() > 0 else {
    raise Malformed(detail="hexadecimal value is empty")
  }
  let out : Array[Byte] = []
  for index = 0; index < digits.length(); index = index + 2 {
    let high = hex_digit_value(digits[index])
    let low = hex_digit_value(digits[index + 1])
    out.push((high * 16 + low).to_byte())
  }
  Bytes::from_array(out[:])
}

///|
fn hex_digit_value(digit : Char) -> Int raise KeyError {
  let code = digit.to_int()
  if code >= '0'.to_int() && code <= '9'.to_int() {
    return code - '0'.to_int()
  }
  if code >= 'a'.to_int() && code <= 'f'.to_int() {
    return code - 'a'.to_int() + 10
  }
  if code >= 'A'.to_int() && code <= 'F'.to_int() {
    return code - 'A'.to_int() + 10
  }
  raise Malformed(detail="not a hexadecimal digit: \{digit}")
}