// The record-layer driver a transport pumps (RFC 8446 §5): bytes in, handshake messages and
// application data out, and the reverse. It owns the two things a caller should not have to
// re-derive at every call site — the read and write sequence numbers, and the reassembly
// buffer that lets one handshake message span several records while several messages share
// one. Records arrive in the clear until a traffic secret is installed and as TLSCiphertext
// after; change_cipher_spec is the one exception, staying unencrypted for as long as §5
// tolerates it at all. Nothing here touches a socket: a malformed record raises the §5
// alert and the caller decides how the connection dies.

///|
/// What a record turned into. A `HandshakeMsg` carries the whole message, header included,
/// so it can go straight into the transcript hash as well as be parsed.
pub(all) enum TlsRecordEvent {
  HandshakeMsg(msg_type~ : Int, message~ : Bytes)
  AppData(Bytes)
  PeerAlert(TlsAlert)
} derive(Eq, Debug)

///|
/// A record layer in progress: what has arrived but not yet parsed, the handshake bytes
/// still short of a whole message, and each direction's keys once they exist.
pub struct TlsRecordLayer {
  mut inbox : Bytes
  mut partial : Bytes
  mut read : TlsRecordKeys?
  mut write : TlsRecordKeys?
  mut compat : Bool
}

///|
/// A record layer with no keys yet: everything is TLSPlaintext, as it is for the
/// ClientHello and ServerHello.
pub fn TlsRecordLayer::new() -> TlsRecordLayer {
  { inbox: b"", partial: b"", read: None, write: None, compat: true, }
}

///|
/// Install the read traffic secret: records from the peer are TLSCiphertext from here on
/// and the receive sequence restarts at zero (RFC 8446 §5.3).
pub fn TlsRecordLayer::set_read_secret(
  self : TlsRecordLayer,
  secret : Bytes,
) -> Unit {
  match self.read {
    Some(keys) => keys.rekey(secret)
    None => self.read = Some(TlsRecordKeys::new(secret))
  }
}

///|
/// Install the write traffic secret: records this endpoint sends are TLSCiphertext from
/// here on and the send sequence restarts at zero (RFC 8446 §5.3).
pub fn TlsRecordLayer::set_write_secret(
  self : TlsRecordLayer,
  secret : Bytes,
) -> Unit {
  match self.write {
    Some(keys) => keys.rekey(secret)
    None => self.write = Some(TlsRecordKeys::new(secret))
  }
}

///|
/// The sequence number the next record read will use, zero while no read keys are in.
pub fn TlsRecordLayer::read_seq(self : TlsRecordLayer) -> UInt64 {
  match self.read {
    Some(keys) => keys.seq()
    None => 0UL
  }
}

///|
/// The sequence number the next record written will use, zero while no write keys are in.
pub fn TlsRecordLayer::write_seq(self : TlsRecordLayer) -> UInt64 {
  match self.write {
    Some(keys) => keys.seq()
    None => 0UL
  }
}

///|
/// Close the middlebox-compatibility window (RFC 8446 §5): once the handshake is finished a
/// change_cipher_spec has nothing left to pretend to be, and the next one is an
/// unexpected_message.
pub fn TlsRecordLayer::handshake_done(self : TlsRecordLayer) -> Unit {
  self.compat = false
}

///|
/// Whether a handshake message has arrived in part and is waiting on the rest of itself.
pub fn TlsRecordLayer::has_partial(self : TlsRecordLayer) -> Bool {
  self.partial.length() > 0
}

///|
/// Turn one record's content into events. Handshake content is appended to the reassembly
/// buffer and every whole message now in it comes out, which is what makes a message split
/// across records and several messages in one record the same code path.
fn TlsRecordLayer::deliver(
  self : TlsRecordLayer,
  content_type : Int,
  content : Bytes,
  events : Array[TlsRecordEvent],
) -> Unit raise TlsAlert {
  if content_type == tls_record_handshake {
    self.partial = bytes_concat(self.partial, content)
    let view = self.partial[:]
    let mut off = 0
    for ;; {
      match tls_parse_handshake(view[off:]) {
        Some((msg_type, body)) => {
          let used = 4 + body.length()
          events.push(
            HandshakeMsg(msg_type~, message=view[off:off + used].to_owned()),
          )
          off = off + used
        }
        None => break
      }
    }
    self.partial = view[off:view.length()].to_owned()
    return
  }
  // §5.1: handshake messages must not be interleaved with records of another type, so
  // anything arriving mid-message is a framing error rather than the next thing to handle.
  if self.partial.length() > 0 {
    raise tls_fatal(tls_alert_unexpected_message)
  }
  if content_type == tls_record_application_data {
    events.push(AppData(content))
  } else if content_type == tls_record_alert {
    // §6: an alert is two octets, never split across records and never sharing one.
    if content.length() != 2 {
      raise tls_fatal(tls_alert_decode_error)
    }
    guard tls_decode_alert(content[:]) is Some(alert) else {
      raise tls_fatal(tls_alert_decode_error)
    }
    events.push(PeerAlert(alert))
  } else {
    // A change_cipher_spec that reached this far came out of a TLSCiphertext, and §5 makes
    // a protected one an unexpected_message.
    raise tls_fatal(tls_alert_unexpected_message)
  }
}

