///|
#cfg(target="native")
priv suberror Tls13ClientHelloError {
  Tls13BadRandomLength
  Tls13BadSessionIdLength
  Tls13BadKeyShareLength
  Tls13VectorTooLarge
} derive(Debug, ToJson)

///|
#cfg(target="native")
#warnings("-struct_never_constructed")
priv struct Tls13ClientHelloConfig {
  server_name : String
  alpn : String
  random : Bytes
  legacy_session_id : Bytes
  x25519_public_key : Bytes
  transport_parameters : Bytes
}

///|
#cfg(target="native")
fn tls13_write_u16(out : @buffer.Buffer, value : Int) -> Unit raise {
  guard value >= 0 && value <= 0xffff else { raise Tls13VectorTooLarge }
  out.write_byte(((value >> 8) & 0xff).to_byte())
  out.write_byte((value & 0xff).to_byte())
}

///|
#cfg(target="native")
fn tls13_write_u24(out : @buffer.Buffer, value : Int) -> Unit raise {
  guard value >= 0 && value <= 0xffffff else { raise Tls13VectorTooLarge }
  out.write_byte(((value >> 16) & 0xff).to_byte())
  out.write_byte(((value >> 8) & 0xff).to_byte())
  out.write_byte((value & 0xff).to_byte())
}

///|
#cfg(target="native")
fn tls13_extension(extension_type : Int, payload : Bytes) -> Bytes raise {
  let out = @buffer.new()
  tls13_write_u16(out, extension_type)
  tls13_write_u16(out, payload.length())
  out.write_bytes(payload)
  out.contents()
}

///|
#cfg(target="native")
fn tls13_server_name_extension(server_name : String) -> Bytes raise {
  let name = @utf8.encode(server_name)
  let list = @buffer.new()
  list.write_byte(b'\x00')
  tls13_write_u16(list, name.length())
  list.write_bytes(name)
  let payload = @buffer.new()
  tls13_write_u16(payload, list.length())
  payload.write_bytes(list.contents())
  tls13_extension(0x0000, payload.contents())
}

///|
#cfg(target="native")
fn tls13_alpn_extension(alpn : String) -> Bytes raise {
  let protocol = @utf8.encode(alpn)
  guard protocol.length() <= 255 else { raise Tls13VectorTooLarge }
  let list = @buffer.new()
  list.write_byte(protocol.length().to_byte())
  list.write_bytes(protocol)
  let payload = @buffer.new()
  tls13_write_u16(payload, list.length())
  payload.write_bytes(list.contents())
  tls13_extension(0x0010, payload.contents())
}

///|
#cfg(target="native")
fn tls13_supported_groups_extension() -> Bytes raise {
  let payload = @buffer.new()
  tls13_write_u16(payload, 2)
  tls13_write_u16(payload, 0x001d)
  tls13_extension(0x000a, payload.contents())
}

///|
#cfg(target="native")
fn tls13_signature_algorithms_extension() -> Bytes raise {
  let payload = @buffer.new()
  tls13_write_u16(payload, 4)
  tls13_write_u16(payload, tls13_signature_ecdsa_secp256r1_sha256)
  tls13_write_u16(payload, tls13_signature_rsa_pss_rsae_sha256)
  tls13_extension(0x000d, payload.contents())
}

///|
#cfg(target="native")
fn tls13_supported_versions_extension() -> Bytes raise {
  let payload = @buffer.new()
  payload.write_byte(b'\x02')
  tls13_write_u16(payload, 0x0304)
  tls13_extension(0x002b, payload.contents())
}

///|
#cfg(target="native")
fn tls13_psk_modes_extension() -> Bytes raise {
  tls13_extension(0x002d, b"\x01\x01")
}

///|
#cfg(target="native")
fn tls13_key_share_extension(public_key : Bytes) -> Bytes raise {
  guard public_key.length() == 32 else { raise Tls13BadKeyShareLength }
  let entry = @buffer.new()
  tls13_write_u16(entry, 0x001d)
  tls13_write_u16(entry, public_key.length())
  entry.write_bytes(public_key)
  let payload = @buffer.new()
  tls13_write_u16(payload, entry.length())
  payload.write_bytes(entry.contents())
  tls13_extension(0x0033, payload.contents())
}

///|
#cfg(target="native")
fn tls13_quic_transport_parameters_extension(parameters : Bytes) -> Bytes raise {
  tls13_extension(0x0039, parameters)
}

///|
#cfg(target="native")
#warnings("-unused_value")
fn tls13_encode_client_hello(config : Tls13ClientHelloConfig) -> Bytes raise {
  guard config.random.length() == 32 else { raise Tls13BadRandomLength }
  guard config.legacy_session_id.length() <= 32 else {
    raise Tls13BadSessionIdLength
  }
  let extensions = @buffer.new()
  extensions.write_bytes(tls13_server_name_extension(config.server_name))
  extensions.write_bytes(tls13_supported_groups_extension())
  extensions.write_bytes(tls13_signature_algorithms_extension())
  extensions.write_bytes(tls13_supported_versions_extension())
  extensions.write_bytes(tls13_psk_modes_extension())
  extensions.write_bytes(tls13_key_share_extension(config.x25519_public_key))
  extensions.write_bytes(tls13_alpn_extension(config.alpn))
  extensions.write_bytes(
    tls13_quic_transport_parameters_extension(config.transport_parameters),
  )
  let body = @buffer.new()
  tls13_write_u16(body, 0x0303)
  body.write_bytes(config.random)
  body.write_byte(config.legacy_session_id.length().to_byte())
  body.write_bytes(config.legacy_session_id)
  tls13_write_u16(body, 2)
  tls13_write_u16(body, 0x1301)
  body.write_byte(b'\x01')
  body.write_byte(b'\x00')
  tls13_write_u16(body, extensions.length())
  body.write_bytes(extensions.contents())
  let body = body.contents()
  let out = @buffer.new()
  out.write_byte(b'\x01')
  tls13_write_u24(out, body.length())
  out.write_bytes(body)
  out.contents()
}