///|
/// Encode a BER BOOLEAN (X.690 8.2). `false` encodes as `0x01 0x01 0x00`.
pub fn encode_boolean(b : Bool) -> Bytes {
  let content = if b {
    Bytes::from_array([0xFF])
  } else {
    Bytes::from_array([0x00])
  }
  encode_tlv(universal_tag(tag_boolean, false), content)
}

///|
/// Encode a BER INTEGER with the minimal two's-complement representation
/// (X.690 8.3). Negative values get a leading `0xFF` padding byte when the
/// high bit would otherwise be clear.
pub fn encode_integer(v : Int) -> Bytes {
  encode_tlv(universal_tag(tag_integer, false), integer_content(v))
}

///|
/// Encode a BER NULL (X.690 8.8): `0x05 0x00`.
pub fn encode_null() -> Bytes {
  encode_tlv(universal_tag(tag_null, false), Bytes::new(0))
}

///|
/// Encode a BER OCTET STRING (X.690 8.7).
pub fn encode_octet_string(content : Bytes) -> Bytes {
  encode_tlv(universal_tag(tag_octet_string, false), content)
}

///|
/// Encode a UTF8String (X.690 8.21).
pub fn encode_utf8_string(s : String) -> Bytes {
  encode_tlv(universal_tag(tag_utf8_string, false), @utf8.encode(s))
}

///|
/// Encode an IA5String (X.690 8.22).
pub fn encode_ia5_string(s : String) -> Bytes {
  encode_tlv(universal_tag(tag_ia5_string, false), @utf8.encode(s))
}

///|
/// Encode a BER ENUMERATED (X.690 8.4), reusing the INTEGER encoding rules.
pub fn encode_enumerated(v : Int) -> Bytes {
  encode_tlv(universal_tag(tag_enumerated, false), integer_content(v))
}

///|
/// Encode a constructed SEQUENCE (X.690 8.9).
pub fn encode_sequence(items : Array[Bytes]) -> Bytes {
  let mut content = Bytes::new(0)
  for item in items {
    content = content + item
  }
  encode_tlv(universal_tag(tag_sequence, true), content)
}

///|
/// Encode a constructed SET (X.690 8.11).
pub fn encode_set(items : Array[Bytes]) -> Bytes {
  let mut content = Bytes::new(0)
  for item in items {
    content = content + item
  }
  encode_tlv(universal_tag(tag_set, true), content)
}

///|
/// Encode a BER OID from its dotted-decimal string form (X.690 8.19).
pub fn encode_oid(oid : String) -> Result[Bytes, BerError] {
  result_of_ber(fn() raise BerError {
    let parts = split_oid(oid)
    if parts.length() < 2 {
      raise InvalidOid
    }
    let acc : Array[Byte] = []
    // First arc: first * 40 + second.
    let first = parse_oid_arc(parts[0])
    let second = parse_oid_arc(parts[1])
    let first_value = first * 40 + second
    push_base128(acc, first_value)
    for i in 2.. Bytes {
  let content = Bytes::from_array([unused_bits.to_byte()]) + data
  encode_tlv(universal_tag(tag_bit_string, false), content)
}

// Compute the minimal two's-complement content octets of an integer.

///|
pub fn integer_content(v : Int) -> Bytes {
  let acc : Array[Byte] = []
  let mut remaining = v
  while remaining != 0 && remaining != -1 {
    acc.push((remaining & 0xFF).to_byte())
    remaining = remaining >> 8
  }
  if acc.is_empty() {
    // v is 0 or -1.
    let b = if v == 0 { (0).to_byte() } else { (0xFF).to_byte() }
    return Bytes::from_array([b])
  }
  let out = reverse_array(acc)
  let first = out[0].to_int()
  if v >= 0 && (first & 0x80) != 0 {
    out.insert(0, (0).to_byte())
  } else if v < 0 && (first & 0x80) == 0 {
    out.insert(0, (0xFF).to_byte())
  }
  Bytes::from_array(out)
}

///|
fn split_oid(oid : String) -> Array[String] {
  let out : Array[String] = []
  let mut current = StringBuilder()
  for c in oid {
    if c == '.' {
      out.push(current.to_string())
      current = StringBuilder()
    } else {
      current.write_char(c)
    }
  }
  out.push(current.to_string())
  out
}

///|
fn parse_oid_arc(s : String) -> Int raise BerError {
  let mut value = 0
  let mut seen = false
  for c in s {
    if c.is_ascii_digit() {
      seen = true
      value = value * 10 + (c.to_int() - '0'.to_int())
      if value > 1000000000 {
        raise InvalidOid
      }
    } else {
      raise InvalidOid
    }
  }
  if !seen {
    raise InvalidOid
  }
  value
}

///|
fn push_base128(acc : Array[Byte], v : Int) -> Unit {
  let tmp : Array[Byte] = []
  let mut remaining = v
  while remaining > 0 {
    tmp.push((remaining & 0x7F).to_byte())
    remaining = remaining >> 7
  }
  if tmp.is_empty() {
    tmp.push((0).to_byte())
  }
  let out = reverse_array(tmp)
  for i in 0..