// The TLS 1.3 record layer (RFC 8446 §5): the envelope every other TLS message travels in.
// Without it the handshake this package builds can only be handed to QUIC as CRYPTO frames,
// because QUIC carries the messages itself and needs no record layer; over TCP there is
// nothing else to say where one message ends and the next begins. A record is a content
// type, a frozen legacy version, a length, and a fragment — until the keys arrive, after
// which the real type moves inside the encrypted body and the outer one lies and says
// application_data. This file is that framing, the AEAD that protects it, and the §5.3
// nonce that keeps every record under one key distinct.

///|
/// The change_cipher_spec content type (RFC 8446 §5). TLS 1.3 has no such message: the
/// record survives only so middleboxes watching for a TLS 1.2 handshake see one.
pub let tls_record_change_cipher_spec : Int = 20

///|
/// The alert content type (RFC 8446 §5).
pub let tls_record_alert : Int = 21

///|
/// The handshake content type (RFC 8446 §5).
pub let tls_record_handshake : Int = 22

///|
/// The application_data content type (RFC 8446 §5). Every TLSCiphertext carries it as the
/// outer `opaque_type` too, whatever the real type sealed inside turns out to be.
pub let tls_record_application_data : Int = 23

///|
/// The largest TLSPlaintext fragment (RFC 8446 §5.1): 2^14 octets.
pub let tls_record_max_fragment : Int = 16384

///|
/// The largest TLSCiphertext encrypted_record (RFC 8446 §5.2): 2^14 + 256 — the fragment
/// limit plus the room the inner content type, the padding and the AEAD tag need.
pub let tls_record_max_encrypted : Int = 16640

///|
/// The record header: content type, legacy_record_version, and a 16-bit length.
pub let tls_record_header_len : Int = 5

///|
/// One record's content type and fragment (RFC 8446 §5.1). For a TLSCiphertext the
/// `content_type` is the outer application_data and the `fragment` is still sealed.
pub(all) struct TlsRecord {
  content_type : Int
  fragment : Bytes
} derive(Eq, Debug)

///|
/// Whether `t` is a content type RFC 8446 §5 defines. Anything else on the wire is an
/// unexpected_message, not something to skip past.
fn tls_record_type_known(t : Int) -> Bool {
  t == tls_record_change_cipher_spec ||
  t == tls_record_alert ||
  t == tls_record_handshake ||
  t == tls_record_application_data
}

///|
/// Frame `fragment` as a TLSPlaintext record (RFC 8446 §5.1). legacy_record_version is
/// 0x0303 whatever the negotiated version is; a receiver ignores it.
pub fn tls_record_encode(
  content_type : Int,
  fragment : Bytes,
) -> Bytes raise TlsAlert {
  if fragment.length() > tls_record_max_fragment {
    raise tls_fatal(tls_alert_record_overflow)
  }
  let buf = Buffer()
  buf.write_byte((content_type & 0xff).to_byte())
  ks_write_u16(buf, tls_legacy_version)
  ks_write_u16(buf, fragment.length())
  buf.write_bytes(fragment[:])
  buf.to_bytes()
}

///|
/// Parse one record off the front of `view`, capping the fragment at `max`. `None` means
/// the view does not hold a whole record yet — the length is checked first, so an oversized
/// record fails immediately rather than waiting forever for bytes that may never come.
fn tls_record_read(
  view : BytesView,
  max : Int,
) -> (TlsRecord, Int)? raise TlsAlert {
  if view.length() < tls_record_header_len {
    return None
  }
  let content_type = view[0].to_int()
  if !tls_record_type_known(content_type) {
    raise tls_fatal(tls_alert_unexpected_message)
  }
  let len = ks_read_u16(view, 3)
  if len > max {
    raise tls_fatal(tls_alert_record_overflow)
  }
  // §5.1: only application_data may be sent zero-length, as a traffic-analysis
  // countermeasure. An empty handshake or alert record is a framing error.
  if len == 0 && content_type != tls_record_application_data {
    raise tls_fatal(tls_alert_unexpected_message)
  }
  let end = tls_record_header_len + len
  if view.length() < end {
    return None
  }
  Some(
    (
      { content_type, fragment: view[tls_record_header_len:end].to_owned(), },
      end,
    ),
  )
}

