// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0
///|
/// A protected long-header packet carrying `frames` at this level.
///
/// The three lines it stands for — build the header, seal the payload, protect the
/// header — are worth writing out once to see, and worth not writing out every time
/// after that. `@packet.Long::header` and `@crypto.Keys::seal` are still there for a
/// sender that wants the pieces.
pub fn send_long(
kind : @packet.Kind,
version : UInt32,
dcid : Bytes,
scid : Bytes,
number : Int64,
size : Int32,
frames : Array[@frame.Frame],
keys : @crypto.Keys,
token~ : Bytes = b"",
) -> Bytes {
seal_long(
kind,
version,
dcid,
scid,
number,
size,
@packet.payload(frames),
keys,
token~,
)
}
///|
/// The same from a payload already encoded, which is what padding an Initial to the
/// minimum datagram needs (RFC 9000 §14.1).
pub fn seal_long(
kind : @packet.Kind,
version : UInt32,
dcid : Bytes,
scid : Bytes,
number : Int64,
size : Int32,
payload : Bytes,
keys : @crypto.Keys,
token~ : Bytes = b"",
) -> Bytes {
let pn = @packet.number(number, None, size~)
let h : @packet.Long = {
packet_type: kind,
type_specific: 0,
version,
dcid,
scid,
}
keys.seal(
h.header(pn[:], payload=payload.length() + keys.suite.tag, token~)[:],
payload[:],
number~,
)
}
///|
/// A protected 1-RTT packet carrying `frames`.
pub fn send_short(
dcid : Bytes,
number : Int64,
size : Int32,
frames : Array[@frame.Frame],
keys : @crypto.Keys,
spin~ : Bool = false,
key_phase~ : Bool = false,
path~ : UInt64 = 0UL,
) -> Bytes {
let payload = @packet.payload(frames)
let pn = @packet.number(number, None, size~)
let h : @packet.Short = { spin, key_phase, pn_length: size, dcid, }
keys.seal(h.header(pn[:])[:], payload[:], number~, path~)
}
///|
/// A protected 0-RTT packet carrying `frames` (RFC 9001 §5.2): a long header of the 0-RTT
/// type, no Initial token, the client's early keys, and the Application packet-number space.
pub fn send_zero_rtt(
dcid : Bytes,
scid : Bytes,
number : Int64,
size : Int32,
frames : Array[@frame.Frame],
keys : @crypto.Keys,
version~ : UInt32 = @packet.version1,
) -> Bytes {
send_long(ZeroRtt, version, dcid, scid, number, size, frames, keys)
}
///|
/// The frames a protected long-header packet carries, and its packet number, expanded against
/// `largest`, the largest number received in its space. `None` when the tag does not match,
/// which is a packet to drop rather than an error to report.
pub fn recv_long(
packet : Bytes,
keys : @crypto.Keys,
largest~ : Int64 = -1L,
) -> (Array[@frame.Frame], Int64)? raise @packet.Refused {
match keys.open(packet[:], largest~) {
Some((payload, pn)) => Some((@packet.read_payload(payload), pn))
None => None
}
}
///|
/// The same for a 1-RTT packet, whose connection-id length only the receiver knows.
pub fn recv_short(
packet : Bytes,
dcid_len : Int32,
keys : @crypto.Keys,
largest~ : Int64 = -1L,
path~ : UInt64 = 0UL,
) -> (Array[@frame.Frame], Int64)? raise @packet.Refused {
match keys.open(packet[:], largest~, at=1 + dcid_len, path~) {
Some((payload, pn)) => Some((@packet.read_payload(payload), pn))
None => None
}
}
///|
/// A CertificateVerify body signed over the transcript as RFC 8446 §4.4.3 frames it.
///
/// This is the seam that keeps a signing key out of this library: a flight takes a
/// function from the transcript hash to a CertificateVerify body, and whoever holds the
/// key builds one with this.
pub fn signed(
signer : &@spec.Signer,
scheme~ : Int32 = @ext.ecdsa_secp256r1_sha256,
) -> (Bytes) -> Bytes {
th => {
@msg.certificate_verify(
signer.sign(@msg.signed(@msg.server_context, th[:])[:]),
scheme,
)
}
}
///|
/// Two byte strings joined.
fn join(a : Bytes, b : Bytes) -> Bytes {
let buf = Buffer()
buf.write_bytes(a)
buf.write_bytes(b)
buf.to_bytes()
}