1use std::{
2 error::Error,
3 fmt, io,
4 net::SocketAddr,
5 sync::Arc,
6 time::{Duration, Instant},
7};
8
9use subc_transport::{authenticate_server, AuthError, DAEMON_ID_LEN, WATCHDOG_CLIENT_ROLE};
10use tokio::{
11 io::{AsyncRead, AsyncWrite, AsyncWriteExt, BufReader, BufWriter},
12 net::TcpListener,
13 sync::{mpsc, Semaphore},
14 task::{JoinHandle, JoinSet},
15 time::timeout,
16};
17use tracing::{debug, warn};
18
19use crate::{
20 forwarding::{CloseReason, ConnectionCloseReceiver},
21 observability::ConnectedClients,
22 read_frame,
23 router::{FrameSink, RouteCtx, Router},
24 write_frame, FrameIoError, RouterError,
25};
26
27pub const CONNECTION_EGRESS_BUFFER: usize = 64;
28pub(crate) const MAX_PENDING_ROUTE_OPENS_PER_CONNECTION: usize = CONNECTION_EGRESS_BUFFER / 8;
33pub(crate) const MAX_PENDING_ROUTE_BINDS_PER_TARGET: usize =
37 MAX_PENDING_ROUTE_OPENS_PER_CONNECTION * 2;
38pub const DEFAULT_AUTH_DEADLINE: Duration = Duration::from_secs(2);
39pub const DEFAULT_MAX_UNAUTHENTICATED_CONNECTIONS: usize = 256;
44const CLOSE_DRAIN_GRACE: Duration = Duration::from_secs(2);
45
46#[derive(Clone)]
49pub struct ServerAuth {
50 key: Arc<[u8]>,
51 daemon_id: [u8; DAEMON_ID_LEN],
52 daemon_ver: Arc<str>,
53 deadline: Duration,
54 unauthenticated: Arc<Semaphore>,
55 connected_clients: ConnectedClients,
56}
57
58impl ServerAuth {
59 pub fn new(
60 key: Vec<u8>,
61 daemon_id: [u8; DAEMON_ID_LEN],
62 daemon_ver: impl Into<String>,
63 ) -> Self {
64 Self::with_limits(
65 key,
66 daemon_id,
67 daemon_ver,
68 DEFAULT_AUTH_DEADLINE,
69 DEFAULT_MAX_UNAUTHENTICATED_CONNECTIONS,
70 )
71 }
72
73 pub fn with_limits(
75 key: Vec<u8>,
76 daemon_id: [u8; DAEMON_ID_LEN],
77 daemon_ver: impl Into<String>,
78 deadline: Duration,
79 max_unauthenticated: usize,
80 ) -> Self {
81 Self {
82 key: Arc::from(key),
83 daemon_id,
84 daemon_ver: Arc::from(daemon_ver.into()),
85 deadline,
86 unauthenticated: Arc::new(Semaphore::new(max_unauthenticated.max(1))),
87 connected_clients: ConnectedClients::new(),
88 }
89 }
90
91 pub fn with_connected_clients(mut self, connected_clients: ConnectedClients) -> Self {
92 self.connected_clients = connected_clients;
93 self
94 }
95}
96
97impl fmt::Debug for ServerAuth {
98 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
99 f.debug_struct("ServerAuth")
100 .field("key", &"<redacted>")
101 .field("daemon_id", &self.daemon_id)
102 .field("daemon_ver", &self.daemon_ver)
103 .field("deadline", &self.deadline)
104 .finish_non_exhaustive()
105 }
106}
107
108pub async fn serve_listener(
111 listener: TcpListener,
112 router: Arc<Router>,
113 auth: ServerAuth,
114) -> Result<(), ServerError> {
115 serve_listener_with_accept(listener.local_addr().ok(), router, auth, || {
116 listener.accept()
117 })
118 .await
119}
120
121async fn serve_listener_with_accept<A, F>(
122 local_addr: Option<SocketAddr>,
123 router: Arc<Router>,
124 auth: ServerAuth,
125 mut accept: A,
126) -> Result<(), ServerError>
127where
128 A: FnMut() -> F,
129 F: std::future::Future<Output = io::Result<(tokio::net::TcpStream, SocketAddr)>>,
130{
131 loop {
132 let (stream, peer_addr) = match accept().await {
133 Ok(accepted) => accepted,
134 Err(source) => {
135 let kind = source.kind();
136 let exhausted = accept_resource_exhausted(&source);
140 if matches!(
141 kind,
142 io::ErrorKind::ConnectionAborted
143 | io::ErrorKind::ConnectionReset
144 | io::ErrorKind::Interrupted
145 ) || exhausted
146 {
147 warn!(?local_addr, error = %source, "temporary TCP accept failure");
148 if exhausted {
149 tokio::time::sleep(Duration::from_millis(75)).await;
150 }
151 continue;
152 }
153 return Err(ServerError::Accept { local_addr, source });
154 }
155 };
156 if let Err(source) = stream.set_nodelay(true) {
172 warn!(?peer_addr, error = %source, "could not disable Nagle on accepted connection");
173 }
174 debug!(?peer_addr, ?local_addr, "accepted subc TCP connection");
175 let router = Arc::clone(&router);
176 let auth = auth.clone();
177 tokio::spawn(async move {
178 if let Err(err) = handle_connection(stream, router, auth).await {
179 if err.is_quiet_reject() {
180 debug!(?peer_addr, error = %err, "subc TCP connection rejected before routing");
181 } else {
182 warn!(?peer_addr, error = %err, "subc connection ended with error");
183 }
184 }
185 });
186 }
187}
188
189fn accept_resource_exhausted(error: &io::Error) -> bool {
190 #[cfg(unix)]
195 {
196 use rustix::io::Errno;
197 let exhausted = [Errno::NFILE, Errno::MFILE, Errno::NOBUFS];
198 error
199 .raw_os_error()
200 .is_some_and(|code| exhausted.iter().any(|e| e.raw_os_error() == code))
201 }
202 #[cfg(windows)]
203 {
204 matches!(error.raw_os_error(), Some(10024 | 10055))
205 }
206 #[cfg(not(any(unix, windows)))]
207 {
208 let _ = error;
209 false
210 }
211}
212
213pub async fn serve_listeners(
215 listeners: Vec<TcpListener>,
216 router: Arc<Router>,
217 auth: ServerAuth,
218) -> Result<(), ServerError> {
219 if listeners.is_empty() {
220 return Err(ServerError::NoListeners);
221 }
222
223 let (tx, mut rx) = mpsc::channel(listeners.len());
224 let mut accept_tasks = AbortTasksOnDrop::default();
225 for listener in listeners {
226 let router = Arc::clone(&router);
227 let auth = auth.clone();
228 let tx = tx.clone();
229 accept_tasks.push(tokio::spawn(async move {
230 let result = serve_listener(listener, router, auth).await;
231 let _ = tx.send(result).await;
232 }));
233 }
234 drop(tx);
235
236 rx.recv().await.unwrap_or(Ok(()))
237}
238
239#[derive(Default)]
240struct AbortTasksOnDrop {
241 handles: Vec<JoinHandle<()>>,
242}
243
244impl AbortTasksOnDrop {
245 fn push(&mut self, handle: JoinHandle<()>) {
246 self.handles.push(handle);
247 }
248}
249
250impl Drop for AbortTasksOnDrop {
251 fn drop(&mut self) {
252 for handle in &self.handles {
253 if !handle.is_finished() {
254 handle.abort();
255 }
256 }
257 }
258}
259
260#[derive(Debug)]
261enum ConnectionLoopExit {
262 PeerClosed,
263 CloseRequested(CloseReason),
264}
265
266pub async fn handle_connection<S>(
274 mut stream: S,
275 router: Arc<Router>,
276 auth: ServerAuth,
277) -> Result<(), ConnectionError>
278where
279 S: AsyncRead + AsyncWrite + Unpin + Send + 'static,
280{
281 let permit =
301 match tokio::time::timeout(auth.deadline, auth.unauthenticated.clone().acquire_owned())
302 .await
303 {
304 Ok(Ok(permit)) => permit,
305 Ok(Err(_)) | Err(_) => {
306 let _ = stream.shutdown().await;
307 return Err(ConnectionError::UnauthenticatedCapacity);
308 }
309 };
310
311 let authenticated = authenticate_server(
312 &mut stream,
313 auth.key.as_ref(),
314 &auth.daemon_id,
315 auth.daemon_ver.as_ref(),
316 auth.deadline,
317 )
318 .await
319 .map_err(ConnectionError::Auth)?;
320 drop(permit);
321
322 let mut connection = router.begin_connection();
323 let connection_id = connection.id();
324 let _connected_client = (authenticated.role != WATCHDOG_CLIENT_ROLE)
339 .then(|| auth.connected_clients.open(connection_id));
340 let close_receiver = connection.take_close_receiver();
341 debug!(
342 connection_id = connection_id.get(),
343 "subc authenticated connection opened"
344 );
345
346 let (read_half, write_half) = tokio::io::split(stream);
347 let mut read_half = BufReader::new(read_half);
351 let (tx, rx) = mpsc::channel::<crate::router::OutboundFrame>(CONNECTION_EGRESS_BUFFER);
352 let mut writer = tokio::spawn(drain_writer(write_half, rx));
353
354 let egress = FrameSink::new(tx);
355 let ctx = RouteCtx {
356 connection_id,
357 egress: egress.clone(),
358 };
359
360 let mut route_open_tasks = JoinSet::new();
361 let loop_result = connection_loop(
362 &mut read_half,
363 Arc::clone(&router),
364 ctx.clone(),
365 close_receiver,
366 &mut route_open_tasks,
367 )
368 .await;
369
370 route_open_tasks.shutdown().await;
378
379 drop(ctx);
380 drop(egress);
381 drop(connection);
382
383 let close_reason = match &loop_result {
384 Ok(ConnectionLoopExit::CloseRequested(reason)) => Some(reason.to_string()),
385 Ok(ConnectionLoopExit::PeerClosed) | Err(_) => None,
386 };
387 let writer_result = if close_reason.is_some() {
388 match timeout(CLOSE_DRAIN_GRACE, &mut writer).await {
389 Ok(result) => Some(result.map_err(ConnectionError::WriterTask)),
390 Err(_) => {
391 warn!(
392 connection_id = connection_id.get(),
393 grace = ?CLOSE_DRAIN_GRACE,
394 "connection writer did not drain after close request; aborting writer task"
395 );
396 writer.abort();
397 let _ = writer.await;
398 None
399 }
400 }
401 } else {
402 Some(writer.await.map_err(ConnectionError::WriterTask))
403 };
404
405 let result = if let Some(reason) = close_reason.as_deref() {
406 match writer_result {
407 Some(Ok(Ok(()))) | None => Ok(()),
408 Some(Ok(Err(writer_err))) => {
409 debug!(
410 connection_id = connection_id.get(),
411 close_reason = reason,
412 writer_error = %writer_err,
413 "writer failed after requested connection close"
414 );
415 Ok(())
416 }
417 Some(Err(join_err)) => {
418 warn!(
419 connection_id = connection_id.get(),
420 close_reason = reason,
421 join_error = %join_err,
422 "writer task join failed after requested connection close"
423 );
424 Ok(())
425 }
426 }
427 } else {
428 let writer_result =
429 writer_result.expect("writer result is present without a close request");
430 match (loop_result, writer_result) {
431 (Err(loop_err), Ok(Ok(()))) => Err(loop_err),
432 (Err(loop_err), Ok(Err(writer_err))) => {
433 warn!(
434 connection_id = connection_id.get(),
435 writer_error = %writer_err,
436 "writer failed while closing after connection error"
437 );
438 Err(loop_err)
439 }
440 (Err(loop_err), Err(join_err)) => {
441 warn!(
442 connection_id = connection_id.get(),
443 join_error = %join_err,
444 "writer task join failed while closing after connection error"
445 );
446 Err(loop_err)
447 }
448 (Ok(ConnectionLoopExit::PeerClosed), Ok(Ok(()))) => Ok(()),
449 (Ok(ConnectionLoopExit::PeerClosed), Ok(Err(writer_err))) => {
450 Err(ConnectionError::FrameIo(writer_err))
451 }
452 (Ok(ConnectionLoopExit::PeerClosed), Err(join_err)) => Err(join_err),
453 (Ok(ConnectionLoopExit::CloseRequested(_)), _) => {
454 unreachable!("close requests are handled before normal writer result matching")
455 }
456 }
457 };
458
459 match &result {
460 Ok(()) => {
461 if let Some(reason) = close_reason.as_deref() {
462 debug!(
463 connection_id = connection_id.get(),
464 close_reason = reason,
465 "subc connection closed by request"
466 );
467 } else {
468 debug!(
469 connection_id = connection_id.get(),
470 "subc connection closed"
471 );
472 }
473 }
474 Err(err) => debug!(
475 connection_id = connection_id.get(),
476 error = %err,
477 "subc connection exited with error"
478 ),
479 }
480
481 result
482}
483
484async fn connection_loop<R>(
485 read_half: &mut R,
486 router: Arc<Router>,
487 ctx: RouteCtx,
488 mut close_receiver: ConnectionCloseReceiver,
489 route_open_tasks: &mut JoinSet<Result<(), RouterError>>,
490) -> Result<ConnectionLoopExit, ConnectionError>
491where
492 R: AsyncRead + Unpin,
493{
494 loop {
495 while let Some(result) = route_open_tasks.try_join_next() {
496 finish_route_open_task(result)?;
497 }
498
499 let read = read_frame(&mut *read_half);
502 tokio::pin!(read);
503 let frame = loop {
504 tokio::select! {
505 close = &mut close_receiver => {
506 return Ok(ConnectionLoopExit::CloseRequested(close_reason(close)));
507 }
508 result = route_open_tasks.join_next(), if !route_open_tasks.is_empty() => {
509 finish_route_open_task(
510 result.expect("a non-empty route.open JoinSet has a next task")
511 )?;
512 }
513 result = &mut read => {
514 break match result.map_err(ConnectionError::FrameIo)? {
515 Some(frame) => frame,
516 None => return Ok(ConnectionLoopExit::PeerClosed),
517 };
518 }
519 }
520 };
521
522 if let Some(target_module_id) = router.route_open_target(&frame) {
523 while let Some(result) = route_open_tasks.try_join_next() {
527 finish_route_open_task(result)?;
528 }
529
530 if route_open_tasks.len() >= MAX_PENDING_ROUTE_OPENS_PER_CONNECTION {
531 let refusal = router
534 .route_open_capacity_refusal(
535 &ctx,
536 &frame,
537 &target_module_id,
538 MAX_PENDING_ROUTE_OPENS_PER_CONNECTION,
539 )
540 .map_err(ConnectionError::Router)?;
541 let send_result = tokio::select! {
542 close = &mut close_receiver => {
543 return Ok(ConnectionLoopExit::CloseRequested(close_reason(close)));
544 }
545 result = ctx.egress.send(refusal) => result,
546 };
547 send_result.map_err(ConnectionError::Router)?;
548 continue;
549 }
550
551 let task_router = Arc::clone(&router);
552 let task_ctx = ctx.clone();
553 let dispatch_started_at = Instant::now();
556 route_open_tasks.spawn(async move {
557 route_open_tail(task_router, task_ctx, frame, dispatch_started_at).await
558 });
559 continue;
560 }
561
562 let route_result = tokio::select! {
565 close = &mut close_receiver => {
566 return Ok(ConnectionLoopExit::CloseRequested(close_reason(close)));
567 }
568 result = router.route_for_connection(&ctx, frame) => result,
569 };
570
571 if let Err(err) = route_result {
572 if let Some(error_frame) = err.to_error_frame() {
573 warn!(
574 connection_id = ctx.connection_id.get(),
575 error = %err,
576 "routing failure recovered with ERROR frame"
577 );
578 let send_result = tokio::select! {
579 close = &mut close_receiver => {
580 return Ok(ConnectionLoopExit::CloseRequested(close_reason(close)));
581 }
582 result = ctx.egress.send(error_frame) => result,
583 };
584 send_result.map_err(ConnectionError::Router)?;
585 } else {
586 debug!(
587 connection_id = ctx.connection_id.get(),
588 error = %err,
589 "fatal routing failure"
590 );
591 return Err(ConnectionError::Router(err));
592 }
593 }
594 }
595}
596
597async fn route_open_tail(
598 router: Arc<Router>,
599 ctx: RouteCtx,
600 frame: crate::Frame,
601 dispatch_started_at: Instant,
602) -> Result<(), RouterError> {
603 match router
604 .route_for_connection_started(&ctx, frame, Some(dispatch_started_at))
605 .await
606 {
607 Ok(()) => Ok(()),
608 Err(err) => {
609 let Some(error_frame) = err.to_error_frame() else {
610 return Err(err);
611 };
612 warn!(
613 connection_id = ctx.connection_id.get(),
614 error = %err,
615 "routing failure recovered with ERROR frame"
616 );
617 ctx.egress.send(error_frame).await
618 }
619 }
620}
621
622fn finish_route_open_task(
623 result: Result<Result<(), RouterError>, tokio::task::JoinError>,
624) -> Result<(), ConnectionError> {
625 match result {
626 Ok(Ok(())) => Ok(()),
627 Ok(Err(err)) => Err(ConnectionError::Router(err)),
628 Err(err) => Err(ConnectionError::Router(RouterError::backend(
629 0,
630 0,
631 format!("route.open task failed: {err}"),
632 ))),
633 }
634}
635
636fn close_reason(
637 result: Result<CloseReason, tokio::sync::oneshot::error::RecvError>,
638) -> CloseReason {
639 result.unwrap_or_else(|_| {
640 CloseReason::new(
641 "close_registry_dropped",
642 "connection close registration was dropped without a reason",
643 )
644 })
645}
646
647async fn drain_writer<W>(
648 write_half: W,
649 mut rx: mpsc::Receiver<crate::router::OutboundFrame>,
650) -> Result<(), FrameIoError>
651where
652 W: AsyncWrite + Unpin,
653{
654 let mut writer = BufWriter::new(write_half);
655 while let Some(outbound) = rx.recv().await {
656 write_outbound(&mut writer, outbound).await?;
657 while let Ok(outbound) = rx.try_recv() {
658 write_outbound(&mut writer, outbound).await?;
659 }
660 writer.flush().await.map_err(FrameIoError::Io)?;
661 }
662 writer.flush().await.map_err(FrameIoError::Io)?;
663 Ok(())
664}
665
666async fn write_outbound<W>(
673 writer: &mut BufWriter<W>,
674 outbound: crate::router::OutboundFrame,
675) -> Result<(), FrameIoError>
676where
677 W: AsyncWrite + Unpin,
678{
679 const SLOW_REPLY_QUEUE: Duration = Duration::from_millis(1000);
680 let queued = outbound.enqueued_at.elapsed();
681 let frame = outbound.frame;
682 if frame.header.channel == 0 && queued >= SLOW_REPLY_QUEUE {
683 let write_started = std::time::Instant::now();
684 let result = write_frame(writer, &frame).await;
685 tracing::warn!(
686 corr = frame.header.corr,
687 queued_ms = queued.as_millis() as u64,
688 write_ms = write_started.elapsed().as_millis() as u64,
689 "slow control reply write"
690 );
691 result?;
692 } else {
693 write_frame(writer, &frame).await?;
694 }
695 if let Some(flushed) = outbound.flushed {
696 writer.flush().await.map_err(FrameIoError::Io)?;
697 let _ = flushed.send(());
698 }
699 Ok(())
700}
701
702#[cfg(all(test, unix))]
703#[tokio::test]
704async fn shutdown_notice_ack_waits_for_socket_flush() {
705 let (socket, mut peer) = tokio::io::duplex(1);
706 let (tx, rx) = mpsc::channel(1);
707 let sink = FrameSink::new(tx);
708 let writer = tokio::spawn(drain_writer(socket, rx));
709 let frame = crate::Frame::build(
710 subc_protocol::FrameType::Push,
711 subc_protocol::Flags::new(false, subc_protocol::Priority::Interactive, false),
712 0,
713 0,
714 0,
715 b"notice".to_vec(),
716 )
717 .unwrap();
718 let send = sink.send_flushed(frame);
719 tokio::pin!(send);
720 assert!(
721 timeout(Duration::from_millis(20), &mut send).await.is_err(),
722 "queueing bytes is not a socket flush acknowledgement"
723 );
724 let (sent, received) = timeout(Duration::from_secs(1), async {
725 tokio::join!(&mut send, read_frame(&mut peer))
726 })
727 .await
728 .unwrap();
729 sent.unwrap();
730 assert_eq!(received.unwrap().unwrap().body, b"notice");
731 writer.abort();
732}
733
734#[derive(Debug)]
735pub enum ServerError {
736 NoListeners,
737 Accept {
738 local_addr: Option<SocketAddr>,
739 source: io::Error,
740 },
741}
742
743impl fmt::Display for ServerError {
744 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
745 match self {
746 Self::NoListeners => write!(f, "no TCP listeners were provided"),
747 Self::Accept { local_addr, source } => match local_addr {
748 Some(addr) => write!(f, "failed to accept TCP connection on {addr}: {source}"),
749 None => write!(f, "failed to accept TCP connection: {source}"),
750 },
751 }
752 }
753}
754
755impl Error for ServerError {
756 fn source(&self) -> Option<&(dyn Error + 'static)> {
757 match self {
758 Self::Accept { source, .. } => Some(source),
759 Self::NoListeners => None,
760 }
761 }
762}
763
764#[derive(Debug)]
765pub enum ConnectionError {
766 Auth(AuthError),
767 UnauthenticatedCapacity,
768 FrameIo(FrameIoError),
769 Router(RouterError),
770 WriterTask(tokio::task::JoinError),
771}
772
773impl ConnectionError {
774 fn is_quiet_reject(&self) -> bool {
775 matches!(self, Self::Auth(_) | Self::UnauthenticatedCapacity)
776 }
777}
778
779impl fmt::Display for ConnectionError {
780 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
781 match self {
782 Self::Auth(err) => write!(f, "connection auth failed: {err}"),
783 Self::UnauthenticatedCapacity => write!(
784 f,
785 "too many concurrent unauthenticated subc TCP connections"
786 ),
787 Self::FrameIo(err) => write!(f, "frame connection error: {err}"),
788 Self::Router(err) => write!(f, "router connection error: {err}"),
789 Self::WriterTask(err) => write!(f, "connection writer task failed: {err}"),
790 }
791 }
792}
793
794impl Error for ConnectionError {
795 fn source(&self) -> Option<&(dyn Error + 'static)> {
796 match self {
797 Self::Auth(err) => Some(err),
798 Self::FrameIo(err) => Some(err),
799 Self::Router(err) => Some(err),
800 Self::WriterTask(err) => Some(err),
801 Self::UnauthenticatedCapacity => None,
802 }
803 }
804}
805
806#[cfg(test)]
807mod tests {
808 use std::{
809 pin::Pin,
810 sync::atomic::{AtomicUsize, Ordering},
811 task::{Context, Poll},
812 };
813
814 use super::*;
815 use subc_protocol::{
816 DecodeError, ErrorBody, Flags, FrameType, Priority, HEADER_LEN, PROTOCOL_VERSION,
817 };
818 use tokio::io::{duplex, AsyncReadExt, AsyncWriteExt, ReadBuf};
819
820 use subc_transport::{authenticate_client, ConnectionInfo, Endpoint, SCHEMA_VERSION};
821
822 use crate::{ControlHandler, EchoBackend, Frame, ReadStage, Registry};
823
824 const TEST_DEADLINE: Duration = Duration::from_secs(2);
825 const TEST_DAEMON_VER: &str = "test-subc-server";
826
827 #[tokio::test]
832 async fn stale_queued_control_reply_logs_slow_reply_write() {
833 let (logs, _guard) = crate::router::test_log::log_capture(tracing::Level::WARN);
834 let (tx, rx) = mpsc::channel::<crate::router::OutboundFrame>(4);
835 let reply = Frame::build_with_version(
836 PROTOCOL_VERSION,
837 FrameType::Error,
838 Flags::new(false, Priority::Interactive, false),
839 0,
840 0,
841 7,
842 serde_json::to_vec(&ErrorBody {
843 code: "test".into(),
844 message: "reply".into(),
845 detail: None,
846 })
847 .expect("body encodes"),
848 )
849 .expect("frame builds");
850 tx.send(crate::router::OutboundFrame {
851 frame: reply,
852 enqueued_at: std::time::Instant::now() - Duration::from_millis(1500),
853 flushed: None,
854 })
855 .await
856 .expect("queued");
857 drop(tx);
858 let (write_half, mut read_half) = duplex(64 * 1024);
859 drain_writer(write_half, rx).await.expect("writer drains");
860 let mut sink = Vec::new();
861 read_half.read_to_end(&mut sink).await.expect("read");
862 assert!(!sink.is_empty(), "frame reached the socket");
863 let captured = crate::router::test_log::captured_logs(&logs);
864 assert!(
865 captured.contains("slow control reply write") && captured.contains("corr=7"),
866 "expected slow reply WARN naming corr, got: {captured}"
867 );
868 let queued_ms: u64 = captured
869 .split("queued_ms=")
870 .nth(1)
871 .and_then(|s| s.split_whitespace().next())
872 .and_then(|s| s.parse().ok())
873 .expect("queued_ms present");
874 assert!(
875 queued_ms >= 1500,
876 "queued_ms reflects residency: {queued_ms}"
877 );
878 }
879
880 #[tokio::test]
884 async fn fresh_control_and_stale_data_frames_log_nothing() {
885 let (logs, _guard) = crate::router::test_log::log_capture(tracing::Level::WARN);
886 let (tx, rx) = mpsc::channel::<crate::router::OutboundFrame>(4);
887 let control = Frame::build_with_version(
888 PROTOCOL_VERSION,
889 FrameType::Error,
890 Flags::new(false, Priority::Interactive, false),
891 0,
892 0,
893 8,
894 serde_json::to_vec(&ErrorBody {
895 code: "test".into(),
896 message: "fresh".into(),
897 detail: None,
898 })
899 .expect("body encodes"),
900 )
901 .expect("frame builds");
902 tx.send(crate::router::OutboundFrame {
903 frame: control,
904 enqueued_at: std::time::Instant::now(),
905 flushed: None,
906 })
907 .await
908 .expect("queued");
909 let data = Frame::build_with_version(
910 PROTOCOL_VERSION,
911 FrameType::Error,
912 Flags::new(false, Priority::Interactive, false),
913 9,
914 1,
915 9,
916 serde_json::to_vec(&ErrorBody {
917 code: "test".into(),
918 message: "data".into(),
919 detail: None,
920 })
921 .expect("body encodes"),
922 )
923 .expect("frame builds");
924 tx.send(crate::router::OutboundFrame {
925 frame: data,
926 enqueued_at: std::time::Instant::now() - Duration::from_millis(5000),
927 flushed: None,
928 })
929 .await
930 .expect("queued");
931 drop(tx);
932 let (write_half, _read_half) = duplex(64 * 1024);
933 drain_writer(write_half, rx).await.expect("writer drains");
934 let captured = crate::router::test_log::captured_logs(&logs);
935 assert!(
936 !captured.contains("slow control reply write"),
937 "no WARN for fresh control or stale data frames, got: {captured}"
938 );
939 }
940
941 struct CountingReader {
942 bytes: Vec<u8>,
943 offset: usize,
944 first_read_end: Option<usize>,
945 reads: Arc<AtomicUsize>,
946 }
947
948 impl CountingReader {
949 fn new(bytes: Vec<u8>, first_read_end: Option<usize>) -> (Self, Arc<AtomicUsize>) {
950 let reads = Arc::new(AtomicUsize::new(0));
951 (
952 Self {
953 bytes,
954 offset: 0,
955 first_read_end,
956 reads: Arc::clone(&reads),
957 },
958 reads,
959 )
960 }
961 }
962
963 impl AsyncRead for CountingReader {
964 fn poll_read(
965 mut self: Pin<&mut Self>,
966 _cx: &mut Context<'_>,
967 buf: &mut ReadBuf<'_>,
968 ) -> Poll<io::Result<()>> {
969 self.reads.fetch_add(1, Ordering::Relaxed);
970 let available = self.bytes.len().saturating_sub(self.offset);
971 let first_read_remaining = self
972 .first_read_end
973 .filter(|end| self.offset < *end)
974 .map_or(available, |end| end - self.offset);
975 let count = available.min(first_read_remaining).min(buf.remaining());
976 let end = self.offset + count;
977 buf.put_slice(&self.bytes[self.offset..end]);
978 self.offset = end;
979 Poll::Ready(Ok(()))
980 }
981 }
982
983 fn encode_frames(frames: &[Frame]) -> Vec<u8> {
984 let mut bytes = Vec::new();
985 for frame in frames {
986 bytes.extend_from_slice(&frame.header.encode());
987 bytes.extend_from_slice(&frame.body);
988 }
989 bytes
990 }
991
992 async fn read_frames<R>(reader: &mut R, count: usize) -> Vec<Frame>
993 where
994 R: AsyncRead + Unpin,
995 {
996 let mut frames = Vec::with_capacity(count);
997 for _ in 0..count {
998 frames.push(read_frame(reader).await.unwrap().unwrap());
999 }
1000 frames
1001 }
1002
1003 fn request(channel: u16, corr: u64, body: &[u8]) -> Frame {
1004 Frame::build(
1005 FrameType::Request,
1006 Flags::new(true, Priority::Interactive, false),
1007 channel,
1008 0,
1009 corr,
1010 body.to_vec(),
1011 )
1012 .unwrap()
1013 }
1014
1015 fn echo_router() -> Arc<Router> {
1016 let mut router = Router::with_default_self_handler();
1017 router.register_backend(7, EchoBackend).unwrap();
1018 router.register_backend(9, EchoBackend).unwrap();
1019 Arc::new(router)
1020 }
1021
1022 fn test_auth() -> (ServerAuth, ConnectionInfo) {
1023 test_auth_with_limit(4)
1024 }
1025
1026 fn test_auth_with_limit(max_unauthenticated: usize) -> (ServerAuth, ConnectionInfo) {
1027 let key = vec![0x42; 32];
1028 let daemon_id = [0x24; 16];
1029 let conn = ConnectionInfo {
1030 schema: SCHEMA_VERSION,
1031 wire_version: None,
1032 endpoints: vec![Endpoint {
1033 host: "127.0.0.1".to_owned(),
1034 port: 1,
1035 }],
1036 key: key.clone(),
1037 daemon_id,
1038 pid: std::process::id(),
1039 daemon_ver: TEST_DAEMON_VER.to_owned(),
1040 };
1041 (
1042 ServerAuth::with_limits(
1043 key,
1044 daemon_id,
1045 TEST_DAEMON_VER,
1046 TEST_DEADLINE,
1047 max_unauthenticated,
1048 ),
1049 conn,
1050 )
1051 }
1052
1053 async fn authenticate<S>(stream: &mut S, conn: &ConnectionInfo)
1054 where
1055 S: AsyncRead + AsyncWrite + Unpin,
1056 {
1057 authenticate_client(stream, conn, TEST_DEADLINE)
1058 .await
1059 .expect("test client should authenticate")
1060 }
1061
1062 #[tokio::test]
1063 async fn buffered_frame_reader_coalesces_reads_and_preserves_short_reads() {
1064 let frames = vec![
1065 request(7, 1, b"first"),
1066 request(9, 2, b"second"),
1067 request(7, 3, b"third"),
1068 request(9, 4, b"fourth"),
1069 ];
1070 let bytes = encode_frames(&frames);
1071
1072 let (mut direct, direct_reads) = CountingReader::new(bytes.clone(), None);
1073 assert_eq!(read_frames(&mut direct, frames.len()).await, frames);
1074 assert_eq!(direct_reads.load(Ordering::Relaxed), frames.len() * 3);
1075
1076 let (buffered_source, buffered_reads) = CountingReader::new(bytes, None);
1077 let mut buffered = BufReader::new(buffered_source);
1078 assert_eq!(read_frames(&mut buffered, frames.len()).await, frames);
1079 assert_eq!(buffered_reads.load(Ordering::Relaxed), 1);
1080
1081 let split_frame = request(7, 5, b"split-body");
1082 let split_bytes = encode_frames(std::slice::from_ref(&split_frame));
1083 let (split_source, split_reads) = CountingReader::new(split_bytes, Some(10));
1084 let mut split_reader = BufReader::new(split_source);
1085 assert_eq!(
1086 read_frame(&mut split_reader).await.unwrap(),
1087 Some(split_frame)
1088 );
1089 assert_eq!(split_reads.load(Ordering::Relaxed), 2);
1090 }
1091
1092 #[tokio::test]
1093 async fn interleaved_channels_on_one_stream_demux_byte_identically_after_auth() {
1094 let (mut client, server_stream) = duplex(4096);
1095 let (auth, conn) = test_auth();
1096 let server = tokio::spawn(handle_connection(server_stream, echo_router(), auth));
1097 authenticate(&mut client, &conn).await;
1098 let frames = [
1099 request(7, 1, b"chan7-first\0opaque"),
1100 request(9, 2, b"chan9-middle-{json?}"),
1101 request(7, 3, b"chan7-second\xffbytes"),
1102 ];
1103
1104 for frame in &frames {
1105 crate::write_frame(&mut client, frame).await.unwrap();
1106 }
1107
1108 for expected in &frames {
1109 let response = crate::read_frame(&mut client).await.unwrap().unwrap();
1110 assert_eq!(response.header.ty, FrameType::Response);
1111 assert_eq!(response.header.channel, expected.header.channel);
1112 assert_eq!(response.header.corr, expected.header.corr);
1113 assert_eq!(response.body, expected.body);
1114 }
1115
1116 drop(client);
1117 server.await.unwrap().unwrap();
1118 }
1119
1120 #[tokio::test]
1121 async fn channel_zero_goes_to_subc_self_handler_after_auth() {
1122 let (mut client, server_stream) = duplex(512);
1123 let (auth, conn) = test_auth();
1124 let server = tokio::spawn(handle_connection(
1125 server_stream,
1126 Arc::new(Router::with_default_self_handler()),
1127 auth,
1128 ));
1129 authenticate(&mut client, &conn).await;
1130 let ping = Frame::build(
1131 FrameType::Ping,
1132 Flags::new(false, Priority::Passive, false),
1133 0,
1134 0,
1135 55,
1136 Vec::new(),
1137 )
1138 .unwrap();
1139
1140 crate::write_frame(&mut client, &ping).await.unwrap();
1141 let response = crate::read_frame(&mut client).await.unwrap().unwrap();
1142
1143 assert_eq!(response.header.ty, FrameType::Pong);
1144 assert_eq!(response.header.channel, 0);
1145 assert_eq!(response.header.corr, 55);
1146 assert!(response.body.is_empty());
1147
1148 drop(client);
1149 server.await.unwrap().unwrap();
1150 }
1151
1152 #[tokio::test]
1153 async fn unauthenticated_connection_is_rejected_before_routing() {
1154 let (mut client, server_stream) = duplex(512);
1155 let (auth, _conn) = test_auth();
1156 let registry = Arc::new(Registry::default());
1157 let router = Arc::new(Router::with_control_handler(Arc::new(ControlHandler::new(
1158 Arc::clone(®istry),
1159 ))));
1160 let server = tokio::spawn(handle_connection(server_stream, router, auth));
1161 let ping = Frame::build(
1162 FrameType::Ping,
1163 Flags::new(false, Priority::Passive, false),
1164 0,
1165 0,
1166 66,
1167 Vec::new(),
1168 )
1169 .unwrap();
1170
1171 crate::write_frame(&mut client, &ping).await.unwrap();
1172 if let Ok(Ok(Some(frame))) =
1173 tokio::time::timeout(Duration::from_millis(200), crate::read_frame(&mut client)).await
1174 {
1175 panic!("unauthenticated frame reached router: {frame:?}");
1176 }
1177
1178 let err = server.await.unwrap().unwrap_err();
1179 assert!(matches!(err, ConnectionError::Auth(_)));
1180 assert_eq!(registry.active_registration_count().unwrap(), 0);
1181 }
1182
1183 #[tokio::test]
1184 async fn over_cap_peer_queues_for_a_slot_and_authenticates_when_one_frees() {
1185 let (mut first_client, first_server_stream) = duplex(2048);
1190 let (mut second_client, second_server_stream) = duplex(2048);
1191 let (auth, conn) = test_auth_with_limit(1);
1192 let registry = Arc::new(Registry::default());
1193 let router = Arc::new(Router::with_control_handler(Arc::new(ControlHandler::new(
1194 Arc::clone(®istry),
1195 ))));
1196
1197 let first_server = tokio::spawn(handle_connection(
1198 first_server_stream,
1199 Arc::clone(&router),
1200 auth.clone(),
1201 ));
1202
1203 let second_server = tokio::spawn(handle_connection(
1204 second_server_stream,
1205 Arc::clone(&router),
1206 auth.clone(),
1207 ));
1208
1209 tokio::time::sleep(Duration::from_millis(50)).await;
1211 assert!(
1212 !second_server.is_finished(),
1213 "queued peer must not be reset"
1214 );
1215
1216 authenticate(&mut first_client, &conn).await;
1219 authenticate(&mut second_client, &conn).await;
1220
1221 drop(first_client);
1222 drop(second_client);
1223 let _ = first_server.await;
1224 let _ = second_server.await;
1225 assert_eq!(registry.active_registration_count().unwrap(), 0);
1226 }
1227
1228 #[tokio::test]
1229 async fn over_cap_peer_is_rejected_when_no_slot_frees_within_deadline() {
1230 let (mut second_client, second_server_stream) = duplex(512);
1236 let (auth, conn) = test_auth_with_limit(1);
1237 let registry = Arc::new(Registry::default());
1238 let router = Arc::new(Router::with_control_handler(Arc::new(ControlHandler::new(
1239 Arc::clone(®istry),
1240 ))));
1241
1242 let held_slot = auth
1243 .unauthenticated
1244 .clone()
1245 .try_acquire_owned()
1246 .expect("sole pre-auth slot");
1247
1248 let second_server = tokio::spawn(handle_connection(
1249 second_server_stream,
1250 Arc::clone(&router),
1251 auth.clone(),
1252 ));
1253 let second_err = tokio::time::timeout(TEST_DEADLINE * 2, second_server)
1254 .await
1255 .expect("capacity reject should settle at the deadline")
1256 .expect("second connection task should not panic")
1257 .expect_err("queued peer must be rejected when no slot frees");
1258 drop(held_slot);
1259 assert!(matches!(
1260 second_err,
1261 ConnectionError::UnauthenticatedCapacity
1262 ));
1263 let mut closed = [0u8; 1];
1264 assert_eq!(
1265 second_client.read(&mut closed).await.unwrap(),
1266 0,
1267 "capacity-rejected peer should observe a closed stream"
1268 );
1269 assert_eq!(registry.active_registration_count().unwrap(), 0);
1270
1271 let (mut authed_client, authed_server_stream) = duplex(2048);
1272 let authed_server = tokio::spawn(handle_connection(
1273 authed_server_stream,
1274 Arc::clone(&router),
1275 auth,
1276 ));
1277 authenticate(&mut authed_client, &conn).await;
1278 let ping = Frame::build(
1279 FrameType::Ping,
1280 Flags::new(false, Priority::Passive, false),
1281 0,
1282 0,
1283 77,
1284 Vec::new(),
1285 )
1286 .unwrap();
1287 crate::write_frame(&mut authed_client, &ping).await.unwrap();
1288 let pong = crate::read_frame(&mut authed_client)
1289 .await
1290 .unwrap()
1291 .unwrap();
1292 assert_eq!(pong.header.ty, FrameType::Pong);
1293 assert_eq!(pong.header.channel, 0);
1294 assert_eq!(pong.header.corr, 77);
1295 assert_eq!(registry.active_registration_count().unwrap(), 0);
1296
1297 drop(authed_client);
1298 authed_server.await.unwrap().unwrap();
1299 }
1300
1301 #[tokio::test]
1302 async fn bind_ack_during_partial_frame_preserves_next_request() {
1303 use subc_control::ClientControlRequest;
1304 use subc_protocol::{
1305 manifest::{
1306 Concurrency, ExecutionMode, IdentityScope, ModuleManifest, ProviderRole, Tool,
1307 },
1308 session::{ModuleControlRequest, ModuleControlResponse},
1309 BindIdentity, ModuleHelloBody, RouteTarget,
1310 };
1311
1312 let mut configured_router = Router::with_default_self_handler();
1313 configured_router.register_backend(7, EchoBackend).unwrap();
1314 let router = Arc::new(configured_router);
1315 let (auth, conn) = test_auth();
1316 let (mut module, module_stream) = duplex(4096);
1317 let module_server = tokio::spawn(handle_connection(
1318 module_stream,
1319 Arc::clone(&router),
1320 auth.clone(),
1321 ));
1322 authenticate(&mut module, &conn).await;
1323 let manifest = ModuleManifest::builder("frame-test", "0.1.0")
1324 .protocol_ver(PROTOCOL_VERSION)
1325 .provides(vec![ProviderRole::ToolProvider {
1326 tools: vec![Tool {
1327 name: "read".into(),
1328 description: None,
1329 execution_mode: ExecutionMode::Pure,
1330 schema: serde_json::json!({"type": "object"}),
1331 }],
1332 identity_scope: vec![IdentityScope::Project, IdentityScope::Session],
1333 concurrency: Concurrency::ModuleManaged,
1334 emits_push: true,
1335 sub_supervises: true,
1336 }])
1337 .build();
1338 let hello = Frame::build(
1339 FrameType::Hello,
1340 Flags::new(false, Priority::Passive, false),
1341 0,
1342 0,
1343 1,
1344 serde_json::to_vec(&ModuleHelloBody {
1345 manifest,
1346 protocol_ver: PROTOCOL_VERSION,
1347 control_ops: None,
1348 launch_nonce: None,
1349 })
1350 .unwrap(),
1351 )
1352 .unwrap();
1353 crate::write_frame(&mut module, &hello).await.unwrap();
1354 assert_eq!(
1355 read_frame(&mut module).await.unwrap().unwrap().header.ty,
1356 FrameType::HelloAck
1357 );
1358
1359 let (mut client, client_stream) = duplex(4096);
1360 let client_server = tokio::spawn(handle_connection(client_stream, router, auth));
1361 authenticate(&mut client, &conn).await;
1362 let open = Frame::build(
1363 FrameType::Request,
1364 Flags::new(false, Priority::Passive, false),
1365 0,
1366 0,
1367 2,
1368 serde_json::to_vec(&ClientControlRequest::RouteOpen {
1369 target: RouteTarget::ToolProvider {
1370 module_id: "frame-test".into(),
1371 },
1372 identity: BindIdentity::new(std::env::current_dir().unwrap(), "unit", "session"),
1373 consumer_identity: None,
1374 consumer_capabilities: None,
1375 admission_facts: None,
1376 })
1377 .unwrap(),
1378 )
1379 .unwrap();
1380 crate::write_frame(&mut client, &open).await.unwrap();
1381 let bind = timeout(TEST_DEADLINE, read_frame(&mut module))
1382 .await
1383 .unwrap()
1384 .unwrap()
1385 .unwrap();
1386 assert!(matches!(
1387 serde_json::from_slice::<ModuleControlRequest>(&bind.body).unwrap(),
1388 ModuleControlRequest::RouteBind { .. }
1389 ));
1390
1391 let ping = request(7, 3, b"partial-frame-body");
1392 client.write_all(&ping.header.encode()).await.unwrap();
1393 tokio::time::sleep(Duration::from_millis(30)).await;
1395 let ack = Frame::build(
1396 FrameType::Response,
1397 Flags::new(false, Priority::Passive, false),
1398 0,
1399 0,
1400 bind.header.corr,
1401 serde_json::to_vec(&ModuleControlResponse::RouteBindAck {}).unwrap(),
1402 )
1403 .unwrap();
1404 crate::write_frame(&mut module, &ack).await.unwrap();
1405 let opened = timeout(TEST_DEADLINE, read_frame(&mut client))
1406 .await
1407 .unwrap()
1408 .unwrap();
1409 if opened.is_none() {
1410 panic!("client closed: {:?}", client_server.await);
1411 }
1412 let opened = opened.unwrap();
1413 assert_eq!(opened.header.corr, 2);
1414 client.write_all(&ping.body).await.unwrap();
1415 let pong = timeout(TEST_DEADLINE, read_frame(&mut client))
1416 .await
1417 .expect("partial frame must reach the router after the bind ack")
1418 .expect("frame must decode")
1419 .expect("connection must remain open");
1420 assert_eq!(pong.header.ty, FrameType::Response);
1421 assert_eq!(pong.header.corr, 3);
1422 assert_eq!(pong.body, ping.body);
1423 drop(client);
1424 drop(module);
1425 client_server.await.unwrap().unwrap();
1426 let _ = module_server.await.unwrap();
1427 }
1428
1429 #[tokio::test]
1430 async fn aborted_accept_does_not_end_listener() {
1431 let listener = Arc::new(TcpListener::bind("127.0.0.1:0").await.unwrap());
1432 let addr = listener.local_addr().unwrap();
1433 let (auth, conn) = test_auth();
1434 let mut attempts = 0;
1435 let server = tokio::spawn(serve_listener_with_accept(
1436 Some(addr),
1437 echo_router(),
1438 auth,
1439 move || {
1440 attempts += 1;
1441 let result = if attempts == 1 {
1442 Some(io::Error::from(io::ErrorKind::ConnectionAborted))
1443 } else {
1444 None
1445 };
1446 let listener = Arc::clone(&listener);
1447 async move {
1448 match result {
1449 Some(err) => Err(err),
1450 None => listener.accept().await,
1451 }
1452 }
1453 },
1454 ));
1455 let mut client = tokio::net::TcpStream::connect(addr).await.unwrap();
1456 authenticate(&mut client, &conn).await;
1457 let ping = Frame::build(
1458 FrameType::Ping,
1459 Flags::new(false, Priority::Passive, false),
1460 0,
1461 0,
1462 77,
1463 Vec::new(),
1464 )
1465 .unwrap();
1466 crate::write_frame(&mut client, &ping).await.unwrap();
1467 let pong = timeout(TEST_DEADLINE, read_frame(&mut client))
1468 .await
1469 .unwrap()
1470 .unwrap()
1471 .unwrap();
1472 assert_eq!(pong.header.ty, FrameType::Pong);
1473 assert!(
1474 !server.is_finished(),
1475 "a temporary accept failure must not stop the listener"
1476 );
1477 server.abort();
1478 }
1479
1480 #[tokio::test]
1481 async fn exhausted_accept_backs_off_then_serves_connection() {
1482 let listener = Arc::new(TcpListener::bind("127.0.0.1:0").await.unwrap());
1483 let addr = listener.local_addr().unwrap();
1484 let (auth, conn) = test_auth();
1485 let mut attempts = 0;
1486 let server = tokio::spawn(serve_listener_with_accept(
1487 Some(addr),
1488 echo_router(),
1489 auth,
1490 move || {
1491 attempts += 1;
1492 #[cfg(unix)]
1495 let emfile = rustix::io::Errno::MFILE.raw_os_error();
1496 #[cfg(windows)]
1497 let emfile = 10024;
1498 let error = (attempts == 1).then(|| io::Error::from_raw_os_error(emfile));
1499 let listener = Arc::clone(&listener);
1500 async move {
1501 match error {
1502 Some(err) => Err(err),
1503 None => listener.accept().await,
1504 }
1505 }
1506 },
1507 ));
1508 let started = Instant::now();
1509 let mut client = tokio::net::TcpStream::connect(addr).await.unwrap();
1510 authenticate(&mut client, &conn).await;
1511 assert!(
1512 started.elapsed() >= Duration::from_millis(50),
1513 "fd exhaustion must back off before retrying"
1514 );
1515 assert!(!server.is_finished());
1516 server.abort();
1517 }
1518
1519 #[tokio::test]
1520 async fn fatal_accept_error_still_stops_listener() {
1521 let (auth, _) = test_auth();
1522 let err = serve_listener_with_accept(None, echo_router(), auth, || async {
1523 Err(io::Error::from(io::ErrorKind::PermissionDenied))
1524 })
1525 .await
1526 .unwrap_err();
1527 assert!(
1528 matches!(err, ServerError::Accept { source, .. } if source.kind() == io::ErrorKind::PermissionDenied)
1529 );
1530 }
1531
1532 #[tokio::test]
1533 async fn serve_listeners_with_no_listeners_returns_typed_error() {
1534 let (auth, _conn) = test_auth();
1535 let err = serve_listeners(Vec::new(), echo_router(), auth)
1536 .await
1537 .expect_err("empty listener set must fail loudly");
1538 assert!(matches!(err, ServerError::NoListeners));
1539 }
1540
1541 #[tokio::test]
1542 async fn malformed_header_returns_typed_error_no_panic() {
1543 let (mut client, server_stream) = duplex(128);
1544 let (auth, conn) = test_auth();
1545 let server = tokio::spawn(handle_connection(server_stream, echo_router(), auth));
1546 authenticate(&mut client, &conn).await;
1547 let mut header = [0u8; HEADER_LEN];
1548 header[4] = PROTOCOL_VERSION;
1549 header[5] = 250;
1550
1551 client.write_all(&header).await.unwrap();
1552 drop(client);
1553
1554 let err = server.await.unwrap().unwrap_err();
1555 assert!(matches!(
1556 err,
1557 ConnectionError::FrameIo(FrameIoError::DecodeHeader(DecodeError::UnknownFrameType {
1558 byte: 250
1559 }))
1560 ));
1561 }
1562
1563 #[tokio::test]
1564 async fn truncated_body_returns_typed_error_no_panic() {
1565 let (mut client, server_stream) = duplex(128);
1566 let (auth, conn) = test_auth();
1567 let server = tokio::spawn(handle_connection(server_stream, echo_router(), auth));
1568 authenticate(&mut client, &conn).await;
1569 let frame = request(7, 8, b"abcd");
1570
1571 client.write_all(&frame.header.encode()).await.unwrap();
1572 client.write_all(b"ab").await.unwrap();
1573 drop(client);
1574
1575 let err = server.await.unwrap().unwrap_err();
1576 assert!(matches!(
1577 err,
1578 ConnectionError::FrameIo(FrameIoError::UnexpectedEof {
1579 stage: ReadStage::Body,
1580 expected: 4,
1581 actual: 2
1582 })
1583 ));
1584 }
1585
1586 #[tokio::test]
1587 async fn unknown_channel_is_returned_as_error_frame_and_connection_continues() {
1588 let (mut client, server_stream) = duplex(1024);
1589 let (auth, conn) = test_auth();
1590 let server = tokio::spawn(handle_connection(server_stream, echo_router(), auth));
1591 authenticate(&mut client, &conn).await;
1592 let unknown = request(42, 10, b"lost");
1593 let known = request(7, 11, b"still-routes");
1594
1595 crate::write_frame(&mut client, &unknown).await.unwrap();
1596 crate::write_frame(&mut client, &known).await.unwrap();
1597
1598 let error = crate::read_frame(&mut client).await.unwrap().unwrap();
1599 assert_eq!(error.header.ty, FrameType::Error);
1600 assert_eq!(error.header.channel, 42);
1601 assert_eq!(error.header.corr, 10);
1602 let error_body: ErrorBody = serde_json::from_slice(&error.body).unwrap();
1603 assert_eq!(error_body.code, "unknown_channel");
1604
1605 let response = crate::read_frame(&mut client).await.unwrap().unwrap();
1606 assert_eq!(response.header.ty, FrameType::Response);
1607 assert_eq!(response.header.channel, 7);
1608 assert_eq!(response.header.corr, 11);
1609 assert_eq!(response.body, b"still-routes");
1610
1611 drop(client);
1612 server.await.unwrap().unwrap();
1613 }
1614}