///|
/// Parse one TLSPlaintext record (RFC 8446 §5.1) and the number of bytes it consumed, or
/// `None` while the view is short of a whole record.
pub fn tls_record_parse(view : BytesView) -> (TlsRecord, Int)? raise TlsAlert {
  tls_record_read(view, tls_record_max_fragment)
}

///|
/// Parse one TLSCiphertext record (RFC 8446 §5.2) and the number of bytes it consumed. The
/// fragment is the still-sealed encrypted_record, so the ceiling is the larger §5.2 one.
pub fn tls_record_parse_encrypted(
  view : BytesView,
) -> (TlsRecord, Int)? raise TlsAlert {
  tls_record_read(view, tls_record_max_encrypted)
}

///|
/// A TLSInnerPlaintext (RFC 8446 §5.2): the content, then the content type the outer header
/// is hiding, then `padding` zero octets. The padding is the §5.4 length-hiding knob.
pub fn tls_record_inner(
  content_type : Int,
  content : Bytes,
  padding : Int,
) -> Bytes {
  let buf = Buffer()
  buf.write_bytes(content[:])
  buf.write_byte((content_type & 0xff).to_byte())
  for _i = 0; _i < padding; _i = _i + 1 {
    buf.write_byte(b'\x00')
  }
  buf.to_bytes()
}

///|
/// Split a TLSInnerPlaintext back into its content type and content (RFC 8446 §5.2): scan
/// back over the zero padding to the last non-zero octet, which is the type. An inner
/// plaintext of nothing but zeros names no type at all, which §5.2 makes an
/// unexpected_message rather than an empty record.
pub fn tls_record_unpad(inner : BytesView) -> (Int, Bytes) raise TlsAlert {
  let mut i = inner.length() - 1
  while i >= 0 && inner[i] == b'\x00' {
    i = i - 1
  }
  if i < 0 {
    raise tls_fatal(tls_alert_unexpected_message)
  }
  (inner[i].to_int(), inner[0:i].to_owned())
}

///|
/// The AEAD key and IV one direction protects its records with (RFC 8446 §7.3), at the
/// TLS_AES_128_GCM_SHA256 lengths.
pub(all) struct TlsTrafficKeys {
  key : Bytes
  iv : Bytes
} derive(Eq, Debug)

///|
/// Derive a direction's record-protection key and IV from its traffic secret (RFC 8446
/// §7.3). QUIC expands the same secret under "quic key"/"quic iv" and adds a
/// header-protection key (RFC 9001 §5.1); over TCP the labels are bare and there is no
/// header to protect.
pub fn tls13_traffic_keys(secret : Bytes) -> TlsTrafficKeys {
  {
    key: hkdf_expand_label(secret, b"key", b"", 16),
    iv: hkdf_expand_label(secret, b"iv", b"", 12),
  }
}

///|
/// The per-record AEAD nonce (RFC 8446 §5.3): the 64-bit sequence number, big-endian and
/// left-padded with zeros to the IV's length, XORed with the static write IV. The IV never
/// changes, so the sequence number alone is what keeps two records under one key from ever
/// sharing a nonce.
pub fn tls_record_nonce(iv : Bytes, seq : UInt64) -> Bytes {
  let buf = Buffer()
  for i = 0; i < iv.length(); i = i + 1 {
    let from_end = iv.length() - 1 - i
    let s = if from_end < 8 {
      (seq >> (from_end * 8)).to_byte().to_int()
    } else {
      0
    }
    buf.write_byte((iv[i].to_int() ^ s).to_byte())
  }
  buf.to_bytes()
}

///|
/// One direction's record protection: its traffic keys, and the sequence number that keeps
/// their nonces apart. RFC 8446 §5.3 restarts the sequence at zero on every key change,
/// which is what `rekey` is for — the IV is reused across a generation, so a sequence that
/// carried over would reuse a nonce under the new key.
pub struct TlsRecordKeys {
  mut keys : TlsTrafficKeys
  mut seq : UInt64
}

