///|
/// QUIC packet protection (RFC 9001): the Initial key/iv/hp derive from the
/// client's Destination Connection ID (Appendix A.1), a client Initial packet is
/// protected and then recovered — AEAD authentication plus header protection —
/// and a key update advances the secret deterministically (§6.1).
///
///     moon run src/examples/04-crypto
#coverage.skip
fn main {
  run() catch {
    e => println("quic crypto error: \{e}")
  }
}

///|
fn run() -> Unit raise {
  let dcid = hexb("8394c8f03e515708")
  let keys = @crypto.initial(dcid[:]).0
  println("initial key = \{hex(keys.key)}")
  println("initial iv  = \{hex(keys.iv)}")
  println("initial hp  = \{hex(keys.hp)}")
  let frames : Array[@frame.Frame] = [
    Crypto(offset=0UL, data=b"\x01\x02\x03"),
    Ping,
  ]
  let packet = @conn.send_long(Initial, 1U, dcid, b"", 2L, 4, frames, keys)
  println("protected packet = \{packet.length()} bytes")
  match @conn.recv_long(packet, keys) {
    Some((got, pn)) =>
      println(
        "recovered pn=\{pn.to_string()} frames=\{got.length()} crypto+ping intact=\{got.length() >= 2 && got[0] == frames[0] && got[1] == frames[1]}",
      )
    None => println("authentication failed")
  }
  // Header-protection mask over the RFC 9001 A.2 sample, under the same client
  // Initial keys the appendix derives.
  let sample = hexb("d1b1c98dd7689fb8ec11d242b123dc9b")
  println("hp mask (A.2) = \{hex(keys.mask(sample[:]))}  (expect 437b9aec36)")
  // Key update (§6.1): the next secret is deterministic and distinct.
  let secret = @crypto.secret(dcid[:])
  let updated = @crypto.update(secret[:])
  println(
    "key update deterministic and advances = \{@crypto.update(secret[:]) == updated && updated != secret}",
  )
}

///|
fn hex(b : Bytes) -> String {
  let digits = "0123456789abcdef"
  let sb = StringBuilder()
  for i = 0; i < b.length(); i = i + 1 {
    let x = b[i].to_int()
    sb.write_char(digits[x >> 4].unsafe_to_char())
    sb.write_char(digits[x & 0xf].unsafe_to_char())
  }
  sb.to_string()
}

///|
fn hexb(h : String) -> Bytes {
  let buf = Buffer()
  for i = 0; i < h.length(); i = i + 2 {
    buf.write_byte(
      ((nib(h[i].to_int()) << 4) | nib(h[i + 1].to_int())).to_byte(),
    )
  }
  buf.to_bytes()
}

///|
fn nib(c : Int32) -> Int32 {
  if c >= 0x30 && c <= 0x39 {
    c - 0x30
  } else if c >= 0x61 && c <= 0x66 {
    c - 0x61 + 10
  } else {
    0
  }
}