/-
  IP.TLS.Client — TLS 1.3 client-side state machine
  (RFC 8446 §A.1).

  The client states for a basic 1-RTT handshake:

      START
        |  client sends ClientHello, computes x25519 keypair
        v
      WAIT_SH
        |  on ServerHello: compute (handshake keys, transcript hash after CH||SH)
        v
      WAIT_EE
        |  on EncryptedExtensions: append to transcript
        v
      WAIT_CERT_OR_CV
        |  on Certificate: append, expect CertificateVerify next
        |  (or on PreSharedKey path: skip to WAIT_FINISHED)
        v
      WAIT_CV
        |  on CertificateVerify: verify server signature
        v
      WAIT_FINISHED
        |  on Finished: verify server verify_data,
        |  compute application keys from transcript-after-server-Finished,
        |  build & send client Finished.
        v
      CONNECTED

  This file ships the pure state-transition functions; the
  Sparkle Signal HW wrapper is a future T.6.HW task.
-/

import IP.TLS.KeySchedule
import IP.TLS.Handshake
import IP.Crypto.Codec.HKDF
import IP.Crypto.Proof.X25519
import IP.Crypto.Proof.Ed25519Sign
import IP.Crypto.Proof.P256ECDSA
import IP.Crypto.Codec.RSAPSS
import IP.TLS.X509

namespace Sparkle.IP.TLS.Client

open Sparkle.IP.TLS.KeySchedule
open Sparkle.IP.TLS.Handshake (HandshakeType ServerHello parseServerHelloBody buildFinished wrapHandshake)
open Sparkle.IP.Crypto.HKDF (sha256 hmacSha256 hkdfExpandLabel)

/-! ### Client state. -/

inductive Phase where
  | start          : Phase
  | waitSh         : Phase
  | waitEe         : Phase
  | waitCertOrCv   : Phase
  | waitCv         : Phase
  | waitFinished   : Phase
  | connected      : Phase
  | failed (reason : String) : Phase
  deriving Repr, BEq, Inhabited

/-- Per-connection client state.

    Carries the live secrets, the running transcript bytes
    (so we can hash at any cut point), the client's X25519
    private key for KEX, and the server's verifying key once
    learned. -/
structure State where
  phase            : Phase
  /-- 32-byte X25519 client secret (clamped). -/
  ourEcdhSecret    : Array UInt8
  /-- 32-byte X25519 client public key (sent in ClientHello). -/
  ourEcdhPublic    : Array UInt8
  /-- Concatenation of handshake message bytes seen so far
      (used to compute the running transcript hash on demand). -/
  transcript       : Array UInt8
  /-- DHE shared secret (filled once we see ServerHello). -/
  dheSecret        : Array UInt8
  /-- Handshake-stage secrets (filled after ServerHello). -/
  handshakeSecrets : Option HandshakeSecrets
  /-- Application-stage secrets (filled after server Finished). -/
  applicationSecrets : Option ApplicationSecrets
  /-- Server's verifying key, learned from server's Certificate. -/
  serverPubkey     : Array UInt8
  /-- The client_finished verify_data we end up sending, kept for
      tests / downstream record encryption. -/
  clientFinishedVerifyData : Array UInt8
  deriving Repr

/-- Initial state: nothing computed yet. -/
def initState (ourEcdhSecret ourEcdhPublic : Array UInt8) : State :=
  { phase := .start
  , ourEcdhSecret := ourEcdhSecret
  , ourEcdhPublic := ourEcdhPublic
  , transcript := #[]
  , dheSecret := #[]
  , handshakeSecrets := none
  , applicationSecrets := none
  , serverPubkey := #[]
  , clientFinishedVerifyData := #[] }

/-! ### Transition functions.  Each takes the current state +
    the next message's bytes (the handshake-framed wire form,
    i.e. type || u24 length || body) and returns the new state.
    Failures land the state in `Phase.failed`. -/

