///|
/// Portable hexadecimal transport for binary fixtures and command output.
pub fn encode_hex(bytes : Bytes) -> String {
  let digits = "0123456789abcdef"
  let out = StringBuilder::new()
  for byte in bytes {
    let value = byte.to_int()
    out.write_char(digits[value >> 4].unsafe_to_char())
    out.write_char(digits[value & 15].unsafe_to_char())
  }
  out.to_string()
}

///|
fn hex_digit(value : Char) -> Int raise McapError {
  if value >= '0' && value <= '9' {
    value.to_int() - '0'.to_int()
  } else if value >= 'a' && value <= 'f' {
    value.to_int() - 'a'.to_int() + 10
  } else if value >= 'A' && value <= 'F' {
    value.to_int() - 'A'.to_int() + 10
  } else {
    raise Invalid("invalid hexadecimal digit")
  }
}

///|
pub fn decode_hex(
  value : String,
  max_bytes? : Int = 16 * 1024 * 1024,
) -> Bytes raise McapError {
  if value.length() % 2 != 0 {
    raise Invalid("hexadecimal string must have even length")
  }
  if max_bytes < 0 || value.length() / 2 > max_bytes {
    raise Invalid("hexadecimal input exceeds byte limit")
  }
  let out = @buffer.Buffer::Buffer()
  for i = 0; i < value.length(); i = i + 2 {
    let high = hex_digit(value[i].unsafe_to_char())
    let low = hex_digit(value[i + 1].unsafe_to_char())
    out.write_byte((high * 16 + low).to_byte())
  }
  out.to_bytes()
}