Skip to main content

subc_daemon/
server.rs

1use std::{
2    error::Error,
3    fmt, io,
4    net::SocketAddr,
5    sync::{
6        atomic::{AtomicU64, Ordering as AtomicOrdering},
7        Arc,
8    },
9    time::{Duration, Instant},
10};
11
12use subc_transport::{
13    authenticate_server, AuthError, AuthStage, DAEMON_ID_LEN, WATCHDOG_CLIENT_ROLE,
14};
15use tokio::{
16    io::{AsyncRead, AsyncWrite, AsyncWriteExt, BufReader, BufWriter},
17    net::TcpListener,
18    sync::{mpsc, Semaphore},
19    task::{JoinHandle, JoinSet},
20    time::timeout,
21};
22use tracing::{debug, info, warn};
23
24use crate::{
25    forwarding::{CloseReason, ConnectionCloseReceiver},
26    observability::ConnectedClients,
27    read_frame,
28    router::{FrameSink, RouteCtx, Router},
29    write_frame, FrameIoError, RouterError,
30};
31
32/// Bytes (envelope header plus body) one connection's egress queue may hold
33/// before a non-waiting enqueue is refused, which closes that connection.
34///
35/// Sized for a client that is slow but still reading: one connection can
36/// multiplex many token streams, and at roughly 200-byte frames (21-byte header
37/// plus body) 4 MiB is about 19,000 queued frames, i.e. thousands of frames on
38/// each of several routes, enough to ride out a pause of seconds. It is also the
39/// most a stuck client can make the daemon hold for it: 4 MiB per connection,
40/// so even 128 stuck connections stay near 512 MiB rather than growing without
41/// bound. A single frame larger than the whole budget is still admitted into an
42/// empty queue so it can be sent at all.
43pub const CONNECTION_EGRESS_BYTE_BUDGET: usize = 4 * 1024 * 1024;
44/// Frame-count backstop for the same queue (the tokio channel's capacity).
45/// The byte budget is the real bound; this one only matters for floods of tiny
46/// or empty frames, which cost bookkeeping per frame rather than bytes. 32,768
47/// frames times 128 bytes is exactly the 4 MiB budget, so for any mean frame of
48/// 128 bytes or more the byte budget binds first; below that (for example
49/// 21-byte empty frames, of which 4 MiB would be almost 200,000) the count
50/// does. The channel allocates slots lazily, so a large capacity costs nothing
51/// until it is used.
52pub const CONNECTION_EGRESS_FRAME_CAP: usize = 32 * 1024;
53/// A pending `route.open` holds one reserved slot in the connection's egress
54/// queue until its module answers, and its response is sent outside the byte
55/// budget. Eight concurrent opens per connection keep a reconnect burst
56/// parallel while bounding both the reserved slots and the bytes that bypass
57/// the budget.
58pub const MAX_PENDING_ROUTE_OPENS_PER_CONNECTION: usize = 8;
59/// A reconnect herd may spread one target's opens over many client connections.
60/// Two safe per-connection bursts retain useful parallelism without restoring
61/// the hundreds-of-binds fanout that serial dispatch used to suppress.
62pub(crate) const MAX_PENDING_ROUTE_BINDS_PER_TARGET: usize =
63    MAX_PENDING_ROUTE_OPENS_PER_CONNECTION * 2;
64pub const DEFAULT_AUTH_DEADLINE: Duration = Duration::from_secs(2);
65// Sized for the restart-herd shape: after a daemon bounce, every live client
66// connection plus all supervised children re-dial within the same second
67// (~120+ observed on the 2026-07-14 fleet). Handshakes are cheap loopback
68// HMAC exchanges; the deadline, not the permit count, is the DoS bound.
69pub const DEFAULT_MAX_UNAUTHENTICATED_CONNECTIONS: usize = 256;
70const CLOSE_DRAIN_GRACE: Duration = Duration::from_secs(2);
71const PRE_AUTH_REJECT_LOG_INTERVAL: Duration = Duration::from_secs(1);
72
73#[derive(Clone, Copy, Debug)]
74enum PreAuthRejectStage {
75    WaitingForHandshakeSlot,
76    HandshakeDeadline,
77    InvalidProof,
78    MalformedMessage,
79    PeerClosed,
80    OtherAuthenticationFailure,
81}
82
83impl PreAuthRejectStage {
84    const fn index(self) -> usize {
85        match self {
86            Self::WaitingForHandshakeSlot => 0,
87            Self::HandshakeDeadline => 1,
88            Self::InvalidProof => 2,
89            Self::MalformedMessage => 3,
90            Self::PeerClosed => 4,
91            Self::OtherAuthenticationFailure => 5,
92        }
93    }
94
95    const fn stage(self) -> &'static str {
96        match self {
97            Self::WaitingForHandshakeSlot => "waiting_for_handshake_slot",
98            Self::HandshakeDeadline => "handshake_deadline",
99            Self::InvalidProof => "client_authentication",
100            Self::MalformedMessage => "malformed_message",
101            Self::PeerClosed => "peer_closed",
102            Self::OtherAuthenticationFailure => "authentication",
103        }
104    }
105
106    const fn reason(self) -> &'static str {
107        match self {
108            Self::WaitingForHandshakeSlot => "capacity_wait_timed_out",
109            Self::HandshakeDeadline => "handshake_deadline_expired",
110            Self::InvalidProof => "client_proof_mismatch",
111            Self::MalformedMessage => "malformed_or_oversized_message",
112            Self::PeerClosed => "peer_closed_before_authentication_completed",
113            Self::OtherAuthenticationFailure => "authentication_failed_before_routing",
114        }
115    }
116}
117
118#[derive(Debug)]
119struct PreAuthRejectRateLimit {
120    next_allowed_ns: AtomicU64,
121    suppressed: AtomicU64,
122}
123
124impl PreAuthRejectRateLimit {
125    const fn new() -> Self {
126        Self {
127            next_allowed_ns: AtomicU64::new(0),
128            suppressed: AtomicU64::new(0),
129        }
130    }
131}
132
133/// Rate-limit state for pre-authentication rejection logs, owned by one
134/// `ServerAuth` so each listener (and each test) counts only its own rejects.
135#[derive(Debug)]
136struct PreAuthRejectLog {
137    epoch: Instant,
138    limiter: PreAuthRejectLimiter,
139}
140
141impl PreAuthRejectLog {
142    fn new() -> Self {
143        Self {
144            epoch: Instant::now(),
145            limiter: PreAuthRejectLimiter::new(),
146        }
147    }
148}
149
150// Report accumulated rejects on the next allowed line instead of running a timer task.
151#[derive(Debug)]
152struct PreAuthRejectLimiter {
153    stages: [PreAuthRejectRateLimit; 6],
154}
155
156impl PreAuthRejectLimiter {
157    fn new() -> Self {
158        Self {
159            stages: std::array::from_fn(|_| PreAuthRejectRateLimit::new()),
160        }
161    }
162
163    fn allow_at(&self, stage: PreAuthRejectStage, now: Duration) -> Option<u64> {
164        let slot = &self.stages[stage.index()];
165        let now_ns = now.as_nanos().min(u64::MAX as u128) as u64;
166        let interval_ns = PRE_AUTH_REJECT_LOG_INTERVAL.as_nanos() as u64;
167        let mut next_allowed_ns = slot.next_allowed_ns.load(AtomicOrdering::Relaxed);
168
169        loop {
170            if now_ns < next_allowed_ns {
171                slot.suppressed.fetch_add(1, AtomicOrdering::Relaxed);
172                return None;
173            }
174
175            match slot.next_allowed_ns.compare_exchange_weak(
176                next_allowed_ns,
177                now_ns.saturating_add(interval_ns),
178                AtomicOrdering::Relaxed,
179                AtomicOrdering::Relaxed,
180            ) {
181                Ok(_) => return Some(slot.suppressed.swap(0, AtomicOrdering::Relaxed)),
182                Err(actual) => next_allowed_ns = actual,
183            }
184        }
185    }
186}
187
188fn log_pre_auth_rejection(
189    error: &ConnectionError,
190    peer_addr: SocketAddr,
191    elapsed: Duration,
192    now: Duration,
193    limiter: &PreAuthRejectLimiter,
194) {
195    let Some(stage) = error.pre_auth_reject_stage() else {
196        return;
197    };
198    let Some(suppressed_since_last) = limiter.allow_at(stage, now) else {
199        return;
200    };
201    let elapsed_ms = elapsed.as_millis().min(u64::MAX as u128) as u64;
202
203    info!(
204        peer_addr = %peer_addr,
205        elapsed_ms,
206        stage = stage.stage(),
207        reason = stage.reason(),
208        auth_stage = ?error.auth_stage(),
209        suppressed_since_last,
210        error = %error,
211        "subc TCP connection rejected before routing"
212    );
213}
214
215async fn handle_accepted_connection<S>(
216    stream: S,
217    router: Arc<Router>,
218    auth: ServerAuth,
219    peer_addr: SocketAddr,
220    accepted_at: Instant,
221) -> Result<(), ConnectionError>
222where
223    S: AsyncRead + AsyncWrite + Unpin + Send + 'static,
224{
225    let reject_log = Arc::clone(&auth.pre_auth_reject_log);
226    let result = handle_connection(stream, router, auth).await;
227    if let Err(error) = &result {
228        if error.is_quiet_reject() {
229            log_pre_auth_rejection(
230                error,
231                peer_addr,
232                accepted_at.elapsed(),
233                reject_log.epoch.elapsed(),
234                &reject_log.limiter,
235            );
236        } else {
237            warn!(?peer_addr, error = %error, "subc connection ended with error");
238        }
239    }
240    result
241}
242
243/// Authentication material and DoS bounds applied before a TCP connection may
244/// reach the frame router.
245#[derive(Clone)]
246pub struct ServerAuth {
247    key: Arc<[u8]>,
248    daemon_id: [u8; DAEMON_ID_LEN],
249    daemon_ver: Arc<str>,
250    deadline: Duration,
251    unauthenticated: Arc<Semaphore>,
252    connected_clients: ConnectedClients,
253    pre_auth_reject_log: Arc<PreAuthRejectLog>,
254}
255
256impl ServerAuth {
257    pub fn new(
258        key: Vec<u8>,
259        daemon_id: [u8; DAEMON_ID_LEN],
260        daemon_ver: impl Into<String>,
261    ) -> Self {
262        Self::with_limits(
263            key,
264            daemon_id,
265            daemon_ver,
266            DEFAULT_AUTH_DEADLINE,
267            DEFAULT_MAX_UNAUTHENTICATED_CONNECTIONS,
268        )
269    }
270
271    // Production limits are deliberately not config-routed: loosening pre-auth DoS posture changes attack surface.
272    pub fn with_limits(
273        key: Vec<u8>,
274        daemon_id: [u8; DAEMON_ID_LEN],
275        daemon_ver: impl Into<String>,
276        deadline: Duration,
277        max_unauthenticated: usize,
278    ) -> Self {
279        Self {
280            key: Arc::from(key),
281            daemon_id,
282            daemon_ver: Arc::from(daemon_ver.into()),
283            deadline,
284            unauthenticated: Arc::new(Semaphore::new(max_unauthenticated.max(1))),
285            connected_clients: ConnectedClients::new(),
286            pre_auth_reject_log: Arc::new(PreAuthRejectLog::new()),
287        }
288    }
289
290    pub fn with_connected_clients(mut self, connected_clients: ConnectedClients) -> Self {
291        self.connected_clients = connected_clients;
292        self
293    }
294}
295
296impl fmt::Debug for ServerAuth {
297    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
298        f.debug_struct("ServerAuth")
299            .field("key", &"<redacted>")
300            .field("daemon_id", &self.daemon_id)
301            .field("daemon_ver", &self.daemon_ver)
302            .field("deadline", &self.deadline)
303            .finish_non_exhaustive()
304    }
305}
306
307/// Serve an already-bound TCP listener. Each accepted connection gets its own
308/// async task so concurrent clients do not block the accept loop.
309pub async fn serve_listener(
310    listener: TcpListener,
311    router: Arc<Router>,
312    auth: ServerAuth,
313) -> Result<(), ServerError> {
314    serve_listener_with_accept(listener.local_addr().ok(), router, auth, || {
315        listener.accept()
316    })
317    .await
318}
319
320async fn serve_listener_with_accept<A, F>(
321    local_addr: Option<SocketAddr>,
322    router: Arc<Router>,
323    auth: ServerAuth,
324    mut accept: A,
325) -> Result<(), ServerError>
326where
327    A: FnMut() -> F,
328    F: std::future::Future<Output = io::Result<(tokio::net::TcpStream, SocketAddr)>>,
329{
330    loop {
331        let (stream, peer_addr) = match accept().await {
332            Ok(accepted) => accepted,
333            Err(source) => {
334                let kind = source.kind();
335                // Aborted/reset connections and interrupted syscalls affect one accept only.
336                // File-descriptor or socket-buffer exhaustion needs a short backoff to avoid spinning;
337                // other errors may mean the listener itself is unusable.
338                let exhausted = accept_resource_exhausted(&source);
339                if matches!(
340                    kind,
341                    io::ErrorKind::ConnectionAborted
342                        | io::ErrorKind::ConnectionReset
343                        | io::ErrorKind::Interrupted
344                ) || exhausted
345                {
346                    warn!(?local_addr, error = %source, "temporary TCP accept failure");
347                    if exhausted {
348                        tokio::time::sleep(Duration::from_millis(75)).await;
349                    }
350                    continue;
351                }
352                return Err(ServerError::Accept { local_addr, source });
353            }
354        };
355        let accepted_at = Instant::now();
356        // Every route frame is a discrete message whose reply the peer is waiting
357        // for, so there is never a later write for Nagle to coalesce with -- it can
358        // only hold a frame back until an ACK arrives.
359        //
360        // MEASURED: no effect on this transport. A 50-sample-per-arm sweep from
361        // 1 to 32 KiB over the full client->daemon->module->client path showed a
362        // flat 0.28-0.38ms p50 with no step at any buffer boundary, because a
363        // loopback ACK returns in microseconds and never reaches the delayed-ACK
364        // timer that makes Nagle expensive on a real network. Kept anyway: it is
365        // one syscall at accept, it removes the mechanism rather than relying on
366        // loopback staying fast, and Windows loopback was not part of that sweep.
367        // Do not cite it as a latency fix -- the measurement says it is not one.
368        //
369        // Failure is not fatal: the connection works, and refusing to serve a
370        // client over a socket option would be worse than anything it saves.
371        if let Err(source) = stream.set_nodelay(true) {
372            warn!(?peer_addr, error = %source, "could not disable Nagle on accepted connection");
373        }
374        debug!(?peer_addr, ?local_addr, "accepted subc TCP connection");
375        let router = Arc::clone(&router);
376        let auth = auth.clone();
377        tokio::spawn(async move {
378            let _ = handle_accepted_connection(stream, router, auth, peer_addr, accepted_at).await;
379        });
380    }
381}
382
383fn accept_resource_exhausted(error: &io::Error) -> bool {
384    // ENFILE/EMFILE/ENOBUFS by name rather than number: the numbers differ by
385    // platform (ENOBUFS is 55 on macOS and 105 on Linux, and each number means
386    // something unrelated on the other), so a literal list misclassifies.
387    // Winsock: WSAEMFILE and WSAENOBUFS.
388    #[cfg(unix)]
389    {
390        use rustix::io::Errno;
391        let exhausted = [Errno::NFILE, Errno::MFILE, Errno::NOBUFS];
392        error
393            .raw_os_error()
394            .is_some_and(|code| exhausted.iter().any(|e| e.raw_os_error() == code))
395    }
396    #[cfg(windows)]
397    {
398        matches!(error.raw_os_error(), Some(10024 | 10055))
399    }
400    #[cfg(not(any(unix, windows)))]
401    {
402        let _ = error;
403        false
404    }
405}
406
407/// Serve all already-bound loopback TCP listeners until one accept loop fails.
408pub async fn serve_listeners(
409    listeners: Vec<TcpListener>,
410    router: Arc<Router>,
411    auth: ServerAuth,
412) -> Result<(), ServerError> {
413    if listeners.is_empty() {
414        return Err(ServerError::NoListeners);
415    }
416
417    let (tx, mut rx) = mpsc::channel(listeners.len());
418    let mut accept_tasks = AbortTasksOnDrop::default();
419    for listener in listeners {
420        let router = Arc::clone(&router);
421        let auth = auth.clone();
422        let tx = tx.clone();
423        accept_tasks.push(tokio::spawn(async move {
424            let result = serve_listener(listener, router, auth).await;
425            let _ = tx.send(result).await;
426        }));
427    }
428    drop(tx);
429
430    rx.recv().await.unwrap_or(Ok(()))
431}
432
433#[derive(Default)]
434struct AbortTasksOnDrop {
435    handles: Vec<JoinHandle<()>>,
436}
437
438impl AbortTasksOnDrop {
439    fn push(&mut self, handle: JoinHandle<()>) {
440        self.handles.push(handle);
441    }
442}
443
444impl Drop for AbortTasksOnDrop {
445    fn drop(&mut self) {
446        for handle in &self.handles {
447            if !handle.is_finished() {
448                handle.abort();
449            }
450        }
451    }
452}
453
454#[derive(Debug)]
455enum ConnectionLoopExit {
456    PeerClosed,
457    CloseRequested(CloseReason),
458}
459
460/// Run the authenticated frame read -> route loop for one connection.
461///
462/// Every accepted TCP connection must complete the key-auth prelude before any
463/// envelope bytes are read by the router. Outbound frames flow through a bounded
464/// [`FrameSink`] drained by one writer task. Inbound dispatch remains serial for
465/// every frame except `route.open` and activity operations; their bind/file waits
466/// run in connection-owned tasks so unrelated frames can keep flowing.
467pub async fn handle_connection<S>(
468    mut stream: S,
469    router: Arc<Router>,
470    auth: ServerAuth,
471) -> Result<(), ConnectionError>
472where
473    S: AsyncRead + AsyncWrite + Unpin + Send + 'static,
474{
475    // Over-cap connections WAIT for a handshake slot (bounded by the auth deadline)
476    // instead of being reset. A restart herd — every client and supervised child
477    // re-dialing the fresh daemon at once — otherwise loses supervised modules to
478    // the permit lottery: each reset burns a module restart-budget slot, and a
479    // module that treats auth failure as fatal can exhaust its budget into
480    // state=failed within the boot window (2026-07-14 aft outage). On loopback
481    // with the pre-auth HMAC deadline, a bounded queue is strictly safer than a
482    // reset.
483    //
484    // Pre-auth time is governed by TWO deliberately separate budgets: the queue
485    // wait below is bounded by `auth.deadline`, and `authenticate_server` then
486    // starts a FRESH `auth.deadline` for the handshake itself. Total pre-auth
487    // occupancy per connection is therefore up to 2x the configured deadline.
488    // This is intentional, not an accounting slip: charging queue time against
489    // the handshake budget would hand a herd-queued supervised module a
490    // near-zero handshake window under CPU saturation, recreating the fatal
491    // auth-failure -> restart-budget-burn path this queue exists to prevent.
492    // On a loopback-only, key-authenticated listener the doubled bound is a
493    // per-connection occupancy cost, not a meaningful DoS surface.
494    let permit =
495        match tokio::time::timeout(auth.deadline, auth.unauthenticated.clone().acquire_owned())
496            .await
497        {
498            Ok(Ok(permit)) => permit,
499            Ok(Err(_)) | Err(_) => {
500                let _ = stream.shutdown().await;
501                return Err(ConnectionError::UnauthenticatedCapacity);
502            }
503        };
504
505    let authenticated = authenticate_server(
506        &mut stream,
507        auth.key.as_ref(),
508        &auth.daemon_id,
509        auth.daemon_ver.as_ref(),
510        auth.deadline,
511    )
512    .await
513    .map_err(ConnectionError::Auth)?;
514    drop(permit);
515
516    let mut connection = router.begin_connection();
517    let connection_id = connection.id();
518    // `authenticated.role` is CLIENT-SUPPLIED and unverified -- the handshake
519    // proves possession of the connection key, nothing about who is calling. So
520    // this exclusion is safe ONLY while the role decides a reporting question and
521    // never an authorization one: the watchdog's own loopback probe would
522    // otherwise inflate the very count it exists to sanity-check.
523    //
524    // The consequence of it being unverified, stated so nobody has to rediscover
525    // it: ANY client holding the key can claim this role and omit itself from
526    // `connected_clients`. That is a gauge a caller can lie to, and it is
527    // acceptable because the gauge informs an operator rather than gating
528    // anything. IF A ROLE IS EVER USED TO DECIDE ADMISSION, CAPACITY, OR PRIVILEGE,
529    // this stops being safe and the role must be attested rather than declared --
530    // the daemon already has the mechanism for that in the spawn-nonce path used
531    // for module identity.
532    let _connected_client = (authenticated.role != WATCHDOG_CLIENT_ROLE)
533        .then(|| auth.connected_clients.open(connection_id));
534    let close_receiver = connection.take_close_receiver();
535    debug!(
536        connection_id = connection_id.get(),
537        "subc authenticated connection opened"
538    );
539
540    let (read_half, write_half) = tokio::io::split(stream);
541    // Authentication is complete before this buffer can read ahead. The connection loop
542    // retains a partial frame read across completed route.open tasks; dropping the read
543    // happens only when a frame finishes or the connection ends.
544    let mut read_half = BufReader::new(read_half);
545    let (egress, rx) = connection_egress();
546    let mut writer = tokio::spawn(drain_writer(write_half, rx));
547
548    let ctx = RouteCtx {
549        connection_id,
550        egress: egress.clone(),
551    };
552
553    let mut route_open_tasks = JoinSet::new();
554    let loop_result = connection_loop(
555        &mut read_half,
556        Arc::clone(&router),
557        ctx.clone(),
558        close_receiver,
559        &mut route_open_tasks,
560    )
561    .await;
562
563    // Every in-flight route.open must have FINISHED before `connection` drops,
564    // because that drop runs the forwarding cleanup for this id, and cleanup is
565    // also where the id's closing mark is lifted. Dropping a JoinSet only
566    // requests abort: a task already running on another worker keeps going
567    // until its next await, and could commit a route for this connection after
568    // cleanup has cleared it, with nothing left to refuse it and nothing to
569    // clean it up. shutdown() aborts and then waits.
570    route_open_tasks.shutdown().await;
571
572    drop(ctx);
573    drop(egress);
574    drop(connection);
575
576    let close_reason = match &loop_result {
577        Ok(ConnectionLoopExit::CloseRequested(reason)) => Some(reason.to_string()),
578        Ok(ConnectionLoopExit::PeerClosed) | Err(_) => None,
579    };
580    let writer_result = if close_reason.is_some() {
581        match timeout(CLOSE_DRAIN_GRACE, &mut writer).await {
582            Ok(result) => Some(result.map_err(ConnectionError::WriterTask)),
583            Err(_) => {
584                warn!(
585                    connection_id = connection_id.get(),
586                    grace = ?CLOSE_DRAIN_GRACE,
587                    "connection writer did not drain after close request; aborting writer task"
588                );
589                writer.abort();
590                let _ = writer.await;
591                None
592            }
593        }
594    } else {
595        Some(writer.await.map_err(ConnectionError::WriterTask))
596    };
597
598    let result = if let Some(reason) = close_reason.as_deref() {
599        match writer_result {
600            Some(Ok(Ok(()))) | None => Ok(()),
601            Some(Ok(Err(writer_err))) => {
602                debug!(
603                    connection_id = connection_id.get(),
604                    close_reason = reason,
605                    writer_error = %writer_err,
606                    "writer failed after requested connection close"
607                );
608                Ok(())
609            }
610            Some(Err(join_err)) => {
611                warn!(
612                    connection_id = connection_id.get(),
613                    close_reason = reason,
614                    join_error = %join_err,
615                    "writer task join failed after requested connection close"
616                );
617                Ok(())
618            }
619        }
620    } else {
621        let writer_result =
622            writer_result.expect("writer result is present without a close request");
623        match (loop_result, writer_result) {
624            (Err(loop_err), Ok(Ok(()))) => Err(loop_err),
625            (Err(loop_err), Ok(Err(writer_err))) => {
626                warn!(
627                    connection_id = connection_id.get(),
628                    writer_error = %writer_err,
629                    "writer failed while closing after connection error"
630                );
631                Err(loop_err)
632            }
633            (Err(loop_err), Err(join_err)) => {
634                warn!(
635                    connection_id = connection_id.get(),
636                    join_error = %join_err,
637                    "writer task join failed while closing after connection error"
638                );
639                Err(loop_err)
640            }
641            (Ok(ConnectionLoopExit::PeerClosed), Ok(Ok(()))) => Ok(()),
642            (Ok(ConnectionLoopExit::PeerClosed), Ok(Err(writer_err))) => {
643                Err(ConnectionError::FrameIo(writer_err))
644            }
645            (Ok(ConnectionLoopExit::PeerClosed), Err(join_err)) => Err(join_err),
646            (Ok(ConnectionLoopExit::CloseRequested(_)), _) => {
647                unreachable!("close requests are handled before normal writer result matching")
648            }
649        }
650    };
651
652    match &result {
653        Ok(()) => {
654            if let Some(reason) = close_reason.as_deref() {
655                debug!(
656                    connection_id = connection_id.get(),
657                    close_reason = reason,
658                    "subc connection closed by request"
659                );
660            } else {
661                debug!(
662                    connection_id = connection_id.get(),
663                    "subc connection closed"
664                );
665            }
666        }
667        Err(err) => debug!(
668            connection_id = connection_id.get(),
669            error = %err,
670            "subc connection exited with error"
671        ),
672    }
673
674    result
675}
676
677async fn connection_loop<R>(
678    read_half: &mut R,
679    router: Arc<Router>,
680    ctx: RouteCtx,
681    mut close_receiver: ConnectionCloseReceiver,
682    route_open_tasks: &mut JoinSet<Result<(), RouterError>>,
683) -> Result<ConnectionLoopExit, ConnectionError>
684where
685    R: AsyncRead + Unpin,
686{
687    let mut activity_tasks = JoinSet::new();
688    loop {
689        while let Some(result) = activity_tasks.try_join_next() {
690            finish_route_open_task(result)?;
691        }
692        while let Some(result) = route_open_tasks.try_join_next() {
693            finish_route_open_task(result)?;
694        }
695
696        // Keep the same read future when a route.open completes: read_frame owns
697        // partial header/body buffers that would be lost if that future were dropped.
698        let read = read_frame(&mut *read_half);
699        tokio::pin!(read);
700        let frame = loop {
701            tokio::select! {
702                close = &mut close_receiver => {
703                    return Ok(ConnectionLoopExit::CloseRequested(close_reason(close)));
704                }
705                result = route_open_tasks.join_next(), if !route_open_tasks.is_empty() => {
706                    finish_route_open_task(
707                        result.expect("a non-empty route.open JoinSet has a next task")
708                    )?;
709                }
710                result = activity_tasks.join_next(), if !activity_tasks.is_empty() => {
711                    finish_route_open_task(result.expect("non-empty activity task set"))?;
712                }
713                result = &mut read => {
714                    break match result.map_err(ConnectionError::FrameIo)? {
715                        Some(frame) => frame,
716                        None => return Ok(ConnectionLoopExit::PeerClosed),
717                    };
718                }
719            }
720        };
721
722        if frame.header.channel == 0
723            && frame.header.ty == subc_protocol::FrameType::Request
724            && serde_json::from_slice::<serde_json::Value>(&frame.body)
725                .ok()
726                .is_some_and(|body| {
727                    matches!(
728                        body["op"].as_str(),
729                        Some(
730                            subc_control::ops::ACTIVITY_RECORD
731                                | subc_control::ops::ACTIVITY_QUERY
732                                | subc_control::ops::ACTIVITY_SUBSCRIBE
733                        )
734                    )
735                })
736        {
737            // Bound outstanding activity tasks as well as the disk queue,
738            // so the reader never waits for either capacity when the disk stalls.
739            if activity_tasks.len() >= 128 {
740                let response = crate::control::control_error_frame(
741                    &frame,
742                    subc_control::ACTIVITY_UNAVAILABLE,
743                    "activity queue full",
744                )
745                .map_err(ConnectionError::Router)?;
746                ctx.egress
747                    .send(response)
748                    .await
749                    .map_err(ConnectionError::Router)?;
750            } else {
751                let task_router = Arc::clone(&router);
752                let task_ctx = ctx.clone();
753                activity_tasks.spawn(async move {
754                    route_open_tail(task_router, task_ctx, frame, Instant::now()).await
755                });
756            }
757            continue;
758        }
759
760        if let Some(target_module_id) = router.route_open_target(&frame) {
761            // A task can finish while the reader is waiting for the next frame.
762            // Reap it before checking admission so completed work never occupies
763            // one of the deliberately scarce connection slots.
764            while let Some(result) = route_open_tasks.try_join_next() {
765                finish_route_open_task(result)?;
766            }
767
768            if route_open_tasks.len() >= MAX_PENDING_ROUTE_OPENS_PER_CONNECTION {
769                // Never wait for capacity here: doing so would recreate the same
770                // reader head-of-line stall with a smaller constant.
771                let refusal = router
772                    .route_open_capacity_refusal(
773                        &ctx,
774                        &frame,
775                        &target_module_id,
776                        route_open_tasks.len(),
777                        MAX_PENDING_ROUTE_OPENS_PER_CONNECTION,
778                    )
779                    .map_err(ConnectionError::Router)?;
780                let send_result = tokio::select! {
781                    close = &mut close_receiver => {
782                        return Ok(ConnectionLoopExit::CloseRequested(close_reason(close)));
783                    }
784                    result = ctx.egress.send(refusal) => result,
785                };
786                send_result.map_err(ConnectionError::Router)?;
787                continue;
788            }
789
790            let task_router = Arc::clone(&router);
791            let task_ctx = ctx.clone();
792            // Start slow-dispatch timing before spawn so it covers the task's
793            // full scheduler and handler lifetime, as inline timing did.
794            let dispatch_started_at = Instant::now();
795            route_open_tasks.spawn(async move {
796                route_open_tail(task_router, task_ctx, frame, dispatch_started_at).await
797            });
798            continue;
799        }
800
801        // Other frames still route serially: routing can backpressure reads,
802        // and a connection close cancels the in-progress route.
803        let route_result = tokio::select! {
804            close = &mut close_receiver => {
805                return Ok(ConnectionLoopExit::CloseRequested(close_reason(close)));
806            }
807            result = router.route_for_connection(&ctx, frame) => result,
808        };
809
810        if let Err(err) = route_result {
811            if let Some(error_frame) = err.to_error_frame() {
812                warn!(
813                    connection_id = ctx.connection_id.get(),
814                    error = %err,
815                    "routing failure recovered with ERROR frame"
816                );
817                let send_result = tokio::select! {
818                    close = &mut close_receiver => {
819                        return Ok(ConnectionLoopExit::CloseRequested(close_reason(close)));
820                    }
821                    result = ctx.egress.send(error_frame) => result,
822                };
823                send_result.map_err(ConnectionError::Router)?;
824            } else {
825                debug!(
826                    connection_id = ctx.connection_id.get(),
827                    error = %err,
828                    "fatal routing failure"
829                );
830                return Err(ConnectionError::Router(err));
831            }
832        }
833    }
834}
835
836async fn route_open_tail(
837    router: Arc<Router>,
838    ctx: RouteCtx,
839    frame: crate::Frame,
840    dispatch_started_at: Instant,
841) -> Result<(), RouterError> {
842    match router
843        .route_for_connection_started(&ctx, frame, Some(dispatch_started_at))
844        .await
845    {
846        Ok(()) => Ok(()),
847        Err(err) => {
848            let Some(error_frame) = err.to_error_frame() else {
849                return Err(err);
850            };
851            warn!(
852                connection_id = ctx.connection_id.get(),
853                error = %err,
854                "routing failure recovered with ERROR frame"
855            );
856            ctx.egress.send(error_frame).await
857        }
858    }
859}
860
861fn finish_route_open_task(
862    result: Result<Result<(), RouterError>, tokio::task::JoinError>,
863) -> Result<(), ConnectionError> {
864    match result {
865        Ok(Ok(())) => Ok(()),
866        Ok(Err(err)) => Err(ConnectionError::Router(err)),
867        Err(err) => Err(ConnectionError::Router(RouterError::backend(
868            0,
869            0,
870            format!("route.open task failed: {err}"),
871        ))),
872    }
873}
874
875fn close_reason(
876    result: Result<CloseReason, tokio::sync::oneshot::error::RecvError>,
877) -> CloseReason {
878    result.unwrap_or_else(|_| {
879        CloseReason::new(
880            "close_registry_dropped",
881            "connection close registration was dropped without a reason",
882        )
883    })
884}
885
886/// The outbound queue for one daemon connection: a sink bounded by
887/// [`CONNECTION_EGRESS_BYTE_BUDGET`] and [`CONNECTION_EGRESS_FRAME_CAP`], and
888/// the receiver its writer drains.
889pub(crate) fn connection_egress() -> (FrameSink, mpsc::Receiver<crate::router::OutboundFrame>) {
890    let (tx, rx) = mpsc::channel::<crate::router::OutboundFrame>(CONNECTION_EGRESS_FRAME_CAP);
891    (
892        FrameSink::with_byte_budget(tx, CONNECTION_EGRESS_BYTE_BUDGET),
893        rx,
894    )
895}
896
897async fn drain_writer<W>(
898    write_half: W,
899    mut rx: mpsc::Receiver<crate::router::OutboundFrame>,
900) -> Result<(), FrameIoError>
901where
902    W: AsyncWrite + Unpin,
903{
904    let mut writer = BufWriter::new(write_half);
905    while let Some(outbound) = rx.recv().await {
906        write_outbound(&mut writer, outbound).await?;
907        while let Ok(outbound) = rx.try_recv() {
908            write_outbound(&mut writer, outbound).await?;
909        }
910        writer.flush().await.map_err(FrameIoError::Io)?;
911    }
912    writer.flush().await.map_err(FrameIoError::Io)?;
913    Ok(())
914}
915
916/// Reply-path half of slow-control diagnosis: a channel-0 reply that sat in
917/// the writer queue past the threshold is reported with its queue residency
918/// and its own write duration separated, because "writer task not scheduled"
919/// and "socket write blocked" are different defects and the sum hides which.
920/// Data-plane frames are exempt: their latency is the client's own flow
921/// control, and logging them would drown the control signal in bulk traffic.
922async fn write_outbound<W>(
923    writer: &mut BufWriter<W>,
924    outbound: crate::router::OutboundFrame,
925) -> Result<(), FrameIoError>
926where
927    W: AsyncWrite + Unpin,
928{
929    const SLOW_REPLY_QUEUE: Duration = Duration::from_millis(1000);
930    let queued = outbound.enqueued_at.elapsed();
931    // Held until this frame has been written, then dropped at the end of this
932    // function, which gives its bytes back to the connection's egress budget.
933    let charge = outbound.charge;
934    if let Some(charge) = &charge {
935        charge.taken_by_writer();
936    }
937    let frame = outbound.frame;
938    if frame.header.channel == 0 && queued >= SLOW_REPLY_QUEUE {
939        let write_started = std::time::Instant::now();
940        let result = write_frame(writer, &frame).await;
941        tracing::warn!(
942            corr = frame.header.corr,
943            queued_ms = queued.as_millis() as u64,
944            write_ms = write_started.elapsed().as_millis() as u64,
945            "slow control reply write"
946        );
947        result?;
948    } else {
949        write_frame(writer, &frame).await?;
950    }
951    if let Some(flushed) = outbound.flushed {
952        writer.flush().await.map_err(FrameIoError::Io)?;
953        let _ = flushed.send(());
954    }
955    Ok(())
956}
957
958#[cfg(all(test, unix))]
959#[tokio::test]
960async fn shutdown_notice_ack_waits_for_socket_flush() {
961    let (socket, mut peer) = tokio::io::duplex(1);
962    let (tx, rx) = mpsc::channel(1);
963    let sink = FrameSink::new(tx);
964    let writer = tokio::spawn(drain_writer(socket, rx));
965    let frame = crate::Frame::build(
966        subc_protocol::FrameType::Push,
967        subc_protocol::Flags::new(false, subc_protocol::Priority::Interactive, false),
968        0,
969        0,
970        0,
971        b"notice".to_vec(),
972    )
973    .unwrap();
974    let send = sink.send_flushed(frame);
975    tokio::pin!(send);
976    assert!(
977        timeout(Duration::from_millis(20), &mut send).await.is_err(),
978        "queueing bytes is not a socket flush acknowledgement"
979    );
980    let (sent, received) = timeout(Duration::from_secs(1), async {
981        tokio::join!(&mut send, read_frame(&mut peer))
982    })
983    .await
984    .unwrap();
985    sent.unwrap();
986    assert_eq!(received.unwrap().unwrap().body, b"notice");
987    writer.abort();
988}
989
990#[derive(Debug)]
991pub enum ServerError {
992    NoListeners,
993    Accept {
994        local_addr: Option<SocketAddr>,
995        source: io::Error,
996    },
997}
998
999impl fmt::Display for ServerError {
1000    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1001        match self {
1002            Self::NoListeners => write!(f, "no TCP listeners were provided"),
1003            Self::Accept { local_addr, source } => match local_addr {
1004                Some(addr) => write!(f, "failed to accept TCP connection on {addr}: {source}"),
1005                None => write!(f, "failed to accept TCP connection: {source}"),
1006            },
1007        }
1008    }
1009}
1010
1011impl Error for ServerError {
1012    fn source(&self) -> Option<&(dyn Error + 'static)> {
1013        match self {
1014            Self::Accept { source, .. } => Some(source),
1015            Self::NoListeners => None,
1016        }
1017    }
1018}
1019
1020#[derive(Debug)]
1021pub enum ConnectionError {
1022    Auth(AuthError),
1023    UnauthenticatedCapacity,
1024    FrameIo(FrameIoError),
1025    Router(RouterError),
1026    WriterTask(tokio::task::JoinError),
1027}
1028
1029impl ConnectionError {
1030    fn is_quiet_reject(&self) -> bool {
1031        matches!(self, Self::Auth(_) | Self::UnauthenticatedCapacity)
1032    }
1033
1034    fn pre_auth_reject_stage(&self) -> Option<PreAuthRejectStage> {
1035        match self {
1036            Self::UnauthenticatedCapacity => Some(PreAuthRejectStage::WaitingForHandshakeSlot),
1037            Self::Auth(AuthError::Timeout { .. }) => Some(PreAuthRejectStage::HandshakeDeadline),
1038            Self::Auth(
1039                AuthError::InvalidClientAuth
1040                | AuthError::InvalidServerProof
1041                | AuthError::DaemonIdMismatch,
1042            ) => Some(PreAuthRejectStage::InvalidProof),
1043            Self::Auth(AuthError::MessageTooLarge { .. } | AuthError::JsonDecode { .. }) => {
1044                Some(PreAuthRejectStage::MalformedMessage)
1045            }
1046            Self::Auth(AuthError::UnexpectedEof { .. }) => Some(PreAuthRejectStage::PeerClosed),
1047            Self::Auth(AuthError::Io { source, .. })
1048                if matches!(
1049                    source.kind(),
1050                    io::ErrorKind::BrokenPipe
1051                        | io::ErrorKind::ConnectionAborted
1052                        | io::ErrorKind::ConnectionReset
1053                        | io::ErrorKind::UnexpectedEof
1054                ) =>
1055            {
1056                Some(PreAuthRejectStage::PeerClosed)
1057            }
1058            Self::Auth(_) => Some(PreAuthRejectStage::OtherAuthenticationFailure),
1059            Self::FrameIo(_) | Self::Router(_) | Self::WriterTask(_) => None,
1060        }
1061    }
1062
1063    fn auth_stage(&self) -> Option<AuthStage> {
1064        match self {
1065            Self::Auth(
1066                AuthError::Io { stage, .. }
1067                | AuthError::Timeout { stage, .. }
1068                | AuthError::UnexpectedEof { stage, .. }
1069                | AuthError::MessageTooLarge { stage, .. }
1070                | AuthError::JsonEncode { stage, .. }
1071                | AuthError::JsonDecode { stage, .. },
1072            ) => Some(*stage),
1073            Self::Auth(
1074                AuthError::Random(_)
1075                | AuthError::KeyTooShort { .. }
1076                | AuthError::InvalidServerProof
1077                | AuthError::DaemonIdMismatch
1078                | AuthError::InvalidClientAuth,
1079            )
1080            | Self::UnauthenticatedCapacity
1081            | Self::FrameIo(_)
1082            | Self::Router(_)
1083            | Self::WriterTask(_) => None,
1084        }
1085    }
1086}
1087
1088impl fmt::Display for ConnectionError {
1089    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1090        match self {
1091            Self::Auth(err) => write!(f, "connection auth failed: {err}"),
1092            Self::UnauthenticatedCapacity => write!(
1093                f,
1094                "too many concurrent unauthenticated subc TCP connections"
1095            ),
1096            Self::FrameIo(err) => write!(f, "frame connection error: {err}"),
1097            Self::Router(err) => write!(f, "router connection error: {err}"),
1098            Self::WriterTask(err) => write!(f, "connection writer task failed: {err}"),
1099        }
1100    }
1101}
1102
1103impl Error for ConnectionError {
1104    fn source(&self) -> Option<&(dyn Error + 'static)> {
1105        match self {
1106            Self::Auth(err) => Some(err),
1107            Self::FrameIo(err) => Some(err),
1108            Self::Router(err) => Some(err),
1109            Self::WriterTask(err) => Some(err),
1110            Self::UnauthenticatedCapacity => None,
1111        }
1112    }
1113}
1114
1115#[cfg(test)]
1116mod tests {
1117    use std::{
1118        pin::Pin,
1119        sync::atomic::{AtomicUsize, Ordering},
1120        task::{Context, Poll},
1121    };
1122
1123    use super::*;
1124    use serde::{de::DeserializeOwned, Serialize};
1125    use subc_protocol::{
1126        DecodeError, ErrorBody, Flags, FrameType, Priority, HEADER_LEN, PROTOCOL_VERSION,
1127    };
1128    use tokio::io::{duplex, AsyncReadExt, AsyncWriteExt, ReadBuf};
1129
1130    use subc_transport::{
1131        authenticate_client, compute_proof, ClientAuth, ClientHello, ConnectionInfo, Endpoint,
1132        ServerProof, CLIENT_AUTH_DOMAIN, NONCE_LEN, SCHEMA_VERSION,
1133    };
1134
1135    use crate::{ControlHandler, EchoBackend, Frame, ReadStage, Registry};
1136
1137    const TEST_DEADLINE: Duration = Duration::from_secs(2);
1138    const TEST_DAEMON_VER: &str = "test-subc-server";
1139
1140    /// Reply-path stamp, slow polarity: a channel-0 reply whose queue residency
1141    /// exceeds the threshold must produce the slow-control-reply-write WARN with
1142    /// queued_ms reflecting the residency. Uses a backdated stamp rather than a
1143    /// real stall so the test is fast and deterministic.
1144    #[tokio::test]
1145    async fn stale_queued_control_reply_logs_slow_reply_write() {
1146        let (logs, _guard) = crate::router::test_log::log_capture(tracing::Level::WARN);
1147        let (tx, rx) = mpsc::channel::<crate::router::OutboundFrame>(4);
1148        let reply = Frame::build_with_version(
1149            PROTOCOL_VERSION,
1150            FrameType::Error,
1151            Flags::new(false, Priority::Interactive, false),
1152            0,
1153            0,
1154            7,
1155            serde_json::to_vec(&ErrorBody {
1156                code: "test".into(),
1157                message: "reply".into(),
1158                detail: None,
1159            })
1160            .expect("body encodes"),
1161        )
1162        .expect("frame builds");
1163        tx.send(crate::router::OutboundFrame {
1164            frame: reply,
1165            enqueued_at: std::time::Instant::now() - Duration::from_millis(1500),
1166            flushed: None,
1167            charge: None,
1168        })
1169        .await
1170        .expect("queued");
1171        drop(tx);
1172        let (write_half, mut read_half) = duplex(64 * 1024);
1173        drain_writer(write_half, rx).await.expect("writer drains");
1174        let mut sink = Vec::new();
1175        read_half.read_to_end(&mut sink).await.expect("read");
1176        assert!(!sink.is_empty(), "frame reached the socket");
1177        let captured = crate::router::test_log::captured_logs(&logs);
1178        assert!(
1179            captured.contains("slow control reply write") && captured.contains("corr=7"),
1180            "expected slow reply WARN naming corr, got: {captured}"
1181        );
1182        let queued_ms: u64 = captured
1183            .split("queued_ms=")
1184            .nth(1)
1185            .and_then(|s| s.split_whitespace().next())
1186            .and_then(|s| s.parse().ok())
1187            .expect("queued_ms present");
1188        assert!(
1189            queued_ms >= 1500,
1190            "queued_ms reflects residency: {queued_ms}"
1191        );
1192    }
1193
1194    /// Fast polarity: a promptly-drained control reply and a stale DATA-PLANE
1195    /// frame must both stay silent — the WARN is channel-0-only by design, and
1196    /// a healthy queue must add zero log volume.
1197    #[tokio::test]
1198    async fn fresh_control_and_stale_data_frames_log_nothing() {
1199        let (logs, _guard) = crate::router::test_log::log_capture(tracing::Level::WARN);
1200        let (tx, rx) = mpsc::channel::<crate::router::OutboundFrame>(4);
1201        let control = Frame::build_with_version(
1202            PROTOCOL_VERSION,
1203            FrameType::Error,
1204            Flags::new(false, Priority::Interactive, false),
1205            0,
1206            0,
1207            8,
1208            serde_json::to_vec(&ErrorBody {
1209                code: "test".into(),
1210                message: "fresh".into(),
1211                detail: None,
1212            })
1213            .expect("body encodes"),
1214        )
1215        .expect("frame builds");
1216        tx.send(crate::router::OutboundFrame {
1217            frame: control,
1218            enqueued_at: std::time::Instant::now(),
1219            flushed: None,
1220            charge: None,
1221        })
1222        .await
1223        .expect("queued");
1224        let data = Frame::build_with_version(
1225            PROTOCOL_VERSION,
1226            FrameType::Error,
1227            Flags::new(false, Priority::Interactive, false),
1228            9,
1229            1,
1230            9,
1231            serde_json::to_vec(&ErrorBody {
1232                code: "test".into(),
1233                message: "data".into(),
1234                detail: None,
1235            })
1236            .expect("body encodes"),
1237        )
1238        .expect("frame builds");
1239        tx.send(crate::router::OutboundFrame {
1240            frame: data,
1241            enqueued_at: std::time::Instant::now() - Duration::from_millis(5000),
1242            flushed: None,
1243            charge: None,
1244        })
1245        .await
1246        .expect("queued");
1247        drop(tx);
1248        let (write_half, _read_half) = duplex(64 * 1024);
1249        drain_writer(write_half, rx).await.expect("writer drains");
1250        let captured = crate::router::test_log::captured_logs(&logs);
1251        assert!(
1252            !captured.contains("slow control reply write"),
1253            "no WARN for fresh control or stale data frames, got: {captured}"
1254        );
1255    }
1256
1257    struct CountingReader {
1258        bytes: Vec<u8>,
1259        offset: usize,
1260        first_read_end: Option<usize>,
1261        reads: Arc<AtomicUsize>,
1262    }
1263
1264    impl CountingReader {
1265        fn new(bytes: Vec<u8>, first_read_end: Option<usize>) -> (Self, Arc<AtomicUsize>) {
1266            let reads = Arc::new(AtomicUsize::new(0));
1267            (
1268                Self {
1269                    bytes,
1270                    offset: 0,
1271                    first_read_end,
1272                    reads: Arc::clone(&reads),
1273                },
1274                reads,
1275            )
1276        }
1277    }
1278
1279    impl AsyncRead for CountingReader {
1280        fn poll_read(
1281            mut self: Pin<&mut Self>,
1282            _cx: &mut Context<'_>,
1283            buf: &mut ReadBuf<'_>,
1284        ) -> Poll<io::Result<()>> {
1285            self.reads.fetch_add(1, Ordering::Relaxed);
1286            let available = self.bytes.len().saturating_sub(self.offset);
1287            let first_read_remaining = self
1288                .first_read_end
1289                .filter(|end| self.offset < *end)
1290                .map_or(available, |end| end - self.offset);
1291            let count = available.min(first_read_remaining).min(buf.remaining());
1292            let end = self.offset + count;
1293            buf.put_slice(&self.bytes[self.offset..end]);
1294            self.offset = end;
1295            Poll::Ready(Ok(()))
1296        }
1297    }
1298
1299    fn encode_frames(frames: &[Frame]) -> Vec<u8> {
1300        let mut bytes = Vec::new();
1301        for frame in frames {
1302            bytes.extend_from_slice(&frame.header.encode());
1303            bytes.extend_from_slice(&frame.body);
1304        }
1305        bytes
1306    }
1307
1308    async fn read_frames<R>(reader: &mut R, count: usize) -> Vec<Frame>
1309    where
1310        R: AsyncRead + Unpin,
1311    {
1312        let mut frames = Vec::with_capacity(count);
1313        for _ in 0..count {
1314            frames.push(read_frame(reader).await.unwrap().unwrap());
1315        }
1316        frames
1317    }
1318
1319    fn request(channel: u16, corr: u64, body: &[u8]) -> Frame {
1320        Frame::build(
1321            FrameType::Request,
1322            Flags::new(true, Priority::Interactive, false),
1323            channel,
1324            0,
1325            corr,
1326            body.to_vec(),
1327        )
1328        .unwrap()
1329    }
1330
1331    fn echo_router() -> Arc<Router> {
1332        let mut router = Router::with_default_self_handler();
1333        router.register_backend(7, EchoBackend).unwrap();
1334        router.register_backend(9, EchoBackend).unwrap();
1335        Arc::new(router)
1336    }
1337
1338    fn test_auth() -> (ServerAuth, ConnectionInfo) {
1339        test_auth_with_limit(4)
1340    }
1341
1342    fn test_auth_with_limit(max_unauthenticated: usize) -> (ServerAuth, ConnectionInfo) {
1343        test_auth_with_deadline_and_limit(TEST_DEADLINE, max_unauthenticated)
1344    }
1345
1346    fn test_auth_with_deadline_and_limit(
1347        deadline: Duration,
1348        max_unauthenticated: usize,
1349    ) -> (ServerAuth, ConnectionInfo) {
1350        let key = vec![0x42; 32];
1351        let daemon_id = [0x24; 16];
1352        let conn = ConnectionInfo {
1353            schema: SCHEMA_VERSION,
1354            wire_version: None,
1355            endpoints: vec![Endpoint {
1356                host: "127.0.0.1".to_owned(),
1357                port: 1,
1358            }],
1359            key: key.clone(),
1360            daemon_id,
1361            pid: std::process::id(),
1362            daemon_ver: TEST_DAEMON_VER.to_owned(),
1363        };
1364        (
1365            ServerAuth::with_limits(
1366                key,
1367                daemon_id,
1368                TEST_DAEMON_VER,
1369                deadline,
1370                max_unauthenticated,
1371            ),
1372            conn,
1373        )
1374    }
1375
1376    async fn authenticate<S>(stream: &mut S, conn: &ConnectionInfo)
1377    where
1378        S: AsyncRead + AsyncWrite + Unpin,
1379    {
1380        authenticate_client(stream, conn, TEST_DEADLINE)
1381            .await
1382            .expect("test client should authenticate")
1383    }
1384
1385    async fn write_auth_message<S, T>(stream: &mut S, value: &T)
1386    where
1387        S: AsyncWrite + Unpin,
1388        T: Serialize,
1389    {
1390        let body = serde_json::to_vec(value).expect("auth message encodes");
1391        stream
1392            .write_all(&(body.len() as u32).to_le_bytes())
1393            .await
1394            .expect("auth message length writes");
1395        stream
1396            .write_all(&body)
1397            .await
1398            .expect("auth message body writes");
1399    }
1400
1401    async fn read_auth_message<S, T>(stream: &mut S) -> T
1402    where
1403        S: AsyncRead + Unpin,
1404        T: DeserializeOwned,
1405    {
1406        let mut length = [0; 4];
1407        stream
1408            .read_exact(&mut length)
1409            .await
1410            .expect("auth message length reads");
1411        let mut body = vec![0; u32::from_le_bytes(length) as usize];
1412        stream
1413            .read_exact(&mut body)
1414            .await
1415            .expect("auth message body reads");
1416        serde_json::from_slice(&body).expect("auth message decodes")
1417    }
1418
1419    async fn send_wrong_key_client_auth<S>(stream: &mut S, conn: &ConnectionInfo)
1420    where
1421        S: AsyncRead + AsyncWrite + Unpin,
1422    {
1423        let hello = ClientHello {
1424            client_nonce: [0x57; NONCE_LEN],
1425            role: "client".to_owned(),
1426        };
1427        write_auth_message(stream, &hello).await;
1428        let proof: ServerProof = read_auth_message(stream).await;
1429        let mut wrong_key = conn.key.clone();
1430        wrong_key[0] ^= 0xff;
1431        let client_auth = ClientAuth {
1432            client_auth: compute_proof(
1433                &wrong_key,
1434                CLIENT_AUTH_DOMAIN,
1435                &hello.client_nonce,
1436                &proof.server_nonce,
1437                &conn.daemon_id,
1438            ),
1439        };
1440        write_auth_message(stream, &client_auth).await;
1441    }
1442
1443    fn assert_pre_auth_log(logs: &Arc<std::sync::Mutex<Vec<u8>>>, stage: &str, port: u16) {
1444        let captured = crate::router::test_log::captured_logs(logs);
1445        assert!(
1446            captured.contains(&format!("stage=\"{stage}\"")),
1447            "expected rejection stage {stage}, got: {captured}"
1448        );
1449        assert!(
1450            captured.contains(&format!("peer_addr=127.0.0.1:{port}")),
1451            "expected peer address, got: {captured}"
1452        );
1453        assert!(
1454            captured.contains("elapsed_ms=") && captured.contains("suppressed_since_last="),
1455            "expected elapsed time and suppression count, got: {captured}"
1456        );
1457        assert!(
1458            !captured.contains("WARN"),
1459            "pre-auth rejection must not be logged as a warning: {captured}"
1460        );
1461    }
1462
1463    #[tokio::test]
1464    async fn logs_handshake_deadline_pre_auth_rejection() {
1465        let (logs, _guard) = crate::router::test_log::log_capture(tracing::Level::INFO);
1466        let deadline = Duration::from_millis(60);
1467        let (auth, _) = test_auth_with_deadline_and_limit(deadline, 4);
1468        let (client, server_stream) = duplex(512);
1469        let port = 41001;
1470        let server = handle_accepted_connection(
1471            server_stream,
1472            echo_router(),
1473            auth,
1474            SocketAddr::from(([127, 0, 0, 1], port)),
1475            Instant::now(),
1476        );
1477
1478        let error = tokio::time::timeout(deadline * 3, server)
1479            .await
1480            .expect("idle handshake should end at its deadline")
1481            .expect_err("idle handshake must be rejected");
1482        drop(client);
1483        assert!(matches!(
1484            error,
1485            ConnectionError::Auth(AuthError::Timeout {
1486                stage: AuthStage::ClientHello,
1487                ..
1488            })
1489        ));
1490        let captured = crate::router::test_log::captured_logs(&logs);
1491        assert!(
1492            captured.contains("reason=\"handshake_deadline_expired\""),
1493            "{captured}"
1494        );
1495        assert_pre_auth_log(&logs, "handshake_deadline", port);
1496    }
1497
1498    #[tokio::test]
1499    async fn logs_invalid_client_proof_pre_auth_rejection() {
1500        let (logs, _guard) = crate::router::test_log::log_capture(tracing::Level::INFO);
1501        let (auth, conn) = test_auth_with_deadline_and_limit(Duration::from_millis(200), 4);
1502        let (mut client, server_stream) = duplex(512);
1503        let port = 41002;
1504        let server = handle_accepted_connection(
1505            server_stream,
1506            echo_router(),
1507            auth,
1508            SocketAddr::from(([127, 0, 0, 1], port)),
1509            Instant::now(),
1510        );
1511
1512        let (error, ()) = tokio::join!(server, send_wrong_key_client_auth(&mut client, &conn));
1513        assert!(matches!(
1514            error,
1515            Err(ConnectionError::Auth(AuthError::InvalidClientAuth))
1516        ));
1517        let captured = crate::router::test_log::captured_logs(&logs);
1518        assert!(
1519            captured.contains("reason=\"client_proof_mismatch\""),
1520            "{captured}"
1521        );
1522        assert_pre_auth_log(&logs, "client_authentication", port);
1523    }
1524
1525    #[tokio::test]
1526    async fn logs_malformed_pre_auth_message() {
1527        let (logs, _guard) = crate::router::test_log::log_capture(tracing::Level::INFO);
1528        let (auth, _) = test_auth_with_deadline_and_limit(Duration::from_millis(200), 4);
1529        let (mut client, server_stream) = duplex(512);
1530        let port = 41003;
1531        let server = handle_accepted_connection(
1532            server_stream,
1533            echo_router(),
1534            auth,
1535            SocketAddr::from(([127, 0, 0, 1], port)),
1536            Instant::now(),
1537        );
1538        let send_malformed_message = async {
1539            let body = b"{";
1540            client
1541                .write_all(&(body.len() as u32).to_le_bytes())
1542                .await
1543                .expect("message length writes");
1544            client.write_all(body).await.expect("message body writes");
1545        };
1546
1547        let (error, ()) = tokio::join!(server, send_malformed_message);
1548        assert!(matches!(
1549            error,
1550            Err(ConnectionError::Auth(AuthError::JsonDecode {
1551                stage: AuthStage::ClientHello,
1552                ..
1553            }))
1554        ));
1555        let captured = crate::router::test_log::captured_logs(&logs);
1556        assert!(
1557            captured.contains("reason=\"malformed_or_oversized_message\""),
1558            "{captured}"
1559        );
1560        assert_pre_auth_log(&logs, "malformed_message", port);
1561    }
1562
1563    #[tokio::test]
1564    async fn logs_peer_close_during_handshake() {
1565        let (logs, _guard) = crate::router::test_log::log_capture(tracing::Level::INFO);
1566        let (auth, _) = test_auth_with_deadline_and_limit(Duration::from_millis(200), 4);
1567        let (client, server_stream) = duplex(512);
1568        let port = 41004;
1569        drop(client);
1570
1571        let error = handle_accepted_connection(
1572            server_stream,
1573            echo_router(),
1574            auth,
1575            SocketAddr::from(([127, 0, 0, 1], port)),
1576            Instant::now(),
1577        )
1578        .await
1579        .expect_err("closed peer must be rejected");
1580        assert!(matches!(
1581            error,
1582            ConnectionError::Auth(AuthError::UnexpectedEof {
1583                stage: AuthStage::ClientHello,
1584                actual: 0,
1585                ..
1586            })
1587        ));
1588        let captured = crate::router::test_log::captured_logs(&logs);
1589        assert!(
1590            captured.contains("reason=\"peer_closed_before_authentication_completed\""),
1591            "{captured}"
1592        );
1593        assert_pre_auth_log(&logs, "peer_closed", port);
1594    }
1595
1596    // Rejection-log rate limits belong to one listener. A process-wide limiter
1597    // let one listener's rejects silence another's for a second, which hid the
1598    // second listener's first rejection entirely.
1599    #[tokio::test]
1600    async fn each_listener_rate_limits_its_own_rejection_logs() {
1601        let (logs, _guard) = crate::router::test_log::log_capture(tracing::Level::INFO);
1602        for port in [41011, 41012] {
1603            let (auth, _) = test_auth_with_deadline_and_limit(Duration::from_millis(200), 4);
1604            let (client, server_stream) = duplex(512);
1605            drop(client);
1606            handle_accepted_connection(
1607                server_stream,
1608                echo_router(),
1609                auth,
1610                SocketAddr::from(([127, 0, 0, 1], port)),
1611                Instant::now(),
1612            )
1613            .await
1614            .expect_err("closed peer must be rejected");
1615        }
1616        let captured = crate::router::test_log::captured_logs(&logs);
1617        for port in [41011, 41012] {
1618            assert!(
1619                captured.contains(&format!("127.0.0.1:{port}")),
1620                "listener on port {port} logged nothing: {captured}"
1621            );
1622        }
1623    }
1624
1625    #[tokio::test]
1626    async fn logs_unauthenticated_capacity_rejection() {
1627        let (logs, _guard) = crate::router::test_log::log_capture(tracing::Level::INFO);
1628        let deadline = Duration::from_millis(60);
1629        let (auth, _) = test_auth_with_deadline_and_limit(deadline, 1);
1630        let held_slot = auth
1631            .unauthenticated
1632            .clone()
1633            .try_acquire_owned()
1634            .expect("the only pre-auth slot is available");
1635        let (client, server_stream) = duplex(512);
1636        let port = 41005;
1637        let server = handle_accepted_connection(
1638            server_stream,
1639            echo_router(),
1640            auth,
1641            SocketAddr::from(([127, 0, 0, 1], port)),
1642            Instant::now(),
1643        );
1644
1645        let error = tokio::time::timeout(deadline * 3, server)
1646            .await
1647            .expect("queued connection should end at the capacity deadline")
1648            .expect_err("connection without a free slot must be rejected");
1649        drop(held_slot);
1650        drop(client);
1651        assert!(matches!(error, ConnectionError::UnauthenticatedCapacity));
1652        let captured = crate::router::test_log::captured_logs(&logs);
1653        assert!(
1654            captured.contains("reason=\"capacity_wait_timed_out\""),
1655            "{captured}"
1656        );
1657        assert_pre_auth_log(&logs, "waiting_for_handshake_slot", port);
1658    }
1659
1660    #[test]
1661    fn pre_auth_rejection_limiter_suppresses_bursts_and_reports_count() {
1662        let (logs, _guard) = crate::router::test_log::log_capture(tracing::Level::INFO);
1663        let limiter = PreAuthRejectLimiter::new();
1664        let capacity = ConnectionError::UnauthenticatedCapacity;
1665        let handshake_timeout = ConnectionError::Auth(AuthError::Timeout {
1666            stage: AuthStage::ClientHello,
1667            deadline: Duration::from_millis(200),
1668        });
1669        let peer_addr = SocketAddr::from(([127, 0, 0, 1], 41006));
1670
1671        log_pre_auth_rejection(
1672            &capacity,
1673            peer_addr,
1674            Duration::from_millis(1),
1675            Duration::ZERO,
1676            &limiter,
1677        );
1678        for _ in 0..4 {
1679            log_pre_auth_rejection(
1680                &capacity,
1681                peer_addr,
1682                Duration::from_millis(2),
1683                Duration::from_millis(100),
1684                &limiter,
1685            );
1686        }
1687        log_pre_auth_rejection(
1688            &handshake_timeout,
1689            peer_addr,
1690            Duration::from_millis(3),
1691            Duration::from_millis(100),
1692            &limiter,
1693        );
1694        log_pre_auth_rejection(
1695            &capacity,
1696            peer_addr,
1697            Duration::from_millis(4),
1698            Duration::from_secs(1),
1699            &limiter,
1700        );
1701
1702        let captured = crate::router::test_log::captured_logs(&logs);
1703        assert_eq!(
1704            captured
1705                .matches("subc TCP connection rejected before routing")
1706                .count(),
1707            3,
1708            "one line per stage in a burst, then a later capacity line: {captured}"
1709        );
1710        assert!(
1711            captured.contains("suppressed_since_last=4"),
1712            "later capacity line should report the four hidden rejects: {captured}"
1713        );
1714        assert!(
1715            captured.contains("stage=\"handshake_deadline\""),
1716            "{captured}"
1717        );
1718    }
1719
1720    #[tokio::test]
1721    async fn buffered_frame_reader_coalesces_reads_and_preserves_short_reads() {
1722        let frames = vec![
1723            request(7, 1, b"first"),
1724            request(9, 2, b"second"),
1725            request(7, 3, b"third"),
1726            request(9, 4, b"fourth"),
1727        ];
1728        let bytes = encode_frames(&frames);
1729
1730        let (mut direct, direct_reads) = CountingReader::new(bytes.clone(), None);
1731        assert_eq!(read_frames(&mut direct, frames.len()).await, frames);
1732        assert_eq!(direct_reads.load(Ordering::Relaxed), frames.len() * 3);
1733
1734        let (buffered_source, buffered_reads) = CountingReader::new(bytes, None);
1735        let mut buffered = BufReader::new(buffered_source);
1736        assert_eq!(read_frames(&mut buffered, frames.len()).await, frames);
1737        assert_eq!(buffered_reads.load(Ordering::Relaxed), 1);
1738
1739        let split_frame = request(7, 5, b"split-body");
1740        let split_bytes = encode_frames(std::slice::from_ref(&split_frame));
1741        let (split_source, split_reads) = CountingReader::new(split_bytes, Some(10));
1742        let mut split_reader = BufReader::new(split_source);
1743        assert_eq!(
1744            read_frame(&mut split_reader).await.unwrap(),
1745            Some(split_frame)
1746        );
1747        assert_eq!(split_reads.load(Ordering::Relaxed), 2);
1748    }
1749
1750    #[tokio::test]
1751    async fn interleaved_channels_on_one_stream_demux_byte_identically_after_auth() {
1752        let (mut client, server_stream) = duplex(4096);
1753        let (auth, conn) = test_auth();
1754        let server = tokio::spawn(handle_connection(server_stream, echo_router(), auth));
1755        authenticate(&mut client, &conn).await;
1756        let frames = [
1757            request(7, 1, b"chan7-first\0opaque"),
1758            request(9, 2, b"chan9-middle-{json?}"),
1759            request(7, 3, b"chan7-second\xffbytes"),
1760        ];
1761
1762        for frame in &frames {
1763            crate::write_frame(&mut client, frame).await.unwrap();
1764        }
1765
1766        for expected in &frames {
1767            let response = crate::read_frame(&mut client).await.unwrap().unwrap();
1768            assert_eq!(response.header.ty, FrameType::Response);
1769            assert_eq!(response.header.channel, expected.header.channel);
1770            assert_eq!(response.header.corr, expected.header.corr);
1771            assert_eq!(response.body, expected.body);
1772        }
1773
1774        drop(client);
1775        server.await.unwrap().unwrap();
1776    }
1777
1778    #[tokio::test]
1779    async fn channel_zero_goes_to_subc_self_handler_after_auth() {
1780        let (mut client, server_stream) = duplex(512);
1781        let (auth, conn) = test_auth();
1782        let server = tokio::spawn(handle_connection(
1783            server_stream,
1784            Arc::new(Router::with_default_self_handler()),
1785            auth,
1786        ));
1787        authenticate(&mut client, &conn).await;
1788        let ping = Frame::build(
1789            FrameType::Ping,
1790            Flags::new(false, Priority::Passive, false),
1791            0,
1792            0,
1793            55,
1794            Vec::new(),
1795        )
1796        .unwrap();
1797
1798        crate::write_frame(&mut client, &ping).await.unwrap();
1799        let response = crate::read_frame(&mut client).await.unwrap().unwrap();
1800
1801        assert_eq!(response.header.ty, FrameType::Pong);
1802        assert_eq!(response.header.channel, 0);
1803        assert_eq!(response.header.corr, 55);
1804        assert!(response.body.is_empty());
1805
1806        drop(client);
1807        server.await.unwrap().unwrap();
1808    }
1809
1810    #[tokio::test]
1811    async fn unauthenticated_connection_is_rejected_before_routing() {
1812        let (mut client, server_stream) = duplex(512);
1813        let (auth, _conn) = test_auth();
1814        let registry = Arc::new(Registry::default());
1815        let router = Arc::new(Router::with_control_handler(Arc::new(ControlHandler::new(
1816            Arc::clone(&registry),
1817        ))));
1818        let server = tokio::spawn(handle_connection(server_stream, router, auth));
1819        let ping = Frame::build(
1820            FrameType::Ping,
1821            Flags::new(false, Priority::Passive, false),
1822            0,
1823            0,
1824            66,
1825            Vec::new(),
1826        )
1827        .unwrap();
1828
1829        crate::write_frame(&mut client, &ping).await.unwrap();
1830        if let Ok(Ok(Some(frame))) =
1831            tokio::time::timeout(Duration::from_millis(200), crate::read_frame(&mut client)).await
1832        {
1833            panic!("unauthenticated frame reached router: {frame:?}");
1834        }
1835
1836        let err = server.await.unwrap().unwrap_err();
1837        assert!(matches!(err, ConnectionError::Auth(_)));
1838        assert_eq!(registry.active_registration_count().unwrap(), 0);
1839    }
1840
1841    #[tokio::test]
1842    async fn over_cap_peer_queues_for_a_slot_and_authenticates_when_one_frees() {
1843        // Restart-herd contract: an over-cap pre-auth connection WAITS (bounded by
1844        // the auth deadline) instead of being reset. When the slot-holder finishes,
1845        // the queued peer must complete a normal handshake — the 2026-07-14 aft
1846        // outage was exactly a supervised child being reset out of this lottery.
1847        let (mut first_client, first_server_stream) = duplex(2048);
1848        let (mut second_client, second_server_stream) = duplex(2048);
1849        let (auth, conn) = test_auth_with_limit(1);
1850        let registry = Arc::new(Registry::default());
1851        let router = Arc::new(Router::with_control_handler(Arc::new(ControlHandler::new(
1852            Arc::clone(&registry),
1853        ))));
1854
1855        let first_server = tokio::spawn(handle_connection(
1856            first_server_stream,
1857            Arc::clone(&router),
1858            auth.clone(),
1859        ));
1860
1861        let second_server = tokio::spawn(handle_connection(
1862            second_server_stream,
1863            Arc::clone(&router),
1864            auth.clone(),
1865        ));
1866
1867        // The second peer must still be pending (not reset) while the slot is held.
1868        tokio::time::sleep(Duration::from_millis(50)).await;
1869        assert!(
1870            !second_server.is_finished(),
1871            "queued peer must not be reset"
1872        );
1873
1874        // First peer completes its handshake, freeing the slot; the queued second
1875        // peer then authenticates normally.
1876        authenticate(&mut first_client, &conn).await;
1877        authenticate(&mut second_client, &conn).await;
1878
1879        drop(first_client);
1880        drop(second_client);
1881        let _ = first_server.await;
1882        let _ = second_server.await;
1883        assert_eq!(registry.active_registration_count().unwrap(), 0);
1884    }
1885
1886    #[tokio::test]
1887    async fn over_cap_peer_is_rejected_when_no_slot_frees_within_deadline() {
1888        // The deadline stays the DoS bound: if no slot frees, the queued peer is
1889        // rejected at the deadline with a closed stream. The slot is held DIRECTLY
1890        // (not by another connection) so nothing can free it mid-test — a squatting
1891        // connection's own auth deadline would release the slot at exactly the
1892        // waiter's timeout, making the outcome racy.
1893        let (mut second_client, second_server_stream) = duplex(512);
1894        let (auth, conn) = test_auth_with_limit(1);
1895        let registry = Arc::new(Registry::default());
1896        let router = Arc::new(Router::with_control_handler(Arc::new(ControlHandler::new(
1897            Arc::clone(&registry),
1898        ))));
1899
1900        let held_slot = auth
1901            .unauthenticated
1902            .clone()
1903            .try_acquire_owned()
1904            .expect("sole pre-auth slot");
1905
1906        let second_server = tokio::spawn(handle_connection(
1907            second_server_stream,
1908            Arc::clone(&router),
1909            auth.clone(),
1910        ));
1911        let second_err = tokio::time::timeout(TEST_DEADLINE * 2, second_server)
1912            .await
1913            .expect("capacity reject should settle at the deadline")
1914            .expect("second connection task should not panic")
1915            .expect_err("queued peer must be rejected when no slot frees");
1916        drop(held_slot);
1917        assert!(matches!(
1918            second_err,
1919            ConnectionError::UnauthenticatedCapacity
1920        ));
1921        let mut closed = [0u8; 1];
1922        assert_eq!(
1923            second_client.read(&mut closed).await.unwrap(),
1924            0,
1925            "capacity-rejected peer should observe a closed stream"
1926        );
1927        assert_eq!(registry.active_registration_count().unwrap(), 0);
1928
1929        let (mut authed_client, authed_server_stream) = duplex(2048);
1930        let authed_server = tokio::spawn(handle_connection(
1931            authed_server_stream,
1932            Arc::clone(&router),
1933            auth,
1934        ));
1935        authenticate(&mut authed_client, &conn).await;
1936        let ping = Frame::build(
1937            FrameType::Ping,
1938            Flags::new(false, Priority::Passive, false),
1939            0,
1940            0,
1941            77,
1942            Vec::new(),
1943        )
1944        .unwrap();
1945        crate::write_frame(&mut authed_client, &ping).await.unwrap();
1946        let pong = crate::read_frame(&mut authed_client)
1947            .await
1948            .unwrap()
1949            .unwrap();
1950        assert_eq!(pong.header.ty, FrameType::Pong);
1951        assert_eq!(pong.header.channel, 0);
1952        assert_eq!(pong.header.corr, 77);
1953        assert_eq!(registry.active_registration_count().unwrap(), 0);
1954
1955        drop(authed_client);
1956        authed_server.await.unwrap().unwrap();
1957    }
1958
1959    #[tokio::test]
1960    async fn bind_ack_during_partial_frame_preserves_next_request() {
1961        use subc_control::ClientControlRequest;
1962        use subc_protocol::{
1963            manifest::{
1964                Concurrency, ExecutionMode, IdentityScope, ModuleManifest, ProviderRole, Tool,
1965            },
1966            session::{ModuleControlRequest, ModuleControlResponse},
1967            BindIdentity, ModuleHelloBody, RouteTarget,
1968        };
1969
1970        let mut configured_router = Router::with_default_self_handler();
1971        configured_router.register_backend(7, EchoBackend).unwrap();
1972        let router = Arc::new(configured_router);
1973        let (auth, conn) = test_auth();
1974        let (mut module, module_stream) = duplex(4096);
1975        let module_server = tokio::spawn(handle_connection(
1976            module_stream,
1977            Arc::clone(&router),
1978            auth.clone(),
1979        ));
1980        authenticate(&mut module, &conn).await;
1981        let manifest = ModuleManifest::builder("frame-test", "0.1.0")
1982            .protocol_ver(PROTOCOL_VERSION)
1983            .provides(vec![ProviderRole::ToolProvider {
1984                tools: vec![Tool {
1985                    name: "read".into(),
1986                    description: None,
1987                    execution_mode: ExecutionMode::Pure,
1988                    schema: serde_json::json!({"type": "object"}),
1989                }],
1990                identity_scope: vec![IdentityScope::Project, IdentityScope::Session],
1991                concurrency: Concurrency::ModuleManaged,
1992                emits_push: true,
1993                sub_supervises: true,
1994            }])
1995            .build();
1996        let hello = Frame::build(
1997            FrameType::Hello,
1998            Flags::new(false, Priority::Passive, false),
1999            0,
2000            0,
2001            1,
2002            serde_json::to_vec(&ModuleHelloBody {
2003                manifest,
2004                protocol_ver: PROTOCOL_VERSION,
2005                control_ops: None,
2006                launch_nonce: None,
2007            })
2008            .unwrap(),
2009        )
2010        .unwrap();
2011        crate::write_frame(&mut module, &hello).await.unwrap();
2012        assert_eq!(
2013            read_frame(&mut module).await.unwrap().unwrap().header.ty,
2014            FrameType::HelloAck
2015        );
2016
2017        let (mut client, client_stream) = duplex(4096);
2018        let client_server = tokio::spawn(handle_connection(client_stream, router, auth));
2019        authenticate(&mut client, &conn).await;
2020        let open = Frame::build(
2021            FrameType::Request,
2022            Flags::new(false, Priority::Passive, false),
2023            0,
2024            0,
2025            2,
2026            serde_json::to_vec(&ClientControlRequest::RouteOpen {
2027                target: RouteTarget::ToolProvider {
2028                    module_id: "frame-test".into(),
2029                },
2030                identity: BindIdentity::new(std::env::current_dir().unwrap(), "unit", "session"),
2031                consumer_identity: None,
2032                consumer_capabilities: None,
2033                role_versions: None,
2034                admission_facts: None,
2035                scope: None,
2036            })
2037            .unwrap(),
2038        )
2039        .unwrap();
2040        crate::write_frame(&mut client, &open).await.unwrap();
2041        let bind = timeout(TEST_DEADLINE, read_frame(&mut module))
2042            .await
2043            .unwrap()
2044            .unwrap()
2045            .unwrap();
2046        assert!(matches!(
2047            serde_json::from_slice::<ModuleControlRequest>(&bind.body).unwrap(),
2048            ModuleControlRequest::RouteBind { .. }
2049        ));
2050
2051        let ping = request(7, 3, b"partial-frame-body");
2052        client.write_all(&ping.header.encode()).await.unwrap();
2053        // Give the connection reader time to consume the header while the bind is pending.
2054        tokio::time::sleep(Duration::from_millis(30)).await;
2055        let ack = Frame::build(
2056            FrameType::Response,
2057            Flags::new(false, Priority::Passive, false),
2058            0,
2059            0,
2060            bind.header.corr,
2061            serde_json::to_vec(&ModuleControlResponse::RouteBindAck {}).unwrap(),
2062        )
2063        .unwrap();
2064        crate::write_frame(&mut module, &ack).await.unwrap();
2065        let opened = timeout(TEST_DEADLINE, read_frame(&mut client))
2066            .await
2067            .unwrap()
2068            .unwrap();
2069        if opened.is_none() {
2070            panic!("client closed: {:?}", client_server.await);
2071        }
2072        let opened = opened.unwrap();
2073        assert_eq!(opened.header.corr, 2);
2074        client.write_all(&ping.body).await.unwrap();
2075        let pong = timeout(TEST_DEADLINE, read_frame(&mut client))
2076            .await
2077            .expect("partial frame must reach the router after the bind ack")
2078            .expect("frame must decode")
2079            .expect("connection must remain open");
2080        assert_eq!(pong.header.ty, FrameType::Response);
2081        assert_eq!(pong.header.corr, 3);
2082        assert_eq!(pong.body, ping.body);
2083        drop(client);
2084        drop(module);
2085        client_server.await.unwrap().unwrap();
2086        let _ = module_server.await.unwrap();
2087    }
2088
2089    #[tokio::test]
2090    async fn aborted_accept_does_not_end_listener() {
2091        let listener = Arc::new(TcpListener::bind("127.0.0.1:0").await.unwrap());
2092        let addr = listener.local_addr().unwrap();
2093        let (auth, conn) = test_auth();
2094        let mut attempts = 0;
2095        let server = tokio::spawn(serve_listener_with_accept(
2096            Some(addr),
2097            echo_router(),
2098            auth,
2099            move || {
2100                attempts += 1;
2101                let result = if attempts == 1 {
2102                    Some(io::Error::from(io::ErrorKind::ConnectionAborted))
2103                } else {
2104                    None
2105                };
2106                let listener = Arc::clone(&listener);
2107                async move {
2108                    match result {
2109                        Some(err) => Err(err),
2110                        None => listener.accept().await,
2111                    }
2112                }
2113            },
2114        ));
2115        let mut client = tokio::net::TcpStream::connect(addr).await.unwrap();
2116        authenticate(&mut client, &conn).await;
2117        let ping = Frame::build(
2118            FrameType::Ping,
2119            Flags::new(false, Priority::Passive, false),
2120            0,
2121            0,
2122            77,
2123            Vec::new(),
2124        )
2125        .unwrap();
2126        crate::write_frame(&mut client, &ping).await.unwrap();
2127        let pong = timeout(TEST_DEADLINE, read_frame(&mut client))
2128            .await
2129            .unwrap()
2130            .unwrap()
2131            .unwrap();
2132        assert_eq!(pong.header.ty, FrameType::Pong);
2133        assert!(
2134            !server.is_finished(),
2135            "a temporary accept failure must not stop the listener"
2136        );
2137        server.abort();
2138    }
2139
2140    #[tokio::test]
2141    async fn exhausted_accept_backs_off_then_serves_connection() {
2142        let listener = Arc::new(TcpListener::bind("127.0.0.1:0").await.unwrap());
2143        let addr = listener.local_addr().unwrap();
2144        let (auth, conn) = test_auth();
2145        let mut attempts = 0;
2146        let server = tokio::spawn(serve_listener_with_accept(
2147            Some(addr),
2148            echo_router(),
2149            auth,
2150            move || {
2151                attempts += 1;
2152                // The platform's "too many open files" code: EMFILE on Unix,
2153                // WSAEMFILE on Windows. A bare 24 is ERROR_BAD_LENGTH on Windows.
2154                #[cfg(unix)]
2155                let emfile = rustix::io::Errno::MFILE.raw_os_error();
2156                #[cfg(windows)]
2157                let emfile = 10024;
2158                let error = (attempts == 1).then(|| io::Error::from_raw_os_error(emfile));
2159                let listener = Arc::clone(&listener);
2160                async move {
2161                    match error {
2162                        Some(err) => Err(err),
2163                        None => listener.accept().await,
2164                    }
2165                }
2166            },
2167        ));
2168        let started = Instant::now();
2169        let mut client = tokio::net::TcpStream::connect(addr).await.unwrap();
2170        authenticate(&mut client, &conn).await;
2171        assert!(
2172            started.elapsed() >= Duration::from_millis(50),
2173            "fd exhaustion must back off before retrying"
2174        );
2175        assert!(!server.is_finished());
2176        server.abort();
2177    }
2178
2179    #[tokio::test]
2180    async fn fatal_accept_error_still_stops_listener() {
2181        let (auth, _) = test_auth();
2182        let err = serve_listener_with_accept(None, echo_router(), auth, || async {
2183            Err(io::Error::from(io::ErrorKind::PermissionDenied))
2184        })
2185        .await
2186        .unwrap_err();
2187        assert!(
2188            matches!(err, ServerError::Accept { source, .. } if source.kind() == io::ErrorKind::PermissionDenied)
2189        );
2190    }
2191
2192    #[tokio::test]
2193    async fn serve_listeners_with_no_listeners_returns_typed_error() {
2194        let (auth, _conn) = test_auth();
2195        let err = serve_listeners(Vec::new(), echo_router(), auth)
2196            .await
2197            .expect_err("empty listener set must fail loudly");
2198        assert!(matches!(err, ServerError::NoListeners));
2199    }
2200
2201    #[tokio::test]
2202    async fn malformed_header_returns_typed_error_no_panic() {
2203        let (mut client, server_stream) = duplex(128);
2204        let (auth, conn) = test_auth();
2205        let server = tokio::spawn(handle_connection(server_stream, echo_router(), auth));
2206        authenticate(&mut client, &conn).await;
2207        let mut header = [0u8; HEADER_LEN];
2208        header[4] = PROTOCOL_VERSION;
2209        header[5] = 250;
2210
2211        client.write_all(&header).await.unwrap();
2212        drop(client);
2213
2214        let err = server.await.unwrap().unwrap_err();
2215        assert!(matches!(
2216            err,
2217            ConnectionError::FrameIo(FrameIoError::DecodeHeader(DecodeError::UnknownFrameType {
2218                byte: 250
2219            }))
2220        ));
2221    }
2222
2223    #[tokio::test]
2224    async fn truncated_body_returns_typed_error_no_panic() {
2225        let (mut client, server_stream) = duplex(128);
2226        let (auth, conn) = test_auth();
2227        let server = tokio::spawn(handle_connection(server_stream, echo_router(), auth));
2228        authenticate(&mut client, &conn).await;
2229        let frame = request(7, 8, b"abcd");
2230
2231        client.write_all(&frame.header.encode()).await.unwrap();
2232        client.write_all(b"ab").await.unwrap();
2233        drop(client);
2234
2235        let err = server.await.unwrap().unwrap_err();
2236        assert!(matches!(
2237            err,
2238            ConnectionError::FrameIo(FrameIoError::UnexpectedEof {
2239                stage: ReadStage::Body,
2240                expected: 4,
2241                actual: 2
2242            })
2243        ));
2244    }
2245
2246    #[tokio::test]
2247    async fn unknown_channel_is_returned_as_error_frame_and_connection_continues() {
2248        let (mut client, server_stream) = duplex(1024);
2249        let (auth, conn) = test_auth();
2250        let server = tokio::spawn(handle_connection(server_stream, echo_router(), auth));
2251        authenticate(&mut client, &conn).await;
2252        let unknown = request(42, 10, b"lost");
2253        let known = request(7, 11, b"still-routes");
2254
2255        crate::write_frame(&mut client, &unknown).await.unwrap();
2256        crate::write_frame(&mut client, &known).await.unwrap();
2257
2258        let error = crate::read_frame(&mut client).await.unwrap().unwrap();
2259        assert_eq!(error.header.ty, FrameType::Error);
2260        assert_eq!(error.header.channel, 42);
2261        assert_eq!(error.header.corr, 10);
2262        let error_body: ErrorBody = serde_json::from_slice(&error.body).unwrap();
2263        assert_eq!(error_body.code, "unknown_channel");
2264
2265        let response = crate::read_frame(&mut client).await.unwrap().unwrap();
2266        assert_eq!(response.header.ty, FrameType::Response);
2267        assert_eq!(response.header.channel, 7);
2268        assert_eq!(response.header.corr, 11);
2269        assert_eq!(response.body, b"still-routes");
2270
2271        drop(client);
2272        server.await.unwrap().unwrap();
2273    }
2274}