///|
/// Feed received bytes and take out everything now complete, in wire order. Bytes short of
/// a whole record are held for the next call. A record that breaks §5 — an undefined
/// content type, a length past the limit, a change_cipher_spec outside its window, a tag
/// that does not verify — raises the alert §5 names for it, and the connection is over.
pub fn TlsRecordLayer::feed(
  self : TlsRecordLayer,
  bytes : Bytes,
) -> Array[TlsRecordEvent] raise TlsAlert {
  self.inbox = bytes_concat(self.inbox, bytes)
  let events : Array[TlsRecordEvent] = []
  let view = self.inbox[:]
  let mut off = 0
  for ;; {
    let rest = view[off:]
    if rest.length() < tls_record_header_len {
      break
    }
    let outer = rest[0].to_int()
    // A change_cipher_spec stays in the clear even after the keys are in (§5), so it is the
    // outer content type, not just the presence of keys, that decides how to read a record.
    let protect = self.read is Some(_) && outer != tls_record_change_cipher_spec
    let limit = if protect {
      tls_record_max_encrypted
    } else {
      tls_record_max_fragment
    }
    guard tls_record_read(rest, limit) is Some((rec, used)) else { break }
    if protect {
      // Once the read keys are in, the only outer type left is application_data: a
      // handshake or alert record still in the clear is an unexpected_message.
      if outer != tls_record_application_data {
        raise tls_fatal(tls_alert_unexpected_message)
      }
      guard self.read is Some(keys) else {
        raise tls_fatal(tls_alert_internal_error)
      }
      let (content_type, content) = keys.open(rest[0:used])
      self.deliver(content_type, content, events)
    } else if rec.content_type == tls_record_change_cipher_spec {
      // §5: tolerated between the first ClientHello and the peer's Finished, and only as
      // the single octet 0x01. It means nothing in TLS 1.3, so it is dropped rather than
      // delivered — but a late one, or one carrying anything else, ends the connection.
      if !self.compat || rec.fragment != b"\x01" {
        raise tls_fatal(tls_alert_unexpected_message)
      }
    } else if rec.content_type == tls_record_application_data {
      // Application data is never sent in the clear: with no read keys installed there is
      // nothing the peer could legitimately be saying under this content type.
      raise tls_fatal(tls_alert_unexpected_message)
    } else {
      self.deliver(rec.content_type, rec.fragment, events)
    }
    off = off + used
  }
  self.inbox = view[off:view.length()].to_owned()
  events
}

///|
/// One record's worth of `content`, sealed if the write keys are in and TLSPlaintext if not.
fn TlsRecordLayer::write_record(
  self : TlsRecordLayer,
  content_type : Int,
  content : Bytes,
) -> Bytes raise TlsAlert {
  match self.write {
    Some(keys) => keys.seal(content_type, content)
    None => tls_record_encode(content_type, content)
  }
}

///|
/// `content` as however many records it takes, splitting at the §5.1 fragment limit.
fn TlsRecordLayer::write_fragmented(
  self : TlsRecordLayer,
  content_type : Int,
  content : Bytes,
) -> Bytes raise TlsAlert {
  if content.length() <= tls_record_max_fragment {
    return self.write_record(content_type, content)
  }
  let out = Buffer()
  let mut off = 0
  while off < content.length() {
    let left = content.length() - off
    let n = if left < tls_record_max_fragment {
      left
    } else {
      tls_record_max_fragment
    }
    out.write_bytes(
      self.write_record(content_type, content[off:off + n].to_owned())[:],
    )
    off = off + n
  }
  out.to_bytes()
}

///|
/// The wire bytes for one or more handshake messages. Pass a whole flight to coalesce it
/// into one record; a message past the fragment limit is split across records by itself
/// (RFC 8446 §5.1), which handshake content is the only content allowed to do.
pub fn TlsRecordLayer::write_handshake(
  self : TlsRecordLayer,
  messages : Bytes,
) -> Bytes raise TlsAlert {
  self.write_fragmented(tls_record_handshake, messages)
}

///|
/// The wire bytes for application data, split across records at the fragment limit.
pub fn TlsRecordLayer::write_app(
  self : TlsRecordLayer,
  data : Bytes,
) -> Bytes raise TlsAlert {
  self.write_fragmented(tls_record_application_data, data)
}

///|
/// The wire bytes for an alert (RFC 8446 §6): one record, two octets, never fragmented.
pub fn TlsRecordLayer::write_alert(
  self : TlsRecordLayer,
  alert : TlsAlert,
) -> Bytes raise TlsAlert {
  self.write_record(tls_record_alert, tls_encode_alert(alert))
}

///|
/// The middlebox-compatibility change_cipher_spec record (RFC 8446 §5): always these six
/// octets — the content type, the frozen version, a length of one, and the single octet
/// 0x01 — always unencrypted, and never meaning anything.
pub let tls_record_ccs : Bytes = b"\x14\x03\x03\x00\x01\x01"