///|
/// Record protection under the traffic `secret`, starting at sequence zero.
pub fn TlsRecordKeys::new(secret : Bytes) -> TlsRecordKeys {
  { keys: tls13_traffic_keys(secret), seq: 0UL, }
}

///|
/// Install a new traffic secret and restart the sequence number at zero (RFC 8446 §5.3).
pub fn TlsRecordKeys::rekey(self : TlsRecordKeys, secret : Bytes) -> Unit {
  self.keys = tls13_traffic_keys(secret)
  self.seq = 0UL
}

///|
/// The traffic keys in force.
pub fn TlsRecordKeys::keys(self : TlsRecordKeys) -> TlsTrafficKeys {
  self.keys
}

///|
/// The sequence number the next record will use.
pub fn TlsRecordKeys::seq(self : TlsRecordKeys) -> UInt64 {
  self.seq
}

///|
/// The nonce the next record will be sealed or opened under (RFC 8446 §5.3).
pub fn TlsRecordKeys::nonce(self : TlsRecordKeys) -> Bytes {
  tls_record_nonce(self.keys.iv, self.seq)
}

///|
/// The five header octets of a TLSCiphertext whose encrypted_record is `len` long. They are
/// also the AEAD's additional data (RFC 8446 §5.2), which is why the length has to be
/// settled before the seal rather than measured after it.
fn tls_record_ciphertext_header(len : Int) -> Bytes {
  let buf = Buffer()
  buf.write_byte((tls_record_application_data & 0xff).to_byte())
  ks_write_u16(buf, tls_legacy_version)
  ks_write_u16(buf, len)
  buf.to_bytes()
}

///|
/// Seal `content` as a TLSCiphertext record (RFC 8446 §5.2) with `padding` zero octets
/// after the inner content type, and advance the sequence number.
pub fn TlsRecordKeys::seal_padded(
  self : TlsRecordKeys,
  content_type : Int,
  content : Bytes,
  padding : Int,
) -> Bytes raise TlsAlert {
  if content.length() > tls_record_max_fragment {
    raise tls_fatal(tls_alert_record_overflow)
  }
  let inner = tls_record_inner(content_type, content, padding)
  let len = inner.length() + 16
  if len > tls_record_max_encrypted {
    raise tls_fatal(tls_alert_record_overflow)
  }
  let aad = tls_record_ciphertext_header(len)
  let body = aes128_gcm_seal(self.keys.key, self.nonce(), inner, aad)
  self.seq = self.seq + 1UL
  bytes_concat(aad, body)
}

///|
/// Seal `content` as a TLSCiphertext record with no padding.
pub fn TlsRecordKeys::seal(
  self : TlsRecordKeys,
  content_type : Int,
  content : Bytes,
) -> Bytes raise TlsAlert {
  self.seal_padded(content_type, content, 0)
}

///|
/// Open one whole TLSCiphertext record — header included, since RFC 8446 §5.2 makes that
/// header the AEAD's additional data — into the content type and content sealed inside,
/// advancing the sequence number. A tag that does not verify is a bad_record_mac and the
/// sequence does not move, because §5.2 ends the connection there.
pub fn TlsRecordKeys::open(
  self : TlsRecordKeys,
  record : BytesView,
) -> (Int, Bytes) raise TlsAlert {
  guard tls_record_read(record, tls_record_max_encrypted) is Some((rec, _)) else {
    raise tls_fatal(tls_alert_decode_error)
  }
  let aad = record[0:tls_record_header_len].to_owned()
  guard aes128_gcm_open(self.keys.key, self.nonce(), rec.fragment, aad)
    is Some(inner) else {
    raise tls_fatal(tls_alert_bad_record_mac)
  }
  self.seq = self.seq + 1UL
  let (content_type, content) = tls_record_unpad(inner[:])
  if content.length() > tls_record_max_fragment {
    raise tls_fatal(tls_alert_record_overflow)
  }
  (content_type, content)
}