feat(server): generate echo payload fill

This commit is contained in:
Nils Andreas Svee committed 2026-08-14 12:58:30 +07:00
1 parent 36500e1ea0
commit e4239f45c5
12 files changed
+721 -41

No files matched your search

+14 -9
View File
@@ -10,7 +10,7 @@ use crate::{
clock::{saturating_ns, ClockSample, ClockSource, SystemClock},
config::ServerConfig,
error::ServerError,
fill::negotiate_server_fill,
fill::{echo_payload_len, negotiate_server_fill},
negotiate::negotiate_params,
session::{RateDecision, Session},
token::{OsTokenSource, TokenSource, TOKEN_ATTEMPTS},
@@ -49,8 +49,8 @@ use crate::{
/// normal echo processing, per-session rate limiting, idle expiry,
/// maximum-duration close and client-initiated close. It decides the traffic
/// class each reply is to be sent with; actually applying it to a socket is the
/// runtime's job. The full server fill policy is a separate slice, and echo
/// payloads are zero-filled. Incoming authenticated requests enforce logical
/// runtime's job. It also settles each session's server fill and generates the
/// payload bytes of every echo reply. Incoming authenticated requests enforce logical
/// expiry exactly; the runtime also calls a private maintenance hook to reclaim
/// idle sessions when no traffic arrives.
#[derive(Debug)]
@@ -356,6 +356,10 @@ impl ServerCore {
// it.
let sent_at = self.clock.sample();
let params = session.params();
// Fill bytes are prepared before the reply is built, so they go through
// the encoder's normal path and the packet's MAC covers them like every
// other byte. Nothing mutates a payload after encoding.
let payload_len = echo_payload_len(self.config.hmac_key().is_some(), params);
// Read before the session can be released below: a maximum-duration
// close is an ordinary echo reply and carries the session's marking like
// any other.
@@ -374,12 +378,13 @@ impl ServerCore {
recv_count: params.received_stats.has_count().then_some(next.count),
recv_window: params.received_stats.has_window().then_some(next.window),
timestamps: timestamp_fields(params, request.received_at, sent_at),
// No payload bytes of our own: the encoder zero-fills the whole
// region between the field block and the negotiated length. That is
// the initial safe fill policy — payload bytes carry no protocol
// meaning, and zeroes cannot disclose residue from another request
// or another client. Request payload bytes never reach a reply.
payload: Vec::new(),
// The session's negotiated fill, generated fresh for this reply and
// sized from the layout rather than from `params.length`. A `none`
// session contributes no bytes and the encoder's zero-fill stands,
// which is what keeps a no-fill reply from disclosing residue.
// Nothing from the request reaches here: its payload is opaque, and
// ServerFill describes the server's own bytes.
payload: session.fill().payload(payload_len),
};
let packet = encode_echo_reply(&reply, params, self.config.hmac_key())
.map_err(|source| ServerError::ReplyEncoding { source })?;