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

// RFC 9368's compatible version negotiation on a server. The client's first flight names the
// version it was sent in and the versions that flight can be converted into; a server reads
// that from the ClientHello before it starts the handshake, picks the version it prefers, and
// answers in it. This is the part of §2.3 that cannot be done after the TLS connection exists:
// the server's own transport parameters are written into EncryptedExtensions by the handshake,
// so the choice has to be made first.

///|
/// What a first flight's contiguous CRYPTO bytes say about versions. `Incomplete` means the
/// ClientHello is still arriving; the other three are the outcomes RFC 9368 §4 names.
priv enum FirstFlight {
  Incomplete
  /// No `version_information` parameter: a server MAY complete the handshake anyway (§4).
  Absent
  /// A parameter that is not four-byte versions, or one that names version zero.
  Malformed
  Present(UInt32, Array[UInt32])
}

///|
/// Read the client's `version_information` out of the first flight without running a TLS
/// handshake. The bytes are the contiguous CRYPTO stream at the Initial level: a TLS handshake
/// message's type and 24-bit length first, then the ClientHello, then its extensions. The
/// parameters themselves are QUIC's own encoding, decoded by this package's `decode_opt`.
fn client_version_information(crypto : BytesView) -> FirstFlight {
  guard @msg.unframe(crypto) is Some((@msg.Kind::ClientHello, body)) else {
    return Incomplete
  }
  guard @msg.read_hello(body) is Some(hello) else { return Absent }
  guard @ext.find(hello.extensions[:], @ext.QuicTransportParameters) is Some(e) else {
    return Absent
  }
  guard decode_opt(e.data[:]) is Some(params) else { return Absent }
  match find(params[:], VersionInformation) {
    Some(p) =>
      match read_version_information(p.value[:]) {
        Some((chosen, available)) => Present(chosen, available)
        None => Malformed
      }
    None => Absent
  }
}

///|
/// Whether a first flight sent in `chosen` can be converted into one in `candidate`. RFC 9368
/// §2.2 leaves compatibility to the document that defines the two versions; this library
/// defines it for the two it speaks. Version 1 and version 2 long headers differ in the salt,
/// the four labels, the Retry key and the two-bit type codes, and in nothing else a first
/// flight carries — the frames, the TLS messages and the transport parameters are the same —
/// so re-deriving the Initial keys and re-reading the type bits is a lossless conversion.
/// No other pair is assumed compatible (§2.2).
fn compatible(chosen : UInt32, candidate : UInt32) -> Bool {
  let known = (v : UInt32) => v == @packet.version1 || v == @packet.version2
  known(chosen) && known(candidate)
}

///|
/// The Negotiated Version (RFC 9368 §2.3): the first of this server's own preferences that the
/// client says its first flight is compatible with and that this server knows how to convert.
/// The client's Chosen Version is always such a version — the identity transformation — so the
/// search always answers, and a client that offers only its Chosen Version disables the
/// mechanism (§2.3).
fn choose_version(
  cfg : Config,
  chosen : UInt32,
  available : Array[UInt32],
) -> UInt32 {
  for version in cfg.versions {
    if available.contains(version) && compatible(chosen, version) {
      return version
    }
  }
  chosen
}

///|
/// What a connection needs to open its handshake once the first flight has been read: the TLS
/// configuration, its randomness and clock, and the transport parameters other than
/// `version_information`, whose Chosen Version is the decision this file makes.
priv struct Pending {
  config : @tls.Server
  rand : (Int32) -> Bytes
  wall : Int64?
  base : Bytes
}

///|
/// Transport parameters whose `version_information` names `chosen`.
fn declared_for(
  base : Bytes,
  chosen : UInt32,
  versions : ArrayView[UInt32],
) -> Bytes {
  let buf = Buffer()
  buf.write_bytes(base)
  buf.write_bytes(encode([version_information(chosen, versions)]))
  buf.to_bytes()
}