/-- Run after we've built & sent ClientHello.  We pre-append
    the ClientHello bytes to the transcript here so subsequent
    transcript-hash calls include them. -/
def afterSendClientHello (s : State) (clientHelloMsg : Array UInt8) : State :=
  { s with transcript := s.transcript ++ clientHelloMsg
         , phase := .waitSh }

/-- Receive ServerHello.  `serverHelloMsg` is the full
    Handshake frame (type byte + u24 length + body).  Strips
    the frame, parses the body, computes the DHE shared
    secret and the handshake-stage secrets, transitions to
    `waitEe`. -/
def onServerHello (s : State) (serverHelloMsg : Array UInt8) : State := Id.run do
  if serverHelloMsg.size < 4 then
    return { s with phase := .failed "ServerHello: truncated frame" }
  if serverHelloMsg[0]! ≠ HandshakeType.serverHello.toByte then
    return { s with phase := .failed "ServerHello: wrong msg_type" }
  -- u24 length at bytes 1..3
  let bodyLen :=
    (serverHelloMsg[1]!.toNat <<< 16) |||
    (serverHelloMsg[2]!.toNat <<<  8) |||
     serverHelloMsg[3]!.toNat
  if serverHelloMsg.size ≠ 4 + bodyLen then
    return { s with phase := .failed "ServerHello: length mismatch" }
  let mut body : Array UInt8 := Array.replicate bodyLen 0
  for i in [:bodyLen] do
    body := body.set! i serverHelloMsg[4 + i]!
  match parseServerHelloBody body with
  | none => return { s with phase := .failed "ServerHello: parse failed" }
  | some sh =>
    -- Compute DHE shared secret.
    let dhe := Sparkle.IP.Crypto.X25519.x25519 s.ourEcdhSecret sh.serverPubkey
    -- Append SH to transcript, compute transcript hash, derive HS-stage.
    let transcript' := s.transcript ++ serverHelloMsg
    let chShHash := sha256 transcript'
    let es := earlySecret #[]
    let hs := deriveHandshakeStage es dhe chShHash
    return { s with phase := .waitEe
                  , transcript := transcript'
                  , dheSecret := dhe
                  , handshakeSecrets := some hs }

/-- Receive EncryptedExtensions.  No semantic processing for
    a minimal client beyond appending to the transcript. -/
def onEncryptedExtensions (s : State) (eeMsg : Array UInt8) : State :=
  if eeMsg.size < 4 ∨ eeMsg[0]! ≠ HandshakeType.encryptedExtensions.toByte then
    { s with phase := .failed "EE: bad framing" }
  else
    { s with transcript := s.transcript ++ eeMsg
           , phase := .waitCertOrCv }

/-- Receive Certificate (manual override variant).  The
    caller provides the already-extracted server pubkey
    bytes — useful for tests that bypass X.509 parsing
    or that present a non-cert form. -/
def onCertificate (s : State) (certMsg : Array UInt8)
    (extractedServerPubkey : Array UInt8) : State :=
  if certMsg.size < 4 ∨ certMsg[0]! ≠ HandshakeType.certificate.toByte then
    { s with phase := .failed "Cert: bad framing" }
  else
    { s with transcript := s.transcript ++ certMsg
           , serverPubkey := extractedServerPubkey
           , phase := .waitCv }

/-- Receive Certificate, parsing the DER blob with the
    full X.509 parser to extract the server pubkey.

    `certMsg` is the full Handshake-wrapped Certificate
    frame; `certDer` is the DER blob of the end-entity
    certificate from inside that message (the caller still
    has to peel the TLS Certificate list framing).

    On success the FSM stamps the appropriate server pubkey
    bytes:
      * ed25519   → 32 raw bytes
      * ecdsaP256 → 65 bytes (SEC1 uncompressed, 0x04||X||Y)
      * rsa       → DER-encoded RSAPublicKey (n + e). -/
