// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0

///|
/// How a client judges the server's certificate chain. There is no switch that turns checking
/// off: a self-signed server is trusted by putting its certificate in the roots, or by a `Custom`
/// judge that raises to refuse.
pub(all) enum Verify {
  /// RFC 5280 path validation to the roots, and the leaf naming the host.
  Chain
  /// The caller's judgement over the chain (DER, leaf first) and the host.
  Custom((Array[Bytes], String) -> Unit raise)
}

///|
/// The suites offered or accepted, in preference order: all three of TLS 1.3's, ChaCha20-Poly1305
/// first. The AES here is software, several times slower than ChaCha20 and with table lookups a
/// cache can time; BoringSSL puts ChaCha20 first on hardware without AES instructions for the
/// same two reasons.
pub let suites : Array[@record.Suite] = [ChaCha20Poly1305, Aes128Gcm, Aes256Gcm]

///|
/// The key-exchange groups, in preference order: X25519, then the two NIST curves.
pub let groups : Array[Int32] = [@ext.x25519, @ext.secp256r1, @ext.secp384r1]

///|
/// A server's configuration: its chain (DER, leaf first) and the leaf's private key, the
/// application protocols it speaks in preference order, and the suites and groups it accepts.
///
/// The scheme a CertificateVerify is signed under follows from the key: an ECDSA key signs with its
/// curve's own hash, an RSA key with RSASSA-PSS.
pub(all) struct Server {
  chain : Array[Bytes]
  key : @x509.PrivateKey
  alpn : Array[String]
  suites : Array[@record.Suite]
  groups : Array[Int32]
}

///|
/// Refuses an empty chain and a key that is not the leaf's: either would only show up later, as a
/// client's alert in the middle of a handshake.
pub fn Server::new(
  chain : Array[Bytes],
  key : @x509.PrivateKey,
  alpn? : Array[String],
  suites~ : Array[@record.Suite] = suites,
  groups~ : Array[Int32] = groups,
) -> Server raise @asn1.Refused {
  guard chain is [leaf, ..] else {
    raise @asn1.Malformed("empty certificate chain")
  }
  if @x509.parse(leaf[:]).spki.key() != key.public() {
    raise @asn1.Malformed("private key is not the leaf certificate's")
  }
  { chain, key, alpn: alpn.unwrap_or([]), suites, groups, }
}

///|
/// A client's configuration: the trust anchors, the application protocols it offers, the suites
/// and groups it offers, how it judges the chain, and the path-validation policy.
pub(all) struct Client {
  roots : @x509.Roots
  alpn : Array[String]
  suites : Array[@record.Suite]
  groups : Array[Int32]
  verify : Verify
  policy : @x509.Policy
}

///|
pub fn Client::new(
  roots : @x509.Roots,
  alpn? : Array[String],
  suites~ : Array[@record.Suite] = suites,
  groups~ : Array[Int32] = groups,
  verify~ : Verify = Chain,
  policy~ : @x509.Policy = @x509.policy,
) -> Client {
  { roots, alpn: alpn.unwrap_or([]), suites, groups, verify, policy, }
}