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