def onCertificateDer (s : State) (certMsg certDer : Array UInt8) : State := Id.run do
  if certMsg.size < 4 ∨ certMsg[0]! ≠ HandshakeType.certificate.toByte then
    return { s with phase := .failed "Cert: bad framing" }
  match Sparkle.IP.TLS.X509.parseCertificate certDer with
  | none =>
    return { s with phase := .failed "Cert: X.509 parse failed" }
  | some cert =>
    -- Reject anything other than the three TLS sig schemes
    -- we support (matches CertificateVerify dispatch).
    match cert.spki.algorithm with
    | Sparkle.IP.TLS.X509.PublicKeyAlg.ed25519
    | Sparkle.IP.TLS.X509.PublicKeyAlg.ecdsaP256
    | Sparkle.IP.TLS.X509.PublicKeyAlg.rsa =>
      return { s with transcript := s.transcript ++ certMsg
                    , serverPubkey := cert.spki.rawKey
                    , phase := .waitCv }
    | Sparkle.IP.TLS.X509.PublicKeyAlg.other oid =>
      return { s with phase := .failed s!"Cert: unsupported pubkey OID {repr oid}" }

/-- Receive CertificateVerify.  RFC 8446 §4.4.3:
      signed content =
        ASCII " " × 64 ||
        "TLS 1.3, server CertificateVerify" ||
        0x00 ||
        Transcript-Hash(ClientHello..Certificate)

    Verify with the previously-extracted server pubkey
    (currently Ed25519 only). -/
def onCertificateVerify (s : State) (cvMsg : Array UInt8) : State := Id.run do
  if cvMsg.size < 4 ∨ cvMsg[0]! ≠ HandshakeType.certificateVerify.toByte then
    return { s with phase := .failed "CV: bad framing" }
  -- Parse body: SignatureScheme (2 bytes) + signature (vec16).
  let bodyLen :=
    (cvMsg[1]!.toNat <<< 16) |||
    (cvMsg[2]!.toNat <<<  8) |||
     cvMsg[3]!.toNat
  if cvMsg.size ≠ 4 + bodyLen ∨ bodyLen < 4 then
    return { s with phase := .failed "CV: length mismatch" }
  let sigScheme :=
    (cvMsg[4]!.toNat <<< 8) ||| cvMsg[5]!.toNat
  let sigLen :=
    (cvMsg[6]!.toNat <<< 8) ||| cvMsg[7]!.toNat
  if cvMsg.size ≠ 8 + sigLen then
    return { s with phase := .failed "CV: signature length mismatch" }
  let mut signature : Array UInt8 := Array.replicate sigLen 0
  for i in [:sigLen] do
    signature := signature.set! i cvMsg[8 + i]!
  -- Compute Transcript-Hash(...Certificate) — i.e. transcript BEFORE this CV.
  let thBeforeCv := sha256 s.transcript
  -- Build signed content per §4.4.3.
  let pad : Array UInt8 := Array.replicate 64 0x20  -- 64 spaces
  let context : Array UInt8 := "TLS 1.3, server CertificateVerify".toUTF8.toList.toArray
  let signedContent : Array UInt8 := pad ++ context ++ #[0x00] ++ thBeforeCv
  -- Dispatch on signature scheme (RFC 8446 §4.2.3).
  match sigScheme with
  | 0x0807 =>
    -- ed25519: 32-byte pubkey, 64-byte raw sig.
    if sigLen ≠ 64 ∨ s.serverPubkey.size ≠ 32 then
      return { s with phase := .failed "CV: ed25519 key/sig size wrong" }
    if Sparkle.IP.Crypto.Ed25519Sign.verify s.serverPubkey signedContent signature then
      return { s with transcript := s.transcript ++ cvMsg
                    , phase := .waitFinished }
    else
      return { s with phase := .failed "CV: ed25519 sig verification failed" }
  | 0x0403 =>
    -- ecdsa_secp256r1_sha256: 65-byte SEC1-uncompressed pubkey,
    -- DER-encoded SEQUENCE { INTEGER r, INTEGER s } signature.
    match Sparkle.IP.Crypto.P256ECDSA.parsePubkeyRaw s.serverPubkey with
    | none =>
      return { s with phase := .failed "CV: bad ECDSA P-256 pubkey encoding" }
    | some q =>
      match Sparkle.IP.Crypto.P256ECDSA.parseDerSignature signature with
      | none =>
        return { s with phase := .failed "CV: bad ECDSA DER signature" }
      | some (r, sigS) =>
        let digest := Sparkle.IP.Crypto.HKDF.sha256 signedContent
        let z := Sparkle.IP.Crypto.P256ECDSA.digestToNat digest
        if Sparkle.IP.Crypto.P256ECDSA.verify q z r sigS then
          return { s with transcript := s.transcript ++ cvMsg
                        , phase := .waitFinished }
        else
          return { s with phase := .failed "CV: ECDSA P-256 sig verification failed" }
  | 0x0804 =>
    -- rsa_pss_rsae_sha256: DER-encoded RSAPublicKey in serverPubkey.
    match Sparkle.IP.Crypto.RSAPSS.parsePubkeyDer s.serverPubkey with
    | none =>
      return { s with phase := .failed "CV: bad RSA pubkey DER" }
    | some (n, e) =>
      if Sparkle.IP.Crypto.RSAPSS.verify n e signedContent signature then
        return { s with transcript := s.transcript ++ cvMsg
                      , phase := .waitFinished }
      else
        return { s with phase := .failed "CV: RSA-PSS sig verification failed" }
  | _ =>
    return { s with phase := .failed s!"CV: unsupported sig scheme {sigScheme}" }

