1use std::collections::HashMap;
58use std::path::PathBuf;
59use std::sync::atomic::{AtomicU32, AtomicU64, Ordering};
60use std::sync::Arc;
61use std::time::Duration;
62
63use crate::aux_egress::AuxEgress;
64use crate::aux_mux::{self, AuxChannel};
65use crate::aux_rpc::{self, RpcMethod};
66use crate::frame::HEADER_SIZE;
67use serde::Serialize;
68use tokio::io::{AsyncReadExt, AsyncWriteExt};
69use tokio::net::{UnixListener, UnixStream};
70use tokio::sync::{oneshot, Mutex, Notify};
71
72pub const RPC_DEFAULT_TIMEOUT: Duration = Duration::from_secs(10);
84
85const IPC_WRITE_TIMEOUT: Duration = Duration::from_millis(200);
92
93const IPC_CONNECT_TIMEOUT: Duration = Duration::from_millis(200);
97
98const RPC_RETRY_DELAYS: [Duration; 4] = [
111 Duration::from_millis(150),
112 Duration::from_millis(250),
113 Duration::from_millis(400),
114 Duration::from_millis(700),
115];
116
117const MAX_PENDING_CALLS: usize = 1024;
126
127const REQUEST_FRAGMENT_PACING: Duration = Duration::from_millis(5);
131
132pub const DEFAULT_RESPONSE_SOCK: &str = "/run/ados/aux-rpc-responses.sock";
134
135#[derive(Debug, Clone, PartialEq, Eq)]
137pub struct RpcResponseOwned {
138 pub status: u16,
140 pub body: Vec<u8>,
143 pub headers: Vec<aux_rpc::ResponseHeader>,
154}
155
156#[derive(Debug, Clone, PartialEq, Eq)]
158pub enum RpcError {
159 TooLarge,
162 Encode,
165 Send(String),
167 Timeout,
169 Incomplete { received: usize, total: u16 },
173 ChannelClosed,
176 Busy,
180}
181
182impl std::fmt::Display for RpcError {
183 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
184 match self {
185 Self::TooLarge => write!(f, "request body exceeds the relay limit"),
186 Self::Encode => write!(f, "request field too long to encode"),
187 Self::Send(e) => write!(f, "aux egress send failed: {e}"),
188 Self::Timeout => write!(f, "no response before timeout"),
189 Self::Incomplete { received, total } => {
190 write!(f, "relay response incomplete: {received}/{total} fragments")
191 }
192 Self::ChannelClosed => write!(f, "aux consumer channel closed before response"),
193 Self::Busy => write!(f, "relay proxy has too many calls in flight"),
194 }
195 }
196}
197
198impl std::error::Error for RpcError {}
199
200struct PendingCall {
203 sender: oneshot::Sender<RpcResponseOwned>,
204 expected_sender: Option<Box<str>>,
211 assembly: Option<Assembly>,
213 response_started: bool,
220}
221
222struct Assembly {
224 status: u16,
225 total: u16,
226 received: usize,
230 accepted: u64,
234 decoder: aux_rpc::ResponseDecoder,
235}
236
237#[derive(Default)]
241pub struct RpcProxyCounters {
242 calls_started: AtomicU64,
243 calls_ok: AtomicU64,
244 calls_timeout: AtomicU64,
245 calls_incomplete: AtomicU64,
246 calls_send_error: AtomicU64,
247 calls_channel_closed: AtomicU64,
248 calls_busy: AtomicU64,
249 retransmits: AtomicU64,
250 retransmit_send_errors: AtomicU64,
253 fragments_received: AtomicU64,
254 fragments_duplicate: AtomicU64,
255 fragments_wrong_sender: AtomicU64,
259 fragments_bad_symbol: AtomicU64,
263 responses_out_of_range: AtomicU64,
264 pending_now: AtomicU64,
268}
269
270#[derive(Debug, Default, Clone, Copy, Serialize, PartialEq, Eq)]
272pub struct RpcProxyStats {
273 pub calls_started: u64,
274 pub calls_ok: u64,
275 pub calls_timeout: u64,
276 pub calls_incomplete: u64,
277 pub calls_send_error: u64,
278 pub calls_channel_closed: u64,
279 pub calls_busy: u64,
280 pub retransmits: u64,
281 pub retransmit_send_errors: u64,
282 pub fragments_received: u64,
283 pub fragments_duplicate: u64,
284 pub fragments_wrong_sender: u64,
285 pub fragments_bad_symbol: u64,
286 pub responses_out_of_range: u64,
287 pub pending_now: u64,
288}
289
290#[derive(Clone)]
293pub struct AuxRpcProxy {
294 egress: Arc<AuxEgress>,
295 pending: Arc<Mutex<HashMap<u32, PendingCall>>>,
296 next_id: Arc<AtomicU32>,
297 counters: Arc<RpcProxyCounters>,
298 timeout: Duration,
299}
300
301impl AuxRpcProxy {
302 pub fn new(egress: AuxEgress) -> Self {
304 Self::with_timeout(egress, RPC_DEFAULT_TIMEOUT)
305 }
306
307 pub fn with_timeout(egress: AuxEgress, timeout: Duration) -> Self {
310 let seed = std::time::SystemTime::now()
316 .duration_since(std::time::UNIX_EPOCH)
317 .map(|d| d.subsec_nanos() ^ std::process::id())
318 .unwrap_or(1)
319 | 1;
320 Self {
321 egress: Arc::new(egress),
322 pending: Arc::new(Mutex::new(HashMap::new())),
323 next_id: Arc::new(AtomicU32::new(seed)),
324 counters: Arc::new(RpcProxyCounters::default()),
325 timeout,
326 }
327 }
328
329 pub async fn call(
338 &self,
339 target: &[u8],
340 method: RpcMethod,
341 path: &[u8],
342 body: &[u8],
343 ) -> Result<RpcResponseOwned, RpcError> {
344 self.call_request(
345 target,
346 method,
347 &aux_rpc::RequestParts {
348 path,
349 body,
350 ..Default::default()
351 },
352 )
353 .await
354 }
355
356 pub async fn call_request(
359 &self,
360 target: &[u8],
361 method: RpcMethod,
362 parts: &aux_rpc::RequestParts<'_>,
363 ) -> Result<RpcResponseOwned, RpcError> {
364 let id = self.next_id.fetch_add(1, Ordering::Relaxed);
365 self.call_with_id(id, target, method, parts).await
366 }
367
368 async fn send_fragments(&self, fragments: &[Vec<u8>]) -> Result<(), String> {
371 let last = fragments.len().saturating_sub(1);
372 for (index, fragment) in fragments.iter().enumerate() {
373 self.egress
374 .send(AuxChannel::Request, fragment)
375 .await
376 .map_err(|e| e.to_string())?;
377 if index < last {
378 tokio::time::sleep(REQUEST_FRAGMENT_PACING).await;
379 }
380 }
381 Ok(())
382 }
383
384 async fn call_with_id(
385 &self,
386 id: u32,
387 target: &[u8],
388 method: RpcMethod,
389 parts: &aux_rpc::RequestParts<'_>,
390 ) -> Result<RpcResponseOwned, RpcError> {
391 let fragments =
392 aux_rpc::encode_request(method, id, target, parts).map_err(|e| match e {
393 aux_rpc::RequestEncodeError::BodyTooLarge => RpcError::TooLarge,
394 aux_rpc::RequestEncodeError::FieldTooLong => RpcError::Encode,
395 })?;
396 let expected_sender = (!target.is_empty()).then(|| {
401 String::from_utf8_lossy(target)
402 .into_owned()
403 .into_boxed_str()
404 });
405
406 let (tx, rx) = oneshot::channel::<RpcResponseOwned>();
407 {
408 let mut pending = self.pending.lock().await;
409 if pending.len() >= MAX_PENDING_CALLS {
410 self.counters.calls_busy.fetch_add(1, Ordering::Relaxed);
411 return Err(RpcError::Busy);
412 }
413 pending.insert(
414 id,
415 PendingCall {
416 sender: tx,
417 expected_sender,
418 assembly: None,
419 response_started: false,
420 },
421 );
422 }
423 self.counters.pending_now.fetch_add(1, Ordering::Relaxed);
424 self.counters.calls_started.fetch_add(1, Ordering::Relaxed);
425
426 let mut guard = PendingGuard {
429 pending: Arc::clone(&self.pending),
430 counters: Arc::clone(&self.counters),
431 id,
432 armed: true,
433 };
434
435 if let Err(e) = self.send_fragments(&fragments).await {
436 guard.disarm();
437 self.remove_pending(id).await;
438 self.counters
439 .calls_send_error
440 .fetch_add(1, Ordering::Relaxed);
441 return Err(RpcError::Send(e));
442 }
443
444 let deadline = tokio::time::Instant::now() + self.timeout;
450 let mut attempt = 0usize;
451 tokio::pin!(rx);
452
453 loop {
454 let wake = match RPC_RETRY_DELAYS.get(attempt).copied() {
455 Some(d) => (tokio::time::Instant::now() + d).min(deadline),
456 None => deadline,
457 };
458 tokio::select! {
459 biased;
460 res = &mut rx => {
461 guard.disarm();
462 return match res {
463 Ok(resp) => {
464 self.counters.calls_ok.fetch_add(1, Ordering::Relaxed);
465 Ok(resp)
466 }
467 Err(_) => {
468 self.remove_pending(id).await;
469 self.counters
470 .calls_channel_closed
471 .fetch_add(1, Ordering::Relaxed);
472 Err(RpcError::ChannelClosed)
473 }
474 };
475 }
476 _ = tokio::time::sleep_until(wake) => {
477 if tokio::time::Instant::now() >= deadline {
478 guard.disarm();
479 let partial = self.remove_pending(id).await;
481 return match partial.and_then(|p| p.assembly) {
482 Some(a) => {
483 self.counters
484 .calls_incomplete
485 .fetch_add(1, Ordering::Relaxed);
486 Err(RpcError::Incomplete {
487 received: a.received,
488 total: a.total,
489 })
490 }
491 None => {
492 self.counters.calls_timeout.fetch_add(1, Ordering::Relaxed);
493 Err(RpcError::Timeout)
494 }
495 };
496 }
497 attempt += 1;
498 let started = self
499 .pending
500 .lock()
501 .await
502 .get(&id)
503 .map(|p| p.response_started)
504 .unwrap_or(false);
505 if !started {
506 match self.send_fragments(&fragments).await {
513 Ok(()) => {
514 self.counters.retransmits.fetch_add(1, Ordering::Relaxed);
515 }
516 Err(e) => {
517 self.counters
518 .retransmit_send_errors
519 .fetch_add(1, Ordering::Relaxed);
520 tracing::warn!(
521 request_id = id,
522 attempt,
523 error = %e,
524 "aux_rpc_retransmit_send_failed"
525 );
526 }
527 }
528 }
529 }
530 }
531 }
532 }
533
534 async fn remove_pending(&self, id: u32) -> Option<PendingCall> {
537 let removed = self.pending.lock().await.remove(&id);
538 if removed.is_some() {
539 self.counters.pending_now.fetch_sub(1, Ordering::Relaxed);
540 }
541 removed
542 }
543
544 pub async fn dispatch_response(&self, response: &aux_rpc::RpcResponse<'_>) {
557 self.counters
558 .fragments_received
559 .fetch_add(1, Ordering::Relaxed);
560 let mut pending = self.pending.lock().await;
561 let Some(mut call) = pending.remove(&response.id) else {
564 return;
565 };
566
567 if call
571 .expected_sender
572 .as_deref()
573 .is_some_and(|expected| response.sender != expected.as_bytes())
574 {
575 self.counters
576 .fragments_wrong_sender
577 .fetch_add(1, Ordering::Relaxed);
578 pending.insert(response.id, call);
579 return;
580 }
581
582 call.response_started = true;
586
587 if call.assembly.is_none() {
588 let bounded =
589 response.total > 0 && response.total as usize <= aux_rpc::MAX_RESPONSE_FRAGMENTS;
590 let seeded = bounded
591 .then(|| aux_rpc::ResponseDecoder::new(response.oti))
592 .flatten();
593 let Some(decoder) = seeded else {
594 self.counters
598 .responses_out_of_range
599 .fetch_add(1, Ordering::Relaxed);
600 self.counters.pending_now.fetch_sub(1, Ordering::Relaxed);
601 let _ = call.sender.send(RpcResponseOwned {
602 status: 502,
603 body: b"relay response geometry out of range".to_vec(),
604 headers: Vec::new(),
605 });
606 return;
607 };
608 call.assembly = Some(Assembly {
609 status: response.status,
610 total: response.total,
611 received: 0,
612 accepted: 0,
613 decoder,
614 });
615 }
616
617 let index = response.index as usize;
618 let bit = 1u64 << (index % 64);
622 let assembly = call.assembly.as_mut().expect("seeded above");
623 let fresh = assembly.total == response.total
627 && index < assembly.total as usize
628 && assembly.accepted & bit == 0;
629 let outcome = fresh.then(|| assembly.decoder.push(response.index, response.body));
630 if matches!(outcome, Some(aux_rpc::FragmentOutcome::Pending)) {
631 assembly.accepted |= bit;
632 assembly.received += 1;
633 }
634 let status = assembly.status;
635
636 match outcome {
637 None => {
638 self.counters
639 .fragments_duplicate
640 .fetch_add(1, Ordering::Relaxed);
641 pending.insert(response.id, call);
642 }
643 Some(aux_rpc::FragmentOutcome::BadSymbol) => {
644 self.counters
645 .fragments_bad_symbol
646 .fetch_add(1, Ordering::Relaxed);
647 pending.insert(response.id, call);
648 }
649 Some(aux_rpc::FragmentOutcome::Pending) => {
650 pending.insert(response.id, call);
651 }
652 Some(aux_rpc::FragmentOutcome::Complete(object)) => {
653 self.counters.pending_now.fetch_sub(1, Ordering::Relaxed);
654 let (status, body, headers) = aux_rpc::unpack_response(status, object);
660 let _ = call.sender.send(RpcResponseOwned {
661 status,
662 body,
663 headers,
664 });
665 }
666 }
667 }
668
669 pub fn stats(&self) -> RpcProxyStats {
671 let c = &self.counters;
672 RpcProxyStats {
673 calls_started: c.calls_started.load(Ordering::Relaxed),
674 calls_ok: c.calls_ok.load(Ordering::Relaxed),
675 calls_timeout: c.calls_timeout.load(Ordering::Relaxed),
676 calls_incomplete: c.calls_incomplete.load(Ordering::Relaxed),
677 calls_send_error: c.calls_send_error.load(Ordering::Relaxed),
678 calls_channel_closed: c.calls_channel_closed.load(Ordering::Relaxed),
679 calls_busy: c.calls_busy.load(Ordering::Relaxed),
680 retransmits: c.retransmits.load(Ordering::Relaxed),
681 retransmit_send_errors: c.retransmit_send_errors.load(Ordering::Relaxed),
682 fragments_received: c.fragments_received.load(Ordering::Relaxed),
683 fragments_duplicate: c.fragments_duplicate.load(Ordering::Relaxed),
684 fragments_wrong_sender: c.fragments_wrong_sender.load(Ordering::Relaxed),
685 fragments_bad_symbol: c.fragments_bad_symbol.load(Ordering::Relaxed),
686 responses_out_of_range: c.responses_out_of_range.load(Ordering::Relaxed),
687 pending_now: c.pending_now.load(Ordering::Relaxed),
688 }
689 }
690
691 pub async fn reset(&self) {
694 let mut pending = self.pending.lock().await;
695 for (_, call) in pending.drain() {
696 let _ = call.sender.send(RpcResponseOwned {
697 status: 503,
698 body: Vec::new(),
699 headers: Vec::new(),
700 });
701 }
702 self.counters.pending_now.store(0, Ordering::Relaxed);
703 }
704
705 pub fn spawn_response_listener(
714 &self,
715 sock_path: impl Into<PathBuf>,
716 cancel: Arc<Notify>,
717 ) -> tokio::task::JoinHandle<()> {
718 let proxy = self.clone();
719 let sock_path = sock_path.into();
720 tokio::spawn(async move {
721 run_response_listener(proxy, sock_path, cancel).await;
722 })
723 }
724}
725
726struct PendingGuard {
736 pending: Arc<Mutex<HashMap<u32, PendingCall>>>,
737 counters: Arc<RpcProxyCounters>,
738 id: u32,
739 armed: bool,
740}
741
742impl PendingGuard {
743 fn disarm(&mut self) {
745 self.armed = false;
746 }
747}
748
749impl Drop for PendingGuard {
750 fn drop(&mut self) {
751 if !self.armed {
752 return;
753 }
754 let pending = Arc::clone(&self.pending);
755 let counters = Arc::clone(&self.counters);
756 let id = self.id;
757 if let Ok(handle) = tokio::runtime::Handle::try_current() {
758 handle.spawn(async move {
759 if pending.lock().await.remove(&id).is_some() {
760 counters.pending_now.fetch_sub(1, Ordering::Relaxed);
761 }
762 });
763 }
764 }
765}
766
767async fn run_response_listener(proxy: AuxRpcProxy, sock_path: PathBuf, cancel: Arc<Notify>) {
770 let _ = std::fs::remove_file(&sock_path);
775
776 let listener = match UnixListener::bind(&sock_path) {
777 Ok(l) => l,
778 Err(e) => {
779 tracing::error!(error = %e, path = %sock_path.display(), "aux_rpc_response_listener_bind_failed");
780 return;
781 }
782 };
783 tracing::info!(path = %sock_path.display(), "aux_rpc_response_listener_listening");
784
785 loop {
786 tokio::select! {
787 biased;
788 _ = cancel.notified() => break,
789 accept_result = listener.accept() => {
790 match accept_result {
791 Ok((stream, _)) => {
792 let proxy = proxy.clone();
793 tokio::spawn(handle_one_connection(stream, proxy));
794 }
795 Err(e) => {
796 tracing::warn!(error = %e, "aux_rpc_response_listener_accept_failed");
797 }
798 }
799 }
800 }
801 }
802 proxy.reset().await;
805 tracing::info!("aux_rpc_response_listener_stopped");
806 let _ = std::fs::remove_file(&sock_path);
807}
808
809async fn handle_one_connection(mut stream: UnixStream, proxy: AuxRpcProxy) {
817 loop {
818 match read_one_frame(&mut stream).await {
819 Ok(payload) => match aux_rpc::decode_response(&payload) {
820 Ok(response) => proxy.dispatch_response(&response).await,
821 Err(e) => {
822 tracing::debug!(error = ?e, len = payload.len(), "aux_rpc_response_undecodable");
823 }
824 },
825 Err(e) => {
826 if e.kind() != std::io::ErrorKind::UnexpectedEof {
827 tracing::debug!(error = %e, "aux_rpc_response_reader_read_failed");
828 }
829 break;
830 }
831 }
832 }
833}
834
835async fn read_one_frame(stream: &mut UnixStream) -> std::io::Result<Vec<u8>> {
837 let mut header = [0u8; HEADER_SIZE];
838 stream.read_exact(&mut header).await?;
839 let len = u32::from_be_bytes(header) as usize;
840 if len > aux_mux::AUX_MAX_PAYLOAD {
841 return Err(std::io::Error::new(
842 std::io::ErrorKind::InvalidData,
843 "response frame exceeds the aux payload maximum",
844 ));
845 }
846 let mut payload = vec![0u8; len];
847 if len > 0 {
848 stream.read_exact(&mut payload).await?;
849 }
850 Ok(payload)
851}
852
853#[derive(Clone)]
857pub struct AuxRpcResponseIngest {
858 sock_path: PathBuf,
859 conn: Arc<Mutex<Option<UnixStream>>>,
860}
861
862impl AuxRpcResponseIngest {
863 pub fn new(sock_path: impl Into<PathBuf>) -> Self {
865 Self {
866 sock_path: sock_path.into(),
867 conn: Arc::new(Mutex::new(None)),
868 }
869 }
870
871 pub async fn send(&self, payload: &[u8]) {
884 let len = payload.len() as u32;
887 let mut frame = Vec::with_capacity(HEADER_SIZE + payload.len());
888 frame.extend_from_slice(&len.to_be_bytes());
889 frame.extend_from_slice(payload);
890 let mut guard = self.conn.lock().await;
891 match guard.as_mut() {
892 Some(stream) => {
893 let wrote = tokio::time::timeout(IPC_WRITE_TIMEOUT, stream.write_all(&frame)).await;
894 if !matches!(wrote, Ok(Ok(()))) {
899 if wrote.is_err() {
900 tracing::warn!(
901 timeout_ms = IPC_WRITE_TIMEOUT.as_millis() as u64,
902 "aux_rpc_ingest_write_timed_out"
903 );
904 }
905 *guard = None;
906 }
907 }
908 None => {
909 match tokio::time::timeout(
910 IPC_CONNECT_TIMEOUT,
911 UnixStream::connect(&self.sock_path),
912 )
913 .await
914 {
915 Ok(Ok(mut stream)) => {
916 let wrote =
917 tokio::time::timeout(IPC_WRITE_TIMEOUT, stream.write_all(&frame)).await;
918 if matches!(wrote, Ok(Ok(()))) {
919 *guard = Some(stream);
920 }
921 }
922 Ok(Err(_)) => {
923 }
925 Err(_) => {
926 tracing::warn!(
927 timeout_ms = IPC_CONNECT_TIMEOUT.as_millis() as u64,
928 "aux_rpc_ingest_connect_timed_out"
929 );
930 }
931 }
932 }
933 }
934 }
935}
936
937#[cfg(test)]
938mod tests {
939 use super::*;
940
941 async fn deaf_listener(path: &std::path::Path) -> tokio::task::JoinHandle<()> {
946 let listener = tokio::net::UnixListener::bind(path).unwrap();
947 tokio::spawn(async move {
948 let mut held = Vec::new();
949 loop {
950 match listener.accept().await {
951 Ok((s, _)) => held.push(s),
953 Err(_) => return,
954 }
955 }
956 })
957 }
958
959 #[tokio::test]
960 async fn a_peer_that_stops_reading_cannot_stall_the_caller() {
961 let dir = tempfile::tempdir().unwrap();
965 let sock = dir.path().join("ingest.sock");
966 let _listener = deaf_listener(&sock).await;
967
968 let ingest = AuxRpcResponseIngest::new(&sock);
969 let big = vec![0u8; 512 * 1024];
970
971 let started = std::time::Instant::now();
973 for _ in 0..8 {
974 ingest.send(&big).await;
975 }
976 let elapsed = started.elapsed();
977 assert!(
978 elapsed < Duration::from_secs(5),
979 "sends took {elapsed:?}; a wedged peer is stalling the caller"
980 );
981 }
982
983 #[tokio::test]
984 async fn an_absent_listener_returns_promptly() {
985 let ingest =
986 AuxRpcResponseIngest::new(std::path::Path::new("/nonexistent/ados/ingest.sock"));
987 let started = std::time::Instant::now();
988 ingest.send(b"payload").await;
989 assert!(started.elapsed() < Duration::from_secs(1));
990 }
991
992 #[test]
993 fn the_ipc_bounds_are_far_below_the_call_bound() {
994 assert!(IPC_WRITE_TIMEOUT < RPC_DEFAULT_TIMEOUT / 10);
997 assert!(IPC_CONNECT_TIMEOUT < RPC_DEFAULT_TIMEOUT / 10);
998 }
999 use crate::aux_mux;
1000 use tokio::net::UdpSocket;
1001 use tokio::time::Duration;
1002
1003 async fn loopback_egress() -> (
1005 AuxEgress,
1006 tokio::sync::mpsc::Receiver<(AuxChannel, Vec<u8>)>,
1007 ) {
1008 let listener = UdpSocket::bind("127.0.0.1:0").await.unwrap();
1009 let port = listener.local_addr().unwrap().port();
1010 let sock = UdpSocket::bind("127.0.0.1:0").await.unwrap();
1011 sock.connect(("127.0.0.1", port)).await.unwrap();
1012 let egress = AuxEgress::connected_for_test(sock);
1013
1014 let (tx, rx) = tokio::sync::mpsc::channel::<(AuxChannel, Vec<u8>)>(8);
1015 tokio::spawn(async move {
1016 let mut buf = [0u8; 2048];
1017 while let Ok((n, _)) = listener.recv_from(&mut buf).await {
1018 if let Ok((ch, payload)) = aux_mux::decode(&buf[..n]) {
1019 let _ = tx.send((ch, payload.to_vec())).await;
1020 }
1021 }
1022 });
1023 (egress, rx)
1024 }
1025
1026 const DRONE: &[u8] = b"0a1b2c3d4e5f";
1028
1029 fn response_payloads(sender: &[u8], id: u32, status: u16, body: &[u8]) -> Vec<Vec<u8>> {
1032 let split = aux_rpc::split_response(body).unwrap();
1033 let total = split.symbols.len() as u16;
1034 split
1035 .symbols
1036 .iter()
1037 .enumerate()
1038 .map(|(index, symbol)| {
1039 aux_rpc::encode_response_fragment(
1040 sender,
1041 id,
1042 status,
1043 index as u16,
1044 total,
1045 split.oti,
1046 symbol,
1047 )
1048 .unwrap()
1049 })
1050 .collect()
1051 }
1052
1053 async fn deliver(proxy: &AuxRpcProxy, payload: &[u8]) {
1056 let fragment = aux_rpc::decode_response(payload).unwrap();
1057 proxy.dispatch_response(&fragment).await;
1058 }
1059
1060 async fn deliver_body(proxy: &AuxRpcProxy, sender: &[u8], id: u32, status: u16, body: &[u8]) {
1062 for payload in response_payloads(sender, id, status, body) {
1063 deliver(proxy, &payload).await;
1064 }
1065 }
1066
1067 async fn pending_id(proxy: &AuxRpcProxy) -> u32 {
1069 let pending = proxy.pending.lock().await;
1070 *pending.keys().next().expect("one pending caller")
1071 }
1072
1073 #[tokio::test]
1074 async fn a_call_round_trips_through_egress_and_a_dispatched_response() {
1075 let (egress, mut sent) = loopback_egress().await;
1076 let proxy = AuxRpcProxy::with_timeout(egress, Duration::from_millis(500));
1077
1078 let proxy_for_consumer = proxy.clone();
1079 let consumer = tokio::spawn(async move {
1080 let (channel, payload) = sent.recv().await.expect("no request datagram");
1081 assert_eq!(channel, AuxChannel::Request);
1082 let fragment = aux_rpc::decode_request_fragment(&payload).unwrap();
1083 assert_eq!(fragment.total, 1, "a small request is one datagram");
1084 let aux_rpc::ReassemblyOutcome::Complete(request) =
1085 aux_rpc::RequestReassembler::new().push(&fragment, std::time::Instant::now())
1086 else {
1087 panic!("a one-fragment request completes on arrival");
1088 };
1089 assert_eq!(request.method, RpcMethod::Get);
1090 assert_eq!(request.target, DRONE);
1091 assert_eq!(request.path, b"/api/pairing/info");
1092 assert!(request.body.is_empty());
1093
1094 let body = br#"{"device_id":"abc"}"#;
1095 deliver_body(&proxy_for_consumer, DRONE, request.id, 200, body).await;
1096 });
1097
1098 let result = proxy
1099 .call(DRONE, RpcMethod::Get, b"/api/pairing/info", &[])
1100 .await
1101 .expect("call must succeed");
1102
1103 consumer.await.unwrap();
1104 assert_eq!(result.status, 200);
1105 assert_eq!(result.body, br#"{"device_id":"abc"}"#);
1106 }
1107
1108 #[tokio::test]
1109 async fn a_multi_fragment_response_completes_on_its_systematic_symbols_alone() {
1110 let (egress, mut sent) = loopback_egress().await;
1111 let proxy = AuxRpcProxy::with_timeout(egress, Duration::from_millis(500));
1112
1113 let body: Vec<u8> = (0..3000u32).map(|i| (i % 251) as u8).collect();
1114 let expected = body.clone();
1115 let proxy_call = proxy.clone();
1116 let call = tokio::spawn(async move {
1117 proxy_call
1118 .call(DRONE, RpcMethod::Get, b"/api/services", &[])
1119 .await
1120 });
1121
1122 let (_, payload) = sent.recv().await.expect("request datagram");
1123 let id = aux_rpc::decode_request_fragment(&payload).unwrap().id;
1124 let fragments = response_payloads(DRONE, id, 200, &body);
1125 assert_eq!(fragments.len(), 3 + aux_rpc::RPC_REPAIR_SYMBOLS as usize);
1126 for fragment in &fragments[..3] {
1130 deliver(&proxy, fragment).await;
1131 }
1132
1133 let result = call.await.unwrap().expect("call must succeed");
1134 assert_eq!(result.status, 200);
1135 assert_eq!(result.body, expected);
1136 }
1137
1138 #[tokio::test]
1139 async fn fragments_delivered_out_of_order_reassemble_identically() {
1140 let (egress, mut sent) = loopback_egress().await;
1141 let proxy = AuxRpcProxy::with_timeout(egress, Duration::from_millis(500));
1142
1143 let body: Vec<u8> = (0..3000u32).map(|i| (i % 251) as u8).collect();
1144 let expected = body.clone();
1145 let proxy_call = proxy.clone();
1146 let call = tokio::spawn(async move {
1147 proxy_call
1148 .call(DRONE, RpcMethod::Get, b"/api/services", &[])
1149 .await
1150 });
1151
1152 let (_, payload) = sent.recv().await.expect("request datagram");
1153 let id = aux_rpc::decode_request_fragment(&payload).unwrap().id;
1154 let mut fragments = response_payloads(DRONE, id, 200, &body);
1155 fragments.reverse();
1156 for fragment in &fragments {
1157 deliver(&proxy, fragment).await;
1158 }
1159
1160 let result = call.await.unwrap().expect("call must succeed");
1161 assert_eq!(result.body, expected);
1162 }
1163
1164 #[tokio::test]
1165 async fn a_thirty_kilobyte_response_completes_after_losing_four_fragments() {
1166 let (egress, mut sent) = loopback_egress().await;
1170 let proxy = AuxRpcProxy::with_timeout(egress, Duration::from_secs(2));
1171
1172 let body: Vec<u8> = (0..29_339u32).map(|i| (i % 251) as u8).collect();
1173 let expected = body.clone();
1174 let proxy_call = proxy.clone();
1175 let call = tokio::spawn(async move {
1176 proxy_call
1177 .call(DRONE, RpcMethod::Get, b"/api/status/full", &[])
1178 .await
1179 });
1180
1181 let (_, payload) = sent.recv().await.expect("request datagram");
1182 let id = aux_rpc::decode_request_fragment(&payload).unwrap().id;
1183 let mut fragments = response_payloads(DRONE, id, 200, &body);
1184 assert_eq!(fragments.len(), 30, "26 systematic + 4 repair");
1185 fragments.rotate_left(7);
1187 fragments.truncate(26);
1188 for fragment in &fragments {
1189 deliver(&proxy, fragment).await;
1190 }
1191
1192 let result = call.await.unwrap().expect("26 of 30 must be enough");
1193 assert_eq!(result.body, expected);
1194 assert_eq!(proxy.stats().calls_ok, 1);
1195 }
1196
1197 #[tokio::test]
1198 async fn a_fragment_from_another_drone_is_dropped_and_counted() {
1199 let (egress, mut sent) = loopback_egress().await;
1204 let proxy = AuxRpcProxy::with_timeout(egress, Duration::from_millis(300));
1205
1206 let proxy_call = proxy.clone();
1207 let call =
1208 tokio::spawn(
1209 async move { proxy_call.call(DRONE, RpcMethod::Get, b"/api/x", &[]).await },
1210 );
1211
1212 let (_, payload) = sent.recv().await.expect("request datagram");
1213 let id = aux_rpc::decode_request_fragment(&payload).unwrap().id;
1214 let imposter = response_payloads(b"ados-elsewhere", id, 200, b"not-your-answer");
1215 for fragment in &imposter {
1216 deliver(&proxy, fragment).await;
1217 }
1218
1219 let err = call.await.unwrap().unwrap_err();
1220 let stats = proxy.stats();
1221 assert_eq!(
1222 err,
1223 RpcError::Timeout,
1224 "an imposter must not complete a call"
1225 );
1226 assert_eq!(stats.fragments_wrong_sender, imposter.len() as u64);
1227 assert_eq!(stats.calls_ok, 0);
1228 assert!(
1229 stats.retransmits > 0,
1230 "another drone's answer proves nothing about ours, so the resend \
1231 loop must keep running"
1232 );
1233 }
1234
1235 #[tokio::test]
1236 async fn the_addressed_drone_still_completes_the_call_after_an_imposter() {
1237 let (egress, mut sent) = loopback_egress().await;
1240 let proxy = AuxRpcProxy::with_timeout(egress, Duration::from_secs(2));
1241
1242 let proxy_call = proxy.clone();
1243 let call =
1244 tokio::spawn(
1245 async move { proxy_call.call(DRONE, RpcMethod::Get, b"/api/x", &[]).await },
1246 );
1247
1248 let (_, payload) = sent.recv().await.expect("request datagram");
1249 let id = aux_rpc::decode_request_fragment(&payload).unwrap().id;
1250 for fragment in &response_payloads(b"ados-elsewhere", id, 200, b"wrong-drone") {
1251 deliver(&proxy, fragment).await;
1252 }
1253 deliver_body(&proxy, DRONE, id, 200, b"right-drone").await;
1254
1255 let result = call.await.unwrap().expect("the addressed drone answered");
1256 assert_eq!(result.body, b"right-drone");
1257 }
1258
1259 #[tokio::test]
1260 async fn a_broadcast_call_accepts_whichever_drone_answers() {
1261 let (egress, mut sent) = loopback_egress().await;
1265 let proxy = AuxRpcProxy::with_timeout(egress, Duration::from_millis(500));
1266
1267 let proxy_call = proxy.clone();
1268 let call =
1269 tokio::spawn(async move { proxy_call.call(&[], RpcMethod::Get, b"/api/x", &[]).await });
1270
1271 let (_, payload) = sent.recv().await.expect("request datagram");
1272 let id = aux_rpc::decode_request_fragment(&payload).unwrap().id;
1273 deliver_body(&proxy, b"ados-anyone", id, 200, b"answered").await;
1274
1275 let result = call.await.unwrap().expect("call must succeed");
1276 assert_eq!(result.body, b"answered");
1277 assert_eq!(proxy.stats().fragments_wrong_sender, 0);
1278 }
1279
1280 #[tokio::test]
1281 async fn a_duplicate_fragment_does_not_double_count() {
1282 let (egress, mut sent) = loopback_egress().await;
1283 let proxy = AuxRpcProxy::with_timeout(egress, Duration::from_millis(400));
1284
1285 let body: Vec<u8> = (0..2000u32).map(|i| (i % 251) as u8).collect();
1286 let expected = body.clone();
1287 let proxy_call = proxy.clone();
1288 let call =
1289 tokio::spawn(
1290 async move { proxy_call.call(DRONE, RpcMethod::Get, b"/api/x", &[]).await },
1291 );
1292
1293 let (_, payload) = sent.recv().await.expect("request datagram");
1294 let id = aux_rpc::decode_request_fragment(&payload).unwrap().id;
1295 let fragments = response_payloads(DRONE, id, 200, &body);
1296 for i in [0usize, 0, 1] {
1299 deliver(&proxy, &fragments[i]).await;
1300 }
1301
1302 let result = call.await.unwrap().expect("call must succeed");
1303 assert_eq!(result.body, expected);
1304 assert_eq!(proxy.stats().fragments_duplicate, 1);
1305 }
1306
1307 #[tokio::test]
1308 async fn a_missing_fragment_yields_incomplete_not_timeout() {
1309 let (egress, mut sent) = loopback_egress().await;
1310 let proxy = AuxRpcProxy::with_timeout(egress, Duration::from_millis(120));
1311
1312 let body: Vec<u8> = (0..3000u32).map(|i| (i % 251) as u8).collect();
1313 let proxy_for_consumer = proxy.clone();
1314 let consumer = tokio::spawn(async move {
1315 let (_, payload) = sent.recv().await.expect("request datagram");
1316 let id = aux_rpc::decode_request_fragment(&payload).unwrap().id;
1317 let fragments = response_payloads(DRONE, id, 200, &body);
1318 deliver(&proxy_for_consumer, &fragments[0]).await;
1319 });
1320
1321 let err = proxy
1322 .call(DRONE, RpcMethod::Get, b"/api/x", &[])
1323 .await
1324 .unwrap_err();
1325 consumer.await.unwrap();
1326 assert_eq!(
1327 err,
1328 RpcError::Incomplete {
1329 received: 1,
1330 total: 7
1331 }
1332 );
1333 assert_eq!(
1334 err.to_string(),
1335 "relay response incomplete: 1/7 fragments",
1336 "the operator must be able to tell a dropped fragment from silence"
1337 );
1338 }
1339
1340 #[tokio::test]
1341 async fn a_fragment_count_past_the_ceiling_completes_with_502() {
1342 let (egress, mut sent) = loopback_egress().await;
1343 let proxy = AuxRpcProxy::with_timeout(egress, Duration::from_millis(400));
1344
1345 let proxy_for_consumer = proxy.clone();
1346 let consumer = tokio::spawn(async move {
1347 let (_, payload) = sent.recv().await.expect("request datagram");
1348 let id = aux_rpc::decode_request_fragment(&payload).unwrap().id;
1349 proxy_for_consumer
1350 .dispatch_response(&aux_rpc::RpcResponse {
1351 id,
1352 sender: DRONE,
1353 status: 200,
1354 index: 0,
1355 total: 65,
1356 oti: 2,
1357 body: b"aa",
1358 })
1359 .await;
1360 });
1361
1362 let result = proxy
1363 .call(DRONE, RpcMethod::Get, b"/api/x", &[])
1364 .await
1365 .expect("the caller must be completed, not left hanging");
1366 consumer.await.unwrap();
1367 assert_eq!(result.status, 502);
1368 assert_eq!(result.body, b"relay response geometry out of range");
1369 assert_eq!(proxy.stats().responses_out_of_range, 1);
1370 }
1371
1372 #[tokio::test]
1373 async fn a_transfer_length_past_the_ceiling_completes_with_502() {
1374 let (egress, mut sent) = loopback_egress().await;
1377 let proxy = AuxRpcProxy::with_timeout(egress, Duration::from_millis(400));
1378
1379 let proxy_for_consumer = proxy.clone();
1380 let consumer = tokio::spawn(async move {
1381 let (_, payload) = sent.recv().await.expect("request datagram");
1382 let id = aux_rpc::decode_request_fragment(&payload).unwrap().id;
1383 proxy_for_consumer
1384 .dispatch_response(&aux_rpc::RpcResponse {
1385 id,
1386 sender: DRONE,
1387 status: 200,
1388 index: 0,
1389 total: 2,
1390 oti: u32::MAX,
1391 body: b"aa",
1392 })
1393 .await;
1394 });
1395
1396 let result = proxy
1397 .call(DRONE, RpcMethod::Get, b"/api/x", &[])
1398 .await
1399 .expect("the caller must be completed, not left hanging");
1400 consumer.await.unwrap();
1401 assert_eq!(result.status, 502);
1402 assert_eq!(proxy.stats().responses_out_of_range, 1);
1403 }
1404
1405 #[tokio::test]
1406 async fn a_symbol_of_the_wrong_size_is_dropped_and_counted() {
1407 let (egress, mut sent) = loopback_egress().await;
1410 let proxy = AuxRpcProxy::with_timeout(egress, Duration::from_millis(200));
1411
1412 let proxy_for_consumer = proxy.clone();
1413 let consumer = tokio::spawn(async move {
1414 let (_, payload) = sent.recv().await.expect("request datagram");
1415 let id = aux_rpc::decode_request_fragment(&payload).unwrap().id;
1416 proxy_for_consumer
1417 .dispatch_response(&aux_rpc::RpcResponse {
1418 id,
1419 sender: DRONE,
1420 status: 200,
1421 index: 0,
1422 total: 5,
1423 oti: 64,
1424 body: b"only-nine",
1425 })
1426 .await;
1427 });
1428
1429 let err = proxy
1430 .call(DRONE, RpcMethod::Get, b"/api/x", &[])
1431 .await
1432 .unwrap_err();
1433 consumer.await.unwrap();
1434 assert_eq!(
1435 err,
1436 RpcError::Incomplete {
1437 received: 0,
1438 total: 5
1439 }
1440 );
1441 assert_eq!(proxy.stats().fragments_bad_symbol, 1);
1442 }
1443
1444 #[tokio::test]
1445 async fn a_call_times_out_when_no_response_arrives() {
1446 let sock = UdpSocket::bind("127.0.0.1:0").await.unwrap();
1447 sock.connect(("127.0.0.1", 9u16)).await.unwrap();
1448 let egress = AuxEgress::connected_for_test(sock);
1449 let proxy = AuxRpcProxy::with_timeout(egress, Duration::from_millis(80));
1450
1451 let started = std::time::Instant::now();
1452 let err = proxy
1453 .call(&[], RpcMethod::Get, b"/api/status", &[])
1454 .await
1455 .unwrap_err();
1456 assert_eq!(err, RpcError::Timeout);
1457 assert!(started.elapsed() < Duration::from_secs(1));
1458 }
1459
1460 #[tokio::test]
1461 async fn a_call_returns_send_error_when_egress_fails() {
1462 let egress =
1463 AuxEgress::with_timeout("/nonexistent/aux-cmd.sock", Duration::from_millis(40));
1464 let proxy = AuxRpcProxy::with_timeout(egress, Duration::from_millis(200));
1465 let err = proxy
1466 .call(&[], RpcMethod::Get, b"/api/status", &[])
1467 .await
1468 .unwrap_err();
1469 assert!(
1470 matches!(err, RpcError::Send(_)),
1471 "expected Send, got {err:?}"
1472 );
1473 }
1474
1475 #[tokio::test]
1476 async fn a_body_past_the_relay_limit_is_refused_before_sending() {
1477 let (egress, _sent) = loopback_egress().await;
1478 let proxy = AuxRpcProxy::with_timeout(egress, Duration::from_millis(200));
1479 let big_body = vec![b'A'; aux_rpc::MAX_REQUEST_BODY + 1];
1480 let err = proxy
1481 .call(&[], RpcMethod::Post, b"/api/x", &big_body)
1482 .await
1483 .unwrap_err();
1484 assert_eq!(err, RpcError::TooLarge);
1485 }
1486
1487 #[tokio::test]
1488 async fn a_body_larger_than_one_frame_goes_out_as_several_fragments() {
1489 let (egress, mut sent) = loopback_egress().await;
1490 let proxy = AuxRpcProxy::with_timeout(egress, Duration::from_millis(500));
1491 let body = vec![b'B'; 4 * aux_mux::AUX_MAX_PAYLOAD];
1492 let expected = body.clone();
1493 let proxy_for_consumer = proxy.clone();
1494 let consumer = tokio::spawn(async move {
1495 let mut reassembler = aux_rpc::RequestReassembler::new();
1496 loop {
1497 let (_, payload) = sent.recv().await.expect("request fragment");
1498 let fragment = aux_rpc::decode_request_fragment(&payload).unwrap();
1499 assert!(fragment.total > 1);
1500 if let aux_rpc::ReassemblyOutcome::Complete(request) =
1501 reassembler.push(&fragment, std::time::Instant::now())
1502 {
1503 assert_eq!(request.method, RpcMethod::Patch);
1504 assert_eq!(request.body, expected);
1505 deliver_body(&proxy_for_consumer, DRONE, request.id, 200, b"ok").await;
1506 return;
1507 }
1508 }
1509 });
1510 let result = proxy
1511 .call(DRONE, RpcMethod::Patch, b"/api/plugins/p/x/upload", &body)
1512 .await
1513 .expect("call must succeed");
1514 consumer.await.unwrap();
1515 assert_eq!(result.status, 200);
1516 }
1517
1518 #[tokio::test]
1519 async fn a_response_for_an_unknown_id_is_a_noop() {
1520 let (egress, _sent) = loopback_egress().await;
1521 let proxy = AuxRpcProxy::with_timeout(egress, Duration::from_millis(50));
1522 for fragment in &response_payloads(DRONE, 9999, 200, b"orphan") {
1523 deliver(&proxy, fragment).await;
1524 }
1525 assert!(proxy.pending.lock().await.is_empty());
1526 }
1527
1528 #[tokio::test]
1529 async fn concurrent_calls_with_distinct_ids_match_their_own_responses() {
1530 let (egress, mut sent) = loopback_egress().await;
1531 let proxy = AuxRpcProxy::with_timeout(egress, Duration::from_millis(500));
1532 let proxy_consumer = proxy.clone();
1533
1534 let consumer = tokio::spawn(async move {
1535 for _ in 0..3 {
1536 let (ch, payload) = sent.recv().await.expect("request datagram");
1537 assert_eq!(ch, AuxChannel::Request);
1538 let request = aux_rpc::decode_request_fragment(&payload).unwrap();
1539 let body = format!("{}", request.id).into_bytes();
1540 deliver_body(&proxy_consumer, DRONE, request.id, 200, &body).await;
1541 }
1542 });
1543
1544 let p1 = tokio::spawn({
1545 let proxy = proxy.clone();
1546 async move { proxy.call(&[], RpcMethod::Get, b"/api/a", &[]).await }
1547 });
1548 let p2 = tokio::spawn({
1549 let proxy = proxy.clone();
1550 async move { proxy.call(&[], RpcMethod::Get, b"/api/b", &[]).await }
1551 });
1552 let p3 = tokio::spawn({
1553 let proxy = proxy.clone();
1554 async move { proxy.call(&[], RpcMethod::Get, b"/api/c", &[]).await }
1555 });
1556
1557 let r1 = p1.await.unwrap().unwrap();
1558 let r2 = p2.await.unwrap().unwrap();
1559 let r3 = p3.await.unwrap().unwrap();
1560 consumer.await.unwrap();
1561
1562 assert_ne!(r1.body, r2.body);
1563 assert_ne!(r2.body, r3.body);
1564 assert_eq!(r1.status, 200);
1565 assert_eq!(r2.status, 200);
1566 assert_eq!(r3.status, 200);
1567 }
1568
1569 #[tokio::test]
1570 async fn reset_drains_pending_callers_with_503() {
1571 let (egress, _sent) = loopback_egress().await;
1572 let proxy = AuxRpcProxy::with_timeout(egress, Duration::from_secs(60));
1573
1574 let proxy_call = proxy.clone();
1575 let call_handle =
1576 tokio::spawn(async move { proxy_call.call(&[], RpcMethod::Get, b"/api/x", &[]).await });
1577
1578 tokio::time::sleep(Duration::from_millis(20)).await;
1579 proxy.reset().await;
1580 let result = tokio::time::timeout(Duration::from_millis(500), call_handle)
1581 .await
1582 .expect("reset must unblock the caller within the bound")
1583 .unwrap();
1584 assert_eq!(result.unwrap().status, 503);
1585 }
1586
1587 #[tokio::test]
1588 async fn the_ingest_writes_and_the_listener_dispatches() {
1589 let dir = tempfile::tempdir().unwrap();
1594 let sock_path = dir.path().join("aux-rpc-responses.sock");
1595
1596 let sock = UdpSocket::bind("127.0.0.1:0").await.unwrap();
1599 sock.connect(("127.0.0.1", 9u16)).await.unwrap();
1600 let egress = AuxEgress::connected_for_test(sock);
1601 let proxy = AuxRpcProxy::with_timeout(egress, Duration::from_millis(500));
1602
1603 let cancel = Arc::new(Notify::new());
1605 let listener_handle = proxy.spawn_response_listener(sock_path.clone(), cancel.clone());
1606
1607 tokio::time::sleep(Duration::from_millis(20)).await;
1609
1610 let proxy_call = proxy.clone();
1612 let call_handle = tokio::spawn(async move {
1613 proxy_call
1614 .call(&[], RpcMethod::Get, b"/api/pairing/info", &[])
1615 .await
1616 });
1617
1618 tokio::time::sleep(Duration::from_millis(20)).await;
1620
1621 let id = pending_id(&proxy).await;
1625
1626 let ingest = AuxRpcResponseIngest::new(&sock_path);
1627 for payload in response_payloads(DRONE, id, 200, br#"{"ok":true}"#) {
1628 ingest.send(&payload).await;
1629 }
1630
1631 let result = tokio::time::timeout(Duration::from_secs(2), call_handle)
1632 .await
1633 .expect("the call must complete within the bound")
1634 .unwrap();
1635 assert_eq!(result.unwrap().status, 200);
1636
1637 cancel.notify_waiters();
1638 let _ = listener_handle.await;
1639 }
1640
1641 #[tokio::test]
1642 async fn an_undecodable_frame_does_not_poison_the_fragments_after_it() {
1643 let dir = tempfile::tempdir().unwrap();
1646 let sock_path = dir.path().join("aux-rpc-responses.sock");
1647
1648 let sock = UdpSocket::bind("127.0.0.1:0").await.unwrap();
1649 sock.connect(("127.0.0.1", 9u16)).await.unwrap();
1650 let egress = AuxEgress::connected_for_test(sock);
1651 let proxy = AuxRpcProxy::with_timeout(egress, Duration::from_secs(2));
1652
1653 let cancel = Arc::new(Notify::new());
1654 let listener_handle = proxy.spawn_response_listener(sock_path.clone(), cancel.clone());
1655 tokio::time::sleep(Duration::from_millis(20)).await;
1656
1657 let proxy_call = proxy.clone();
1658 let call_handle =
1659 tokio::spawn(async move { proxy_call.call(&[], RpcMethod::Get, b"/api/x", &[]).await });
1660 tokio::time::sleep(Duration::from_millis(20)).await;
1661
1662 let id = pending_id(&proxy).await;
1663
1664 let body: Vec<u8> = (0..2000u32).map(|i| (i % 251) as u8).collect();
1665 let fragments = response_payloads(DRONE, id, 200, &body);
1666 let ingest = AuxRpcResponseIngest::new(&sock_path);
1667 ingest.send(&fragments[0]).await;
1668 ingest.send(b"not-a-fragment").await;
1669 ingest.send(&fragments[1]).await;
1670
1671 let result = tokio::time::timeout(Duration::from_secs(3), call_handle)
1672 .await
1673 .expect("the call must complete within the bound")
1674 .unwrap();
1675 assert_eq!(result.unwrap().body, body);
1676
1677 cancel.notify_waiters();
1678 let _ = listener_handle.await;
1679 }
1680
1681 #[tokio::test]
1682 async fn a_lost_request_datagram_is_retransmitted_until_the_drone_answers() {
1683 let (egress, mut sent) = loopback_egress().await;
1684 let proxy = AuxRpcProxy::with_timeout(egress, Duration::from_secs(3));
1685
1686 let proxy_for_consumer = proxy.clone();
1687 let consumer = tokio::spawn(async move {
1688 let (_, first) = sent.recv().await.expect("first attempt");
1691 let (_, second) = sent.recv().await.expect("first retransmit");
1692 let (_, third) = sent.recv().await.expect("second retransmit");
1693 assert_eq!(first, second, "a retransmit must be the identical datagram");
1694 assert_eq!(second, third);
1695 let request = aux_rpc::decode_request_fragment(&third).unwrap();
1696 deliver_body(
1697 &proxy_for_consumer,
1698 DRONE,
1699 request.id,
1700 200,
1701 b"late-but-answered",
1702 )
1703 .await;
1704 });
1705
1706 let result = proxy
1707 .call(DRONE, RpcMethod::Get, b"/api/version", &[])
1708 .await
1709 .expect("a retransmitted request must still complete");
1710 consumer.await.unwrap();
1711 assert_eq!(result.body, b"late-but-answered");
1712 assert!(
1713 proxy.stats().retransmits >= 2,
1714 "one datagram on a 20-40% loss lane is a coin flip; it must be resent"
1715 );
1716 }
1717
1718 #[tokio::test]
1719 async fn a_retransmit_that_could_not_be_sent_is_not_counted_as_one_that_was() {
1720 let sock = tokio::net::UdpSocket::bind("127.0.0.1:0").await.unwrap();
1730 let dead = tokio::net::UdpSocket::bind("127.0.0.1:0").await.unwrap();
1731 let dead_addr = dead.local_addr().unwrap();
1732 drop(dead);
1733 sock.connect(dead_addr).await.unwrap();
1734 let egress = AuxEgress::connected_for_test(sock);
1735 let proxy = AuxRpcProxy::new(egress);
1736
1737 let r = tokio::time::timeout(
1738 RPC_DEFAULT_TIMEOUT + Duration::from_secs(2),
1739 proxy.call(&[], RpcMethod::Get, b"/api/pairing/info", &[]),
1740 )
1741 .await;
1742 assert!(r.is_ok(), "the call never terminated");
1743
1744 let stats = proxy.stats();
1745 assert!(
1746 stats.retransmit_send_errors > 0,
1747 "a broken egress must be visible as a send failure, not as silence \
1748 from the far end: {stats:?}"
1749 );
1750 }
1751
1752 #[tokio::test]
1753 async fn a_response_that_has_started_arriving_stops_the_retransmits() {
1754 let (egress, mut sent) = loopback_egress().await;
1755 let proxy = AuxRpcProxy::with_timeout(egress, Duration::from_millis(600));
1756
1757 let body: Vec<u8> = (0..3000u32).map(|i| (i % 251) as u8).collect();
1758 let proxy_for_consumer = proxy.clone();
1759 let consumer = tokio::spawn(async move {
1760 let (_, payload) = sent.recv().await.expect("request datagram");
1761 let id = aux_rpc::decode_request_fragment(&payload).unwrap().id;
1762 let fragments = response_payloads(DRONE, id, 200, &body);
1766 deliver(&proxy_for_consumer, &fragments[0]).await;
1767 tokio::time::sleep(Duration::from_millis(450)).await;
1768 sent.try_recv().is_ok()
1769 });
1770
1771 let err = proxy
1772 .call(DRONE, RpcMethod::Get, b"/api/x", &[])
1773 .await
1774 .unwrap_err();
1775 let resent = consumer.await.unwrap();
1776 assert_eq!(
1777 err,
1778 RpcError::Incomplete {
1779 received: 1,
1780 total: 7
1781 }
1782 );
1783 assert!(!resent, "no second Request datagram may leave the ground");
1784 assert_eq!(proxy.stats().retransmits, 0);
1785 }
1786
1787 #[tokio::test]
1788 async fn a_saturated_proxy_sheds_load_instead_of_queueing_radio_work() {
1789 let (egress, _sent) = loopback_egress().await;
1790 let proxy = AuxRpcProxy::with_timeout(egress, Duration::from_millis(200));
1791 {
1792 let mut pending = proxy.pending.lock().await;
1793 for id in 0..MAX_PENDING_CALLS as u32 {
1794 let (tx, _rx) = oneshot::channel::<RpcResponseOwned>();
1795 pending.insert(
1796 id,
1797 PendingCall {
1798 sender: tx,
1799 expected_sender: None,
1800 assembly: None,
1801 response_started: false,
1802 },
1803 );
1804 }
1805 }
1806
1807 let err = proxy
1808 .call(&[], RpcMethod::Get, b"/api/x", &[])
1809 .await
1810 .unwrap_err();
1811 assert_eq!(err, RpcError::Busy);
1812 assert_eq!(err.to_string(), "relay proxy has too many calls in flight");
1813 assert_eq!(proxy.stats().calls_busy, 1);
1814 }
1815
1816 #[tokio::test]
1817 async fn a_cancelled_call_does_not_leak_its_pending_entry() {
1818 let sock = UdpSocket::bind("127.0.0.1:0").await.unwrap();
1821 sock.connect(("127.0.0.1", 9u16)).await.unwrap();
1822 let egress = AuxEgress::connected_for_test(sock);
1823 let proxy = AuxRpcProxy::with_timeout(egress, Duration::from_secs(60));
1824
1825 let proxy_call = proxy.clone();
1826 let handle =
1827 tokio::spawn(async move { proxy_call.call(&[], RpcMethod::Get, b"/api/x", &[]).await });
1828 tokio::time::sleep(Duration::from_millis(50)).await;
1829 assert_eq!(proxy.pending.lock().await.len(), 1);
1830
1831 handle.abort();
1832 tokio::time::sleep(Duration::from_millis(80)).await;
1835 assert!(
1836 proxy.pending.lock().await.is_empty(),
1837 "a dropped call future must not leak its sender and assembly forever"
1838 );
1839 assert_eq!(proxy.stats().pending_now, 0);
1840 }
1841
1842 #[tokio::test]
1843 async fn the_request_id_seed_is_not_a_fixed_one() {
1844 let (egress_a, _a) = loopback_egress().await;
1848 let (egress_b, _b) = loopback_egress().await;
1849 let a = AuxRpcProxy::with_timeout(egress_a, Duration::from_millis(50));
1850 let b = AuxRpcProxy::with_timeout(egress_b, Duration::from_millis(50));
1851 assert_ne!(a.next_id.load(Ordering::Relaxed), 1);
1852 assert_ne!(b.next_id.load(Ordering::Relaxed), 1);
1853 }
1854}