dolang_rpc/server.rs
1//! The handling side of a bound RPC session.
2//!
3//! [`Server::serve`] dispatches each incoming request to a handler
4//! concurrently with the others, passing it a [`CallContext`] used to send
5//! the response.
6
7#[cfg(windows)]
8use std::{any::TypeId, io, os::windows::io::OwnedHandle};
9use std::{
10 collections::HashMap,
11 marker::PhantomData,
12 sync::{Arc, Mutex},
13};
14
15use futures::{
16 StreamExt,
17 future::{AbortHandle, Abortable},
18 stream::FuturesUnordered,
19};
20use tokio::sync::{mpsc, oneshot};
21
22use crate::{
23 Error, Limits, Protocol,
24 driver::{Drain, DrainSignal, DrainWatch, drain_signal},
25 fragment::{self, Event, Kind, Message},
26 serde::{decode_payload, encode_payload},
27 session::{self, Cite, Gift, InvalidOpaque, OpaqueGuard, OpaqueResource, Session},
28 trailer::{RecvShared, SendShared, TrailerRecv, TrailerSend},
29 transport::{self, EncodeHandles, Receiver, Sender},
30 window::{ControlSink, PayloadBudget, PayloadCharge, SessionWindow},
31};
32#[cfg(windows)]
33use crate::{handle::TakeHandle, session::Inner as OpaqueInner};
34
35#[cfg(windows)]
36struct DecodeHandles<'a> {
37 receiver: &'a transport::AnyReceiver,
38 session: &'a Arc<Session>,
39 count: usize,
40 max_handles: usize,
41}
42
43#[cfg(windows)]
44impl TakeHandle for DecodeHandles<'_> {
45 fn take_handle(&mut self, value: usize) -> io::Result<OwnedHandle> {
46 if self.count == self.max_handles {
47 return Err(io::Error::new(
48 io::ErrorKind::InvalidData,
49 "message contains too many handle attachments",
50 ));
51 }
52 self.count += 1;
53 self.receiver.duplicate_peer_handle(value)
54 }
55
56 fn take_gift(&mut self, owner: u8, id: u64) -> io::Result<OpaqueInner> {
57 self.session
58 .take_gift(owner, id)
59 .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "invalid opaque reference"))
60 }
61
62 fn take_cite(&mut self, owner: u8, id: u64, marker: TypeId) -> io::Result<OpaqueInner> {
63 self.session
64 .take_cite(owner, id, marker)
65 .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "invalid opaque reference"))
66 }
67}
68
69/// A negotiated server endpoint that has not yet been bound to a [`Protocol`].
70///
71/// Inspect its negotiated application protocol, then consume it with
72/// [`bind`](Unbound::bind) to obtain a [`Server`].
73pub use crate::unbound::UnboundServer as Unbound;
74
75/// A server endpoint for one connection.
76///
77/// Consume it with [`serve`](Self::serve) to dispatch requests from the peer.
78pub struct Server<P: Protocol> {
79 sender: transport::AnySender,
80 receiver: transport::AnyReceiver,
81 outgoing: mpsc::UnboundedSender<Outgoing<P::Response>>,
82 outgoing_rx: mpsc::UnboundedReceiver<Outgoing<P::Response>>,
83 /// The other end of `Inner::shutdown`, held until `serve` hands it to
84 /// the receive driver.
85 shutdown_rx: oneshot::Receiver<()>,
86 /// Held until `serve` hands the two ends to the two drivers.
87 drain: (DrainSignal, DrainWatch),
88 shared: Arc<Shared>,
89 marker: PhantomData<fn() -> P>,
90}
91
92enum Outgoing<R> {
93 Response {
94 id: u64,
95 value: R,
96 trailer: fragment::Trailer,
97 },
98 Error {
99 id: u64,
100 },
101 Cancel {
102 id: u64,
103 },
104 /// We stopped reading a request trailer (it arrived unwanted) and want
105 /// to tell the peer to stop sending it. Always results in a wire
106 /// `Kind::Discard` fragment.
107 DiscardTrailer {
108 id: u64,
109 },
110 /// A wire `Kind::Discard` fragment arrived, telling us the peer no
111 /// longer wants our response trailer. Applied to our own active send;
112 /// never re-sent to the peer.
113 PeerDiscarded {
114 id: u64,
115 },
116 Ack {
117 id: u64,
118 },
119 /// We retired `count` bytes of the request trailer on `id` and are
120 /// returning that much credit. Always results in a wire `Kind::Credit`.
121 Credit {
122 id: u64,
123 count: u32,
124 },
125 /// A call released its request payload and is returning `count` bytes of
126 /// quota. Always results in a wire `Kind::PayloadCredit`, which names no
127 /// message: several of these coalesce into one fragment.
128 PayloadCredit {
129 count: u32,
130 },
131 /// Drops `count` of this endpoint's references to the peer's opaque `id`.
132 Release {
133 id: u64,
134 count: u32,
135 },
136}
137
138/// Emits `Release` frames for opaques whose last local handle dropped. The
139/// strong senders stay exactly what they were: `serve`'s own sender and each
140/// live `CallContext`.
141impl<R: Send + 'static> session::ReleaseSink for mpsc::WeakUnboundedSender<Outgoing<R>> {
142 fn release(&self, id: u64, count: u32) {
143 // Called from `Drop`, so a departed channel is not an error: the
144 // writer is already gone and the peer's table dies with the session.
145 if let Some(outgoing) = self.upgrade() {
146 let _ = outgoing.send(Outgoing::Release { id, count });
147 }
148 }
149}
150
151impl<R: Send + 'static> ControlSink for mpsc::WeakUnboundedSender<Outgoing<R>> {
152 fn credit(&self, id: u64, count: u32) {
153 if let Some(outgoing) = self.upgrade() {
154 let _ = outgoing.send(Outgoing::Credit { id, count });
155 }
156 }
157
158 fn payload_credit(&self, count: u32) {
159 // Reached from `PayloadCharge::drop`, which runs on every path a call
160 // can end on — including ones where the session is already tearing
161 // down, where there is no one left to credit.
162 if let Some(outgoing) = self.upgrade() {
163 let _ = outgoing.send(Outgoing::PayloadCredit { count });
164 }
165 }
166
167 fn discard(&self, id: u64) {
168 // Reached from `TrailerRecv::drop`, so a departed channel just means
169 // the connection is already gone and the peer has nothing to stop.
170 if let Some(outgoing) = self.upgrade() {
171 let _ = outgoing.send(Outgoing::DiscardTrailer { id });
172 }
173 }
174}
175
176/// State both connection drivers and every live handler share.
177///
178/// One `Arc` rather than a bundle of them, and held strongly by all three:
179/// nothing in here can keep the session alive on its own, because the ability
180/// to still get a message out is the `outgoing` sender, which stays outside
181/// it. Closing that channel is still what shuts the writer down.
182struct Shared {
183 inner: Mutex<Inner>,
184 session: Arc<Session>,
185 /// Send-side trailer credit shared by every outgoing response trailer on
186 /// this connection. Bounds what the peer must buffer for us in aggregate.
187 trailer_session: Arc<SessionWindow>,
188 /// Send-side payload quota shared by every outgoing response. Bounds the
189 /// postcard bytes the peer must hold for us across all live calls, and is
190 /// charged in full when a response is admitted to the scheduler.
191 ///
192 /// Kept apart from `trailer_session` on purpose; see [`crate::window`].
193 payload_budget: Arc<PayloadBudget>,
194 limits: Limits,
195}
196
197impl Shared {
198 /// Maximum handle attachments one message may carry.
199 fn max_handles(&self) -> usize {
200 // A transport configured to attach no handles to a fragment can
201 // carry none at all.
202 #[cfg(unix)]
203 if self.limits.max_handles_per_fragment == 0 {
204 return 0;
205 }
206 self.limits.max_handles_per_message
207 }
208
209 /// Finishes handle encoding for message `id`, taking custody of whatever
210 /// this platform must keep alive once the message is on the wire.
211 ///
212 /// On macOS that is the file descriptors themselves, escrowed until the
213 /// peer acknowledges receipt. Every other unix passes them with the
214 /// fragment and is done with them.
215 #[cfg(unix)]
216 fn finish_handles(&self, id: u64, handles: EncodeHandles) -> transport::OutgoingHandles {
217 let handles = handles.finish();
218 #[cfg(target_os = "macos")]
219 if handles.needs_ack() {
220 self.inner.lock().unwrap().fd_escrow.register(id);
221 }
222 #[cfg(not(target_os = "macos"))]
223 let _ = id;
224 handles
225 }
226
227 /// Finishes handle encoding for message `_id`. Windows duplicates each
228 /// handle into the peer as it is encoded, so the originals are this
229 /// end's to close and nothing is escrowed.
230 #[cfg(windows)]
231 fn finish_handles(&self, _id: u64, handles: EncodeHandles) -> transport::OutgoingHandles {
232 let (handles, escrow) = handles.finish();
233 drop(escrow);
234 handles
235 }
236
237 /// Decodes a message payload, taking custody of every handle and opaque
238 /// reference it carries.
239 #[cfg(unix)]
240 fn decode<T: ::serde::de::DeserializeOwned>(
241 &self,
242 payload: &[u8],
243 handles: transport::ReceivedHandles,
244 _receiver: &transport::AnyReceiver,
245 ) -> Result<T, Error> {
246 decode_payload(
247 payload,
248 &mut session::SessionHandles {
249 inner: handles,
250 session: &self.session,
251 },
252 )
253 }
254
255 /// Decodes a message payload, taking custody of every handle and opaque
256 /// reference it carries. Windows handles are named by value in the
257 /// payload and duplicated out of the peer as they are decoded, rather
258 /// than arriving attached to the fragment, so `handles` is empty.
259 #[cfg(windows)]
260 fn decode<T: ::serde::de::DeserializeOwned>(
261 &self,
262 payload: &[u8],
263 _handles: transport::ReceivedHandles,
264 receiver: &transport::AnyReceiver,
265 ) -> Result<T, Error> {
266 decode_payload(
267 payload,
268 &mut DecodeHandles {
269 receiver,
270 session: &self.session,
271 count: 0,
272 max_handles: self.max_handles(),
273 },
274 )
275 }
276
277 /// Records the file descriptors for `id` that just reached the wire.
278 #[cfg(target_os = "macos")]
279 fn escrow_sent(&self, id: u64, fds: Vec<std::os::fd::OwnedFd>, done: bool) {
280 self.inner.lock().unwrap().fd_escrow.sent(id, fds, done);
281 }
282
283 /// Forgets the escrow for a message that will never reach the wire.
284 fn discard_unsent_escrow(&self, id: u64) {
285 #[cfg(target_os = "macos")]
286 self.inner.lock().unwrap().fd_escrow.discard_unsent(id);
287 #[cfg(not(target_os = "macos"))]
288 let _ = id;
289 }
290
291 /// Releases the escrow an `Ack` names, returning false when there is
292 /// none — which is every `Ack` on a platform that escrows nothing.
293 fn release_escrow(&self, id: u64) -> bool {
294 #[cfg(target_os = "macos")]
295 return self.inner.lock().unwrap().fd_escrow.release(id);
296 #[cfg(not(target_os = "macos"))]
297 {
298 let _ = id;
299 false
300 }
301 }
302}
303
304struct Inner {
305 outstanding: HashMap<u64, Cancellation>,
306 /// Signals the receive driver to stop accepting new work. Taken by the
307 /// first handler to ask for shutdown, so later ones are no-ops.
308 shutdown: Option<oneshot::Sender<()>>,
309 #[cfg(target_os = "macos")]
310 fd_escrow: crate::escrow::FdEscrow,
311}
312
313struct Cancellation {
314 signal: Option<oneshot::Sender<()>>,
315 abort: AbortHandle,
316}
317
318/// Refuses a fragment the peer had no business sending, before the
319/// reassembler can allocate anything for it.
320///
321/// Unlike the client's gate, this needs no id: a client never asks this end
322/// for anything, so a response in this direction names nothing at all.
323fn check_header(header: &fragment::FragmentHeader) -> Result<(), Error> {
324 if header.kind == Kind::Response {
325 return Err(Error::Protocol(
326 "server received a Response fragment".into(),
327 ));
328 }
329 Ok(())
330}
331
332impl<P: Protocol> Server<P> {
333 /// Builds a `Server` from an already-negotiated transport. Only reachable
334 /// via [`Unbound::bind`] — `Server` has
335 /// no public constructors of its own, so it's never possible to hold one
336 /// that hasn't already completed `fragment::negotiate`, and `serve`
337 /// never needs to negotiate itself.
338 pub(crate) fn from_transport(
339 sender: transport::AnySender,
340 receiver: transport::AnyReceiver,
341 limits: Limits,
342 ) -> Self {
343 let (outgoing, outgoing_rx) = mpsc::unbounded_channel();
344 let (shutdown, shutdown_rx) = oneshot::channel();
345 Self {
346 sender,
347 receiver,
348 shared: Arc::new(Shared {
349 inner: Mutex::new(Inner {
350 outstanding: HashMap::new(),
351 shutdown: Some(shutdown),
352 #[cfg(target_os = "macos")]
353 fd_escrow: Default::default(),
354 }),
355 session: Session::new(Box::new(outgoing.downgrade())),
356 trailer_session: Arc::new(SessionWindow::new(limits.trailer_session_window)),
357 payload_budget: Arc::new(PayloadBudget::new(limits.max_outstanding_payload)),
358 limits,
359 }),
360 outgoing,
361 outgoing_rx,
362 shutdown_rx,
363 drain: drain_signal(),
364 marker: PhantomData,
365 }
366 }
367
368 /// Serves requests until the peer disconnects, the session fails, or a
369 /// handler requests graceful shutdown.
370 ///
371 /// The handler may be called concurrently for independent requests. Each
372 /// invocation must consume its [`CallContext`] with [`CallContext::respond`]
373 /// or [`CallContext::respond_with_trailer`]; dropping the context without
374 /// responding reports a per-request error to the peer.
375 pub async fn serve<H>(self, handler: H) -> Result<(), Error>
376 where
377 H: AsyncFn(CallContext<P>, P::Request) + Send + Sync + 'static,
378 {
379 let (drain_signal, drain_watch) = self.drain;
380 let send = SendDriver::<P>::new(
381 self.sender,
382 self.outgoing_rx,
383 self.shared.clone(),
384 drain_watch,
385 )
386 .run();
387 tokio::pin!(send);
388 let recv = RecvDriver::new(
389 self.receiver,
390 self.outgoing,
391 self.shutdown_rx,
392 self.shared,
393 handler,
394 drain_signal,
395 )
396 .run();
397 tokio::pin!(recv);
398 let result = tokio::select! {
399 result = &mut recv => result,
400 // A successful send-side exit is not the end of the connection.
401 // In practice graceful mode keeps the driver alive until the
402 // receiver ends, so this only covers the ordinary channel-close
403 // path. A send failure is fatal immediately because no further
404 // receive-side progress can repair it.
405 result = &mut send => {
406 result?;
407 return match recv.await {
408 Err(Error::ConnectionClosed) => Ok(()),
409 result => result,
410 };
411 }
412 };
413 // The receive half ended first, so the session is failing or the peer
414 // is gone. It published `Drain::Abrupt` on the way out; the send
415 // driver flushes what it had already committed to the wire and
416 // abandons anything still waiting on credit that can no longer
417 // arrive. Dropping `recv` above also dropped its sender, so nothing
418 // new can be queued behind it either.
419 result.and(send.await)
420 }
421}
422
423/// Drives the receive half of the connection: reassembles inbound fragments,
424/// dispatches requests to the handler, and owns the tear-down that follows
425/// whatever ends the session.
426///
427/// This is the half that runs on the caller's own future rather than a task
428/// of its own, because it is what [`Server::serve`] returns the result of.
429struct RecvDriver<P: Protocol, H> {
430 transport: transport::AnyReceiver,
431 reassembler: fragment::Reassembler,
432 shared: Arc<Shared>,
433 /// Kept alive for the whole run: dropping this and every handler task's
434 /// clone is what closes the send driver's channel and shuts it down.
435 outgoing: mpsc::UnboundedSender<Outgoing<P::Response>>,
436 handler: Arc<H>,
437 /// Fires when a handler has asked to shut down gracefully.
438 shutdown: oneshot::Receiver<()>,
439 /// Tells the send driver how much it still owes; see [`crate::driver`].
440 drain: DrainSignal,
441}
442
443impl<P: Protocol, H> RecvDriver<P, H>
444where
445 H: AsyncFn(CallContext<P>, P::Request) + Send + Sync + 'static,
446{
447 fn new(
448 transport: transport::AnyReceiver,
449 outgoing: mpsc::UnboundedSender<Outgoing<P::Response>>,
450 shutdown: oneshot::Receiver<()>,
451 shared: Arc<Shared>,
452 handler: H,
453 drain: DrainSignal,
454 ) -> Self {
455 let reassembler = fragment::Reassembler::new(shared.limits, Arc::new(outgoing.downgrade()));
456 Self {
457 transport,
458 reassembler,
459 shared,
460 outgoing,
461 handler: Arc::new(handler),
462 shutdown,
463 drain,
464 }
465 }
466
467 /// Applies `max_concurrent_calls` to a call that is arriving or starting
468 /// to arrive.
469 ///
470 /// A concurrent call is one this end has begun receiving and has not yet
471 /// answered, and it passes through two custodians on the way: the
472 /// reassembler holds it while its payload is still fragmented, and
473 /// `outstanding` holds it from dispatch until the response head. The
474 /// limit is on the *sum* — the two counts are disjoint, since a message
475 /// leaves payload phase in the same `accept` call that dispatches it — so
476 /// neither custodian can enforce it alone, and checking them separately
477 /// would admit twice the limit.
478 ///
479 /// `incomplete` is the reassembler's count *including* the call being
480 /// admitted, so callers add one for a call that has already left payload
481 /// phase.
482 fn check_call_admission(&self, id: u64, incomplete: usize) -> Result<(), Error> {
483 let inner = self.shared.inner.lock().unwrap();
484 let duplicate = inner.outstanding.contains_key(&id);
485 let outstanding = inner.outstanding.len();
486 if duplicate {
487 return Err(Error::Protocol(format!("duplicate active request id {id}")));
488 }
489 if outstanding + incomplete > self.shared.limits.max_concurrent_calls {
490 return Err(Error::Protocol("too many concurrent calls".into()));
491 }
492 Ok(())
493 }
494
495 /// Runs until the peer disconnects or the session fails.
496 ///
497 /// A handler asking to shut down does *not* end this — it starts a
498 /// drain. New requests are refused from that point, but the transport
499 /// keeps being read, because the calls already dispatched still have to
500 /// answer and the flow-control credit their responses may be waiting on
501 /// arrives through this half. Once those handlers have finished, this
502 /// driver publishes [`Drain::Graceful`] and keeps reading until the peer
503 /// closes its transport, including after the send driver has emptied its
504 /// scheduler.
505 ///
506 /// Beyond publishing that signal it knows nothing of the send driver.
507 async fn run(mut self) -> Result<(), Error> {
508 let mut tasks = FuturesUnordered::new();
509 // Set when a handler has asked to shut down. From then on the
510 // `shutdown` branch is disarmed (a consumed `oneshot` resolves
511 // immediately and would spin the loop) and new requests are refused.
512 let mut draining = false;
513 let result = 'main: loop {
514 let mut frame = self.transport.recv();
515 // The header/payload reads must not be dropped and restarted
516 // once they've begun: any bytes already consumed from the
517 // transport into their local buffers would otherwise be lost,
518 // desynchronizing the stream. `step` is polled repeatedly by
519 // the inner loop below (never recreated) so that racing it
520 // against `tasks.next()` and `continue`-ing loses no progress.
521 let complete = {
522 let step = async {
523 let header = fragment::read_fragment_header(&mut frame).await?;
524 check_header(&header)?;
525 self.reassembler.accept(header, &mut frame).await
526 };
527 tokio::pin!(step);
528 loop {
529 tokio::select! {
530 result = &mut step => break result,
531 // Handler tasks must keep being polled here: a
532 // handler reading a request trailer is unblocked by
533 // the very fragment this read is fetching. Their
534 // completions are also the trigger for sealing the
535 // drain, since a drain ends when the last dispatched
536 // call has answered.
537 Some(_) = tasks.next(), if !tasks.is_empty() => {
538 self.drain.seal_if_idle(draining, tasks.is_empty());
539 continue;
540 }
541 _ = &mut self.shutdown, if !draining => {
542 draining = true;
543 self.drain.seal_if_idle(draining, tasks.is_empty());
544 continue;
545 }
546 }
547 }
548 };
549 let complete = match complete {
550 Ok(complete) => complete,
551 Err(error) => break 'main Err(error),
552 };
553 let (message, live_trailer) = match complete {
554 Event::None => (None, None),
555 // A request has started arriving. It occupies the same
556 // budget as one already dispatched, so it is admitted on the
557 // same rule, at the earliest point this end knows about it.
558 Event::PayloadIncomplete { id } => {
559 if let Err(error) =
560 self.check_call_admission(id, self.reassembler.payload_incomplete())
561 {
562 break 'main Err(error);
563 }
564 (None, None)
565 }
566 Event::Aborted {
567 kind: Kind::Request,
568 ..
569 } => (None, None),
570 Event::Aborted { kind, .. } => {
571 break 'main Err(Error::Protocol(format!(
572 "unexpected aborted {kind:?} message"
573 )));
574 }
575 Event::Message(message) => (Some(message), None),
576 Event::Ack { id, message } => {
577 let _ = self.outgoing.send(Outgoing::Ack { id });
578 (message, None)
579 }
580 Event::Trailer {
581 shared: trailer,
582 len,
583 ..
584 } => (None, Some((trailer, len))),
585 Event::Release { id, count } => {
586 self.shared.session.release(id, count);
587 (None, None)
588 }
589 Event::Credit { id, count } => {
590 // Applied here rather than routed through the writer;
591 // see the client's matching arm.
592 self.shared.trailer_session.refund(id, count as usize);
593 (None, None)
594 }
595 Event::PayloadCredit { count } => {
596 self.shared.payload_budget.credit(count as usize);
597 (None, None)
598 }
599 };
600 if let Some(Message {
601 kind,
602 id,
603 payload,
604 handles,
605 trailer,
606 charge,
607 }) = message
608 {
609 match kind {
610 Kind::Request if draining => {
611 // A drain finishes the calls already dispatched; it
612 // does not take on new ones. Refusing here rather
613 // than letting the reassembler reject it keeps the
614 // decision in one place, and dropping `charge`
615 // returns the request's payload quota to the peer —
616 // this end is still reading, so that credit is still
617 // worth sending.
618 //
619 // The trailer is wrapped before being dropped rather
620 // than dropped as it arrived: `TrailerRecv`'s `Drop`
621 // is what tells the peer to stop sending, and a
622 // refused request that left its trailer streaming
623 // would go on consuming the drain it is not part of.
624 let _ = self.outgoing.send(Outgoing::Error { id });
625 drop(trailer.map(TrailerRecv::new));
626 drop(charge);
627 }
628 Kind::Request => {
629 // This message has already left payload phase, so it
630 // is no longer in the reassembler's count and has to
631 // be added back.
632 if let Err(error) =
633 self.check_call_admission(id, self.reassembler.payload_incomplete() + 1)
634 {
635 break Err(error);
636 }
637 let request = match self.shared.decode(&payload, handles, &self.transport) {
638 Ok(request) => request,
639 Err(error) => break Err(error),
640 };
641 let trailer = trailer.map(TrailerRecv::new);
642 let handler = self.handler.clone();
643 let task_shared = self.shared.clone();
644 let task_outgoing = self.outgoing.clone();
645 let (abort, registration) = AbortHandle::new_pair();
646 tasks.push(Abortable::new(
647 async move {
648 let context = CallContext {
649 id,
650 shared: task_shared,
651 request_trailer: trailer,
652 outgoing: task_outgoing,
653 responded: false,
654 shutdown_on_respond: false,
655 charge: Some(charge),
656 marker: PhantomData,
657 };
658 handler(context, request).await;
659 },
660 registration,
661 ));
662 self.shared.inner.lock().unwrap().outstanding.insert(
663 id,
664 Cancellation {
665 signal: None,
666 abort,
667 },
668 );
669 }
670 Kind::Cancel => {
671 let mut state = self.shared.inner.lock().unwrap();
672 if let Some(signal) = state
673 .outstanding
674 .get_mut(&id)
675 .and_then(|cancel| cancel.signal.take())
676 {
677 let _ = signal.send(());
678 } else if let Some(cancel) = state.outstanding.get(&id) {
679 cancel.abort.abort();
680 } else {
681 let _ = self.outgoing.send(Outgoing::Cancel { id });
682 }
683 }
684 Kind::Discard => {
685 let _ = self.outgoing.send(Outgoing::PeerDiscarded { id });
686 }
687 Kind::Ack => {
688 if !self.shared.release_escrow(id) {
689 break Err(Error::Protocol(format!(
690 "Ack for response {id} with no active escrow"
691 )));
692 }
693 }
694 _ => {
695 break Err(Error::Protocol(format!("unexpected {kind:?} frame")));
696 }
697 }
698 }
699 if let Some((trailer, len)) = live_trailer {
700 let frame = self.transport.recv();
701 // SAFETY: the lease retains the receiver borrow and clears
702 // the erased token before it ends.
703 let lease = unsafe { RecvShared::grant(&trailer, frame, len) };
704 let result = loop {
705 tokio::select! {
706 result = RecvShared::wait_fragment(&trailer) => break result,
707 Some(_) = tasks.next(), if !tasks.is_empty() => {
708 self.drain.seal_if_idle(draining, tasks.is_empty());
709 continue;
710 }
711 _ = &mut self.shutdown, if !draining => {
712 draining = true;
713 self.drain.seal_if_idle(draining, tasks.is_empty());
714 continue;
715 }
716 }
717 };
718 if let Err(error) = result {
719 break 'main Err(error.into());
720 }
721 lease.complete();
722 }
723 };
724 drop(self.transport);
725 if draining {
726 // A drain was already under way when the transport failed, so the
727 // calls already dispatched still get to finish — their responses
728 // queue up behind the send driver's channel even though most will
729 // no longer reach the peer.
730 while tasks.next().await.is_some() {}
731 }
732 // Reaching here at all means this half is over, so the send driver
733 // must not be left waiting on credit that can now never arrive. This
734 // is deliberately unconditional and deliberately last: it overrides
735 // any `Graceful` already published, including one this very drain
736 // set a moment ago before the transport gave out.
737 self.drain.set(Drain::Abrupt);
738 if draining && matches!(&result, Err(Error::ConnectionClosed)) {
739 Ok(())
740 } else {
741 result
742 }
743 }
744}
745
746/// Drives the send half of the connection: admits queued messages into the
747/// fragment scheduler and advances the scheduler onto the transport.
748///
749/// Runs on [`Server::serve`]'s own future, alongside the receive driver and
750/// the handlers — a response is queued rather than written by the handler
751/// that produced it, so nothing here blocks on anything there.
752struct SendDriver<P: Protocol> {
753 transport: transport::AnySender,
754 outgoing: mpsc::UnboundedReceiver<Outgoing<P::Response>>,
755 shared: Arc<Shared>,
756 scheduler: fragment::Scheduler,
757 /// How much this driver still owes before it may stop; see
758 /// [`crate::driver`].
759 drain: DrainWatch,
760}
761
762impl<P: Protocol> SendDriver<P> {
763 fn new(
764 transport: transport::AnySender,
765 outgoing: mpsc::UnboundedReceiver<Outgoing<P::Response>>,
766 shared: Arc<Shared>,
767 drain: DrainWatch,
768 ) -> Self {
769 let scheduler = fragment::Scheduler::new(&shared.limits, shared.payload_budget.clone());
770 Self {
771 transport,
772 outgoing,
773 shared,
774 scheduler,
775 drain,
776 }
777 }
778
779 /// Runs until the drain signal says this driver owes nothing more.
780 ///
781 /// Three ways that happens, and they differ only in how much counts as
782 /// owed:
783 ///
784 /// * [`Drain::Running`] — the channel closed. Every handle that could
785 /// queue work is gone, including the receive driver's, so no credit can
786 /// arrive either; finish what is already started.
787 /// * [`Drain::Graceful`] — shutdown was requested. The receive half is
788 /// still running, so finish *everything*, quota-blocked sends included,
789 /// then remain available for control messages until that half ends.
790 /// * [`Drain::Abrupt`] — the receive half is gone. Finish what is
791 /// already started and abandon the rest.
792 ///
793 /// A committed write is never abandoned in any of them: the scheduler is
794 /// advanced to a fragment boundary before the loop can exit.
795 async fn run(mut self) -> Result<(), Error> {
796 // Holding a clone of `outgoing`'s sender half (the receive driver's,
797 // or a `CallContext`'s) is what represents the ability to still get a
798 // message in, so the channel closing — every clone gone — is one of
799 // the terminal conditions in its own right. It is no longer the only
800 // one: under a graceful drain the receive driver keeps its clone
801 // precisely so it can keep servicing credit, and the drain signal is
802 // what says nothing more will be admitted.
803 let mut closed = false;
804 loop {
805 let mode = self.drain.mode();
806 let done = match mode {
807 Drain::Running => closed && !self.scheduler.has_work(),
808 // Also requires the channel to be drained. A handler queues
809 // its response and *then* completes, and completing is what
810 // seals the drain — so at the instant the signal arrives the
811 // last response may still be sitting in the channel, not yet
812 // admitted to the scheduler, which would leave `has_pending`
813 // reporting nothing to do.
814 // The receive driver retains a sender until its transport
815 // ends. Staying alive until the channel closes preserves the
816 // send transport for rejection and control messages received
817 // after the response drain first becomes quiescent.
818 Drain::Graceful => closed && !self.scheduler.has_pending(),
819 Drain::Abrupt => !self.scheduler.has_work(),
820 };
821 if done {
822 return Ok(());
823 }
824 tokio::select! {
825 message = self.outgoing.recv(), if !closed => {
826 let Some(message) = message else {
827 closed = true;
828 continue;
829 };
830 self.admit(message).await?;
831 }
832 // Cancel-safe (a `watch` registration), and re-evaluating the
833 // terminal condition is the whole of the arm — the loop head
834 // above does the work.
835 _ = self.drain.changed() => {}
836 // Not raced against anything — see the matching comment in
837 // client.rs's writer loop. A dropped send future could leave a
838 // committed partial fragment on the transport, or — on
839 // transports whose writes are dispatched to a detached
840 // background task — let an abandoned write complete arbitrarily
841 // later, after the peer has already torn down its end.
842 _ = self.scheduler.ready(), if self.scheduler.has_pending() => {
843 match self.scheduler.advance(&mut self.transport).await? {
844 fragment::AdvanceOutcome::None | fragment::AdvanceOutcome::Aborted(_) => {}
845 #[cfg(target_os = "macos")]
846 fragment::AdvanceOutcome::Escrow { id, fds, handles_done } => {
847 self.shared.escrow_sent(id, fds, handles_done);
848 }
849 }
850 // Flush anything sent by the scheduler
851 let _ = self.transport.flush().await;
852 }
853 }
854 }
855 }
856
857 /// Admits one outgoing item to the fragment scheduler.
858 async fn admit(&mut self, message: Outgoing<P::Response>) -> Result<(), Error> {
859 match message {
860 Outgoing::Response { id, value, trailer } => {
861 let mut ledger = session::Ledger::default();
862 let mut put_handles = session::SessionFrame {
863 inner: EncodeHandles::new(&self.transport, self.shared.max_handles()),
864 session: &self.shared.session,
865 ledger: &mut ledger,
866 };
867 let payload = match encode_payload(&value, &mut put_handles) {
868 Ok(payload) => payload,
869 Err(error) => {
870 drop(put_handles);
871 // Nothing reached the wire, so undo the gift increments
872 // rather than letting the ledger's drop commit them.
873 ledger.rescind();
874 return Err(error);
875 }
876 };
877 let handles = self.shared.finish_handles(id, put_handles.inner);
878 self.scheduler
879 .admit_message(Kind::Response, id, payload, handles, trailer, ledger);
880 }
881 Outgoing::Error { id } => self.scheduler.admit_empty(Kind::Error, id),
882 Outgoing::Cancel { id } => match self.scheduler.try_cancel_active(id) {
883 fragment::AbortOutcome::NotActive => {}
884 fragment::AbortOutcome::Discarded { started, .. } => {
885 if started {
886 self.scheduler.admit_abort(id);
887 }
888 if !started {
889 self.shared.discard_unsent_escrow(id);
890 }
891 }
892 },
893 Outgoing::DiscardTrailer { id } => self.scheduler.admit_empty(Kind::Discard, id),
894 Outgoing::PeerDiscarded { id } => {
895 // The peer will never credit what it just threw away; see the
896 // client's matching arm.
897 self.shared.trailer_session.settle(id);
898 self.scheduler.discard_active_trailer(id);
899 }
900 Outgoing::Ack { id } => self.scheduler.admit_empty(Kind::Ack, id),
901 Outgoing::Release { id, count } => self.scheduler.admit_release(id, count),
902 Outgoing::Credit { id, count } => self.scheduler.admit_credit(id, count),
903 Outgoing::PayloadCredit { count } => self.scheduler.admit_payload_credit(count),
904 }
905 Ok(())
906 }
907}
908
909/// Request-scoped services supplied to a server handler.
910///
911/// A context is not cloneable and must be consumed to send a response.
912pub struct CallContext<P: Protocol> {
913 id: u64,
914 shared: Arc<Shared>,
915 request_trailer: Option<TrailerRecv>,
916 /// A strong sender, so a live handler keeps the writer's channel — and
917 /// with it the connection — open until it has answered.
918 outgoing: mpsc::UnboundedSender<Outgoing<P::Response>>,
919 responded: bool,
920 shutdown_on_respond: bool,
921 /// This request's share of the payload quota, returned to the peer when
922 /// this context is dropped — which is every path a call can end on,
923 /// including a handler that never responds, one aborted by a peer
924 /// cancellation, and one that panics.
925 charge: Option<PayloadCharge>,
926 marker: PhantomData<fn() -> P>,
927}
928
929impl<P: Protocol> CallContext<P> {
930 /// Takes this request's raw-byte trailer, if present.
931 ///
932 /// The returned value implements [`AsyncRead`](tokio::io::AsyncRead).
933 /// Dropping it stops local consumption and immediately tells the peer to
934 /// stop sending, as does responding while the context still holds it.
935 ///
936 /// Taken rather than borrowed, so a handler may keep reading after it
937 /// has responded. Paired with
938 /// [`respond_with_trailer`](Self::respond_with_trailer) that gives a
939 /// duplex byte pipe over one call: each direction is an independent
940 /// stream that ends when its own end says so, and the call itself is
941 /// complete as soon as the response head goes out. Neither direction
942 /// holds a call slot after that, so the pipes are bounded by trailer
943 /// credit rather than by `max_concurrent_calls` — and, as with a socket,
944 /// nothing ties the two halves together: closing one does not close the
945 /// other, and a peer that vanishes is noticed through the transport.
946 pub fn trailer(&mut self) -> Option<TrailerRecv> {
947 self.request_trailer.take()
948 }
949
950 /// Returns this request's raw-byte trailer in manual-credit mode.
951 ///
952 /// The consumer then owes the peer an explicit
953 /// [`TrailerRecv::release`](crate::trailer::TrailerRecv::release) for
954 /// every chunk it finishes with, instead of credit being returned on
955 /// read. Use this when the bytes are being handed somewhere slower than
956 /// this process, so that the peer's send rate follows the real drain
957 /// rate; read [`release`](crate::trailer::TrailerRecv::release) first,
958 /// since manual mode moves a deadlock rule into calling code.
959 ///
960 /// The mode is fixed here rather than switchable afterwards, so a
961 /// trailer cannot be half auto-credited and half not. Taken rather than
962 /// borrowed, exactly as in [`trailer`](Self::trailer).
963 pub fn trailer_manual_credit(&mut self) -> Option<TrailerRecv> {
964 let mut trailer = self.request_trailer.take()?;
965 trailer.set_manual_credit();
966 Some(trailer)
967 }
968
969 /// Sends a response without a trailer and consumes this call context.
970 ///
971 /// A request trailer this context still holds is discarded; one already
972 /// taken by [`trailer`](Self::trailer) is untouched and stays readable.
973 pub fn respond(mut self, response: P::Response) {
974 drop(self.request_trailer.take());
975 self.responded = true;
976 self.shared
977 .inner
978 .lock()
979 .unwrap()
980 .outstanding
981 .remove(&self.id);
982 let _ = self.outgoing.send(Outgoing::Response {
983 id: self.id,
984 value: response,
985 trailer: fragment::Trailer::None,
986 });
987 self.finish_shutdown();
988 }
989
990 /// Sends a response head and returns a writer for its raw-byte trailer.
991 ///
992 /// Call [`TrailerSend::finish`](crate::trailer::TrailerSend::finish), or
993 /// asynchronously shut down the returned writer, to commit the trailer.
994 /// Dropping it without finishing aborts the trailer. A request trailer
995 /// this context still holds is discarded; one already taken by
996 /// [`trailer`](Self::trailer) is untouched, which is what makes the two
997 /// directions a duplex pipe.
998 pub fn respond_with_trailer(mut self, response: P::Response) -> TrailerSend<()> {
999 drop(self.request_trailer.take());
1000 let shared = SendShared::new(
1001 Kind::Response,
1002 self.id,
1003 &self.shared.limits,
1004 self.shared.trailer_session.clone(),
1005 );
1006 self.responded = true;
1007 self.shared
1008 .inner
1009 .lock()
1010 .unwrap()
1011 .outstanding
1012 .remove(&self.id);
1013 let _ = self.outgoing.send(Outgoing::Response {
1014 id: self.id,
1015 value: response,
1016 trailer: fragment::Trailer::Stream(shared.clone()),
1017 });
1018 self.finish_shutdown();
1019 TrailerSend::new(shared, ())
1020 }
1021
1022 /// Returns this request's payload quota to the peer now, rather than when
1023 /// this context is dropped.
1024 ///
1025 /// The quota is charged for the whole call, so a handler that pends for a
1026 /// long time throttles the connection for as long as it pends — which is
1027 /// fine for the small payloads a long-poll usually carries, and is not
1028 /// for a large one. This is the escape hatch: finish with the request,
1029 /// drop whatever you decoded from it, then release. Nothing checks that
1030 /// you did the first two, and releasing while still holding the request's
1031 /// data merely makes the peer's accounting optimistic.
1032 ///
1033 /// Idempotent, and never required — dropping the context releases just
1034 /// the same.
1035 pub fn release_payload(&mut self) {
1036 self.charge = None;
1037 }
1038
1039 /// Requests graceful shutdown after this handler sends its response.
1040 ///
1041 /// The server stops accepting requests once this context is consumed by
1042 /// [`respond`](Self::respond) or [`respond_with_trailer`](Self::respond_with_trailer),
1043 /// then lets already-running handlers finish.
1044 pub fn shutdown(&mut self) {
1045 self.shutdown_on_respond = true;
1046 }
1047
1048 fn finish_shutdown(&self) {
1049 if self.shutdown_on_respond
1050 && let Some(shutdown) = self.shared.inner.lock().unwrap().shutdown.take()
1051 {
1052 let _ = shutdown.send(());
1053 }
1054 }
1055
1056 /// Runs an operation that can observe request cancellation without dropping
1057 /// the handler itself.
1058 ///
1059 /// If the peer cancels while `operation` is running, its future is dropped
1060 /// and this method returns [`RequestCancelled`]. The handler regains the
1061 /// context and may perform cleanup or send an application-level response.
1062 /// Only one cancellation guard may be active at a time; nesting guards
1063 /// panics.
1064 pub async fn cancel_guard<T, F>(&mut self, operation: F) -> Result<T, RequestCancelled>
1065 where
1066 F: AsyncFnOnce(&mut CallContext<P>) -> T,
1067 {
1068 struct Reset {
1069 id: u64,
1070 shared: Arc<Shared>,
1071 }
1072 impl Drop for Reset {
1073 fn drop(&mut self) {
1074 if let Some(cancel) = self
1075 .shared
1076 .inner
1077 .lock()
1078 .unwrap()
1079 .outstanding
1080 .get_mut(&self.id)
1081 {
1082 cancel.signal = None;
1083 }
1084 }
1085 }
1086 let (signal, cancelled) = oneshot::channel();
1087 {
1088 let mut inner = self.shared.inner.lock().unwrap();
1089 let cancel = inner
1090 .outstanding
1091 .get_mut(&self.id)
1092 .expect("call context is not registered");
1093 assert!(cancel.signal.is_none(), "cancel guard is already active");
1094 cancel.signal = Some(signal);
1095 }
1096 let _reset = Reset {
1097 id: self.id,
1098 shared: self.shared.clone(),
1099 };
1100 let future = operation(&mut *self);
1101 tokio::pin!(future);
1102 tokio::select! {
1103 value = &mut future => Ok(value),
1104 result = cancelled => match result { Ok(()) => Err(RequestCancelled), Err(_) => Ok(future.await) },
1105 }
1106 }
1107
1108 /// Register an opqaue handle.
1109 ///
1110 /// The underlying resource will be automatically dropped when both of
1111 /// the following hold:
1112 /// - It is no longer referenced by the client, or the server has unregistered it
1113 /// - All oustanding [`OpaqueGuard`]s have been dropped
1114 ///
1115 /// # Panics
1116 ///
1117 /// If a different concrete type has already been registered under
1118 /// `T::Marker` on this session.
1119 pub fn register<T: OpaqueResource>(&self, value: T) -> Gift<T::Marker> {
1120 self.shared.session.register(value)
1121 }
1122
1123 /// Acquires a guard an opaque handle citation.
1124 ///
1125 /// Returns [`InvalidOpaque`] if the resource was unregistered while the peer
1126 /// still held a reference to it.
1127 ///
1128 /// # Panics
1129 ///
1130 /// If the handle was minted by a different session.
1131 pub fn acquire<T: OpaqueResource>(
1132 &self,
1133 value: Cite<T::Marker>,
1134 ) -> Result<OpaqueGuard<T>, InvalidOpaque> {
1135 self.shared.session.acquire(value)
1136 }
1137
1138 /// Unregisters an opaque handle
1139 ///
1140 /// If no outstanding [`OpaqueGuard`]s existed, the resource is returned
1141 /// directly; otherwise, `None` is returned and the resource will be
1142 /// dropped with the last `OpaqueGuard`. In either case, subsequent
1143 /// uses of [`Self::acquire`] will fail. If the handle has already
1144 /// been unregistered, returns [`InvalidOpaque`].
1145 ///
1146 /// # Panics
1147 ///
1148 /// If the handle was minted by a different session.
1149 pub fn unregister<T: OpaqueResource>(
1150 &self,
1151 value: Cite<T::Marker>,
1152 ) -> Result<Option<T>, InvalidOpaque> {
1153 self.shared.session.unregister::<T>(value)
1154 }
1155
1156 /// Unregisters an opaque handle if not busy
1157 ///
1158 /// The recoverable counterpart of [`unregister`](Self::unregister): if
1159 /// outstanding [`OpaqueGuard`]s exist, the handle is not unregistered,
1160 /// which is signaled by a `None` return value.
1161 ///
1162 /// # Panics
1163 ///
1164 /// If the handle was minted by a different session, as with
1165 /// [`acquire`](Self::acquire).
1166 pub fn try_unregister<T: OpaqueResource>(
1167 &self,
1168 value: Cite<T::Marker>,
1169 ) -> Result<Option<T>, InvalidOpaque> {
1170 self.shared.session.try_unregister::<T>(value)
1171 }
1172}
1173
1174impl<P: Protocol> Drop for CallContext<P> {
1175 fn drop(&mut self) {
1176 if !self.responded {
1177 self.shared
1178 .inner
1179 .lock()
1180 .unwrap()
1181 .outstanding
1182 .remove(&self.id);
1183 let _ = self.outgoing.send(Outgoing::Error { id: self.id });
1184 }
1185 }
1186}
1187
1188/// Indicates that a guarded operation was interrupted by request cancellation.
1189#[derive(Clone, Copy, Debug, thiserror::Error)]
1190#[error("request cancelled")]
1191pub struct RequestCancelled;
1192
1193#[cfg(test)]
1194mod tests {
1195 use std::{future, time::Duration};
1196
1197 use bytes::Bytes;
1198 use tokio::task::JoinHandle;
1199
1200 use super::*;
1201
1202 const APP_PROTOCOL: (&str, &[u16]) = ("test", &[1]);
1203 const FRAGMENT_SIZE: usize = 512;
1204
1205 struct Test;
1206
1207 impl Protocol for Test {
1208 type Request = u32;
1209 type Response = u32;
1210 }
1211
1212 /// A peer that speaks the wire format directly, so a test can send what
1213 /// a real client's own accounting would never let it send.
1214 struct Peer {
1215 transport: transport::AnySender,
1216 scheduler: fragment::Scheduler,
1217 /// Unread: nothing these tests do makes the server say anything back.
1218 _receiver: transport::AnyReceiver,
1219 }
1220
1221 impl Peer {
1222 /// Queues a whole request, small enough to arrive in one fragment
1223 /// and be dispatched the moment it lands.
1224 fn request(&mut self, id: u64, value: u32) {
1225 self.admit(Kind::Request, id, postcard::to_stdvec(&value).unwrap());
1226 }
1227
1228 /// Queues a request far too large for one fragment, so it stays in
1229 /// the server's reassembler for as long as the test declines to send
1230 /// the rest of it. Its bytes are never decoded, so they need not be
1231 /// a valid request.
1232 fn partial_request(&mut self, id: u64) {
1233 self.admit(Kind::Request, id, vec![0; 64 * FRAGMENT_SIZE]);
1234 }
1235
1236 /// Queues a fragment of a kind only a server may send.
1237 fn response(&mut self, id: u64) {
1238 self.admit(Kind::Response, id, postcard::to_stdvec(&0u32).unwrap());
1239 }
1240
1241 fn admit(&mut self, kind: Kind, id: u64, payload: Vec<u8>) {
1242 self.scheduler.admit_message(
1243 kind,
1244 id,
1245 Bytes::from(payload),
1246 Default::default(),
1247 fragment::Trailer::None,
1248 session::Ledger::default(),
1249 );
1250 }
1251
1252 /// Writes `count` fragments. The scheduler round-robins, so a count
1253 /// equal to the number of queued messages puts one fragment of each
1254 /// on the wire.
1255 async fn send(&mut self, count: usize) {
1256 for _ in 0..count {
1257 self.scheduler.advance(&mut self.transport).await.unwrap();
1258 }
1259 }
1260 }
1261
1262 fn endpoint_pair() -> (
1263 (transport::AnySender, transport::AnyReceiver),
1264 (transport::AnySender, transport::AnyReceiver),
1265 ) {
1266 let (a_write, a_read) = tokio::io::duplex(4096);
1267 let (b_write, b_read) = tokio::io::duplex(4096);
1268 let (a_sender, _unused) = transport::generic_duplex(a_write);
1269 let (_unused, a_receiver) = transport::generic_duplex(b_read);
1270 let (b_sender, _unused) = transport::generic_duplex(b_write);
1271 let (_unused, b_receiver) = transport::generic_duplex(a_read);
1272 (
1273 (
1274 transport::AnySender::Generic(a_sender),
1275 transport::AnyReceiver::Generic(a_receiver),
1276 ),
1277 (
1278 transport::AnySender::Generic(b_sender),
1279 transport::AnyReceiver::Generic(b_receiver),
1280 ),
1281 )
1282 }
1283
1284 /// Negotiates a real session, serves one end of it, and hands back the
1285 /// wire-level peer on the other.
1286 ///
1287 /// The handler never responds, so every call it is given stays
1288 /// outstanding until the session ends under it — which is what lets a
1289 /// test hold calls in one custodian while it fills the other.
1290 async fn hostile_session(
1291 max_concurrent_calls: usize,
1292 ) -> (
1293 Peer,
1294 JoinHandle<Result<(), Error>>,
1295 mpsc::UnboundedReceiver<()>,
1296 ) {
1297 hostile_session_with(Limits {
1298 max_concurrent_calls,
1299 max_fragment_size: FRAGMENT_SIZE,
1300 ..Limits::default()
1301 })
1302 .await
1303 }
1304
1305 async fn hostile_session_with(
1306 limits: Limits,
1307 ) -> (
1308 Peer,
1309 JoinHandle<Result<(), Error>>,
1310 mpsc::UnboundedReceiver<()>,
1311 ) {
1312 let ((mut peer_sender, mut peer_receiver), (mut server_sender, mut server_receiver)) =
1313 endpoint_pair();
1314 let (peer, server) = tokio::join!(
1315 fragment::negotiate(
1316 &mut peer_sender,
1317 &mut peer_receiver,
1318 &limits,
1319 APP_PROTOCOL,
1320 None
1321 ),
1322 fragment::negotiate(
1323 &mut server_sender,
1324 &mut server_receiver,
1325 &limits,
1326 APP_PROTOCOL,
1327 None
1328 ),
1329 );
1330 let peer_limits = peer.unwrap().limits;
1331 let server =
1332 Server::<Test>::from_transport(server_sender, server_receiver, server.unwrap().limits);
1333 let (dispatched_tx, dispatched) = mpsc::unbounded_channel();
1334 let serve = tokio::spawn(server.serve(async move |_: CallContext<Test>, _: u32| {
1335 let _ = dispatched_tx.send(());
1336 future::pending::<()>().await
1337 }));
1338 (
1339 Peer {
1340 transport: peer_sender,
1341 // An unbounded budget, so the peer can send what its
1342 // negotiated quota would have stopped it sending. That is
1343 // the whole point of this harness: the server's checks are
1344 // backstops against a peer that ignores what it agreed to,
1345 // and a peer that honoured it could never reach them.
1346 scheduler: fragment::Scheduler::new(
1347 &peer_limits,
1348 Arc::new(PayloadBudget::new(usize::MAX)),
1349 ),
1350 _receiver: peer_receiver,
1351 },
1352 serve,
1353 dispatched,
1354 )
1355 }
1356
1357 /// A server that fails to notice a violation wedges rather than fails,
1358 /// and which await it wedges on depends on the violation, so the bound
1359 /// goes around the whole test.
1360 async fn bounded<F: Future>(test: F) -> F::Output {
1361 tokio::time::timeout(Duration::from_secs(5), test)
1362 .await
1363 .expect("the server should have refused the peer by now")
1364 }
1365
1366 /// A call that has only started arriving is charged to the same budget
1367 /// as one already dispatched. The reassembler counting it separately
1368 /// would let a peer hold twice the limit — and twice the reassembly
1369 /// memory the limit exists to bound.
1370 ///
1371 /// Filling the budget exactly, and seeing the second call dispatched
1372 /// anyway, is also what pins the comparison at `>` rather than `>=`.
1373 #[tokio::test]
1374 async fn a_call_still_arriving_counts_against_dispatched_ones() {
1375 bounded(async {
1376 let (mut peer, serve, mut dispatched) = hostile_session(2).await;
1377 peer.request(1, 7);
1378 peer.send(1).await;
1379 dispatched.recv().await.unwrap();
1380 peer.request(2, 8);
1381 peer.send(1).await;
1382 dispatched.recv().await.unwrap();
1383
1384 // Two of two are dispatched and unanswered, so a third is over
1385 // the limit from its very first fragment.
1386 peer.partial_request(3);
1387 peer.send(1).await;
1388
1389 assert!(matches!(
1390 serve.await.unwrap(),
1391 Err(Error::Protocol(message)) if message == "too many concurrent calls"
1392 ));
1393 })
1394 .await;
1395 }
1396
1397 /// The mirror image: a dispatched call is charged to the same budget as
1398 /// one still arriving, so the check at dispatch has to add the
1399 /// reassembler's count to its own.
1400 #[tokio::test]
1401 async fn a_dispatched_call_counts_against_ones_still_arriving() {
1402 bounded(async {
1403 let (mut peer, serve, mut dispatched) = hostile_session(2).await;
1404 peer.request(1, 7);
1405 peer.send(1).await;
1406 dispatched.recv().await.unwrap();
1407
1408 // Two of two: one dispatched and unanswered, one still arriving.
1409 peer.partial_request(2);
1410 peer.send(1).await;
1411
1412 peer.request(3, 9);
1413 peer.send(2).await;
1414
1415 assert!(matches!(
1416 serve.await.unwrap(),
1417 Err(Error::Protocol(message)) if message == "too many concurrent calls"
1418 ));
1419 })
1420 .await;
1421 }
1422
1423 /// The arriving call is already in the count, so a limit of zero admits
1424 /// nothing at all.
1425 #[tokio::test]
1426 async fn a_zero_call_limit_refuses_the_first_request() {
1427 bounded(async {
1428 let (mut peer, serve, _dispatched) = hostile_session(0).await;
1429 peer.request(1, 7);
1430 peer.send(1).await;
1431 assert!(matches!(
1432 serve.await.unwrap(),
1433 Err(Error::Protocol(message)) if message == "too many concurrent calls"
1434 ));
1435 })
1436 .await;
1437 }
1438
1439 #[tokio::test]
1440 async fn a_request_reusing_a_live_call_id_is_refused() {
1441 bounded(async {
1442 let (mut peer, serve, mut dispatched) = hostile_session(4).await;
1443 peer.request(1, 7);
1444 peer.send(1).await;
1445 dispatched.recv().await.unwrap();
1446 peer.request(1, 8);
1447 peer.send(1).await;
1448 assert!(matches!(
1449 serve.await.unwrap(),
1450 Err(Error::Protocol(message)) if message == "duplicate active request id 1"
1451 ));
1452 })
1453 .await;
1454 }
1455
1456 /// The gate in front of the reassembler: nobody calls a client, so a
1457 /// response arriving here answers nothing.
1458 #[tokio::test]
1459 async fn a_response_from_the_peer_is_refused() {
1460 bounded(async {
1461 let (mut peer, serve, _dispatched) = hostile_session(4).await;
1462 peer.response(1);
1463 peer.send(1).await;
1464 assert!(matches!(
1465 serve.await.unwrap(),
1466 Err(Error::Protocol(message)) if message == "server received a Response fragment"
1467 ));
1468 })
1469 .await;
1470 }
1471
1472 /// The attack `max_outstanding_payload` exists to close: open many
1473 /// messages and send one fragment of each, and `max_concurrent_calls`
1474 /// alone admits every one of them — `max_payload_size` bounds each
1475 /// message, and nothing bounds the sum. Here the call count is deliberately
1476 /// generous, so the only thing that can refuse this is the byte quota.
1477 #[tokio::test]
1478 async fn a_peer_that_ignores_its_payload_quota_is_refused() {
1479 bounded(async {
1480 let (mut peer, serve, _dispatched) = hostile_session_with(Limits {
1481 max_concurrent_calls: 64,
1482 max_fragment_size: FRAGMENT_SIZE,
1483 // Both, because negotiation raises the quota to at least the
1484 // per-message cap: a pool that could not carry one legal
1485 // message would be a configuration with no legal traffic.
1486 max_payload_size: 4 * FRAGMENT_SIZE,
1487 max_outstanding_payload: 4 * FRAGMENT_SIZE,
1488 ..Limits::default()
1489 })
1490 .await;
1491
1492 for id in 1..=16 {
1493 peer.partial_request(id);
1494 }
1495 // One fragment of each, round-robin, until the sum of the
1496 // reassembly buffers passes the quota. Driven from its own task
1497 // because the server stops reading the moment it objects, and a
1498 // peer writing into a full pipe would otherwise block forever
1499 // instead of letting the assertion below run.
1500 let sending = tokio::spawn(async move { peer.send(16).await });
1501
1502 assert!(matches!(
1503 serve.await.unwrap(),
1504 Err(Error::Protocol(message)) if message.contains("session payload quota")
1505 ));
1506 sending.abort();
1507 })
1508 .await;
1509 }
1510}