/-- Receive server Finished.  Verify the server's
    verify_data, then derive application secrets from the
    transcript-hash AFTER server Finished, then build the
    client Finished verify_data and stamp it into the state. -/
def onServerFinished (s : State) (finMsg : Array UInt8) : State := Id.run do
  if finMsg.size < 4 ∨ finMsg[0]! ≠ HandshakeType.finished.toByte then
    return { s with phase := .failed "Fin: bad framing" }
  let bodyLen :=
    (finMsg[1]!.toNat <<< 16) |||
    (finMsg[2]!.toNat <<<  8) |||
     finMsg[3]!.toNat
  if finMsg.size ≠ 4 + bodyLen then
    return { s with phase := .failed "Fin: length mismatch" }
  let mut serverVerifyData : Array UInt8 := Array.replicate bodyLen 0
  for i in [:bodyLen] do
    serverVerifyData := serverVerifyData.set! i finMsg[4 + i]!
  match s.handshakeSecrets with
  | none => return { s with phase := .failed "Fin: missing handshake secrets" }
  | some hs =>
    -- Transcript hash through Certificate Verify (before Finished).
    let thBeforeServerFin := sha256 s.transcript
    let expectedVerifyData :=
      finishedVerifyData hs.sHsTrafficSecret thBeforeServerFin
    if expectedVerifyData ≠ serverVerifyData then
      return { s with phase := .failed "Fin: server verify_data mismatch" }
    -- Append server Finished, derive application secrets.
    let transcript' := s.transcript ++ finMsg
    let thAfterServerFin := sha256 transcript'
    let app := deriveApplicationStage hs.handshakeSecret thAfterServerFin
    -- Compute the client Finished verify_data (transcript NOW
    -- = ClientHello..server Finished, same as above).
    let clientFvd :=
      finishedVerifyData hs.cHsTrafficSecret thAfterServerFin
    return { s with transcript := transcript'
                  , applicationSecrets := some app
                  , clientFinishedVerifyData := clientFvd
                  , phase := .connected }

/-- Build the client Finished wire message (HandshakeType +
    u24 length + verify_data). -/
def buildClientFinished (s : State) : Array UInt8 :=
  buildFinished s.clientFinishedVerifyData

end Sparkle.IP.TLS.Client
