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
32pub const CONNECTION_EGRESS_BYTE_BUDGET: usize = 4 * 1024 * 1024;
44pub const CONNECTION_EGRESS_FRAME_CAP: usize = 32 * 1024;
53pub const MAX_PENDING_ROUTE_OPENS_PER_CONNECTION: usize = 8;
59pub(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);
65pub 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#[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#[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#[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 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
307pub 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 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 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 #[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
407pub 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
460pub 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 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 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 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 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 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 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 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 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 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 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
886pub(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
916async 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 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 #[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 #[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 #[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(®istry),
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 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(®istry),
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 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 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 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(®istry),
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 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 #[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}