Skip to main content

dolang_rpc/
session.rs

1//! Session-scoped opaque handles.
2//!
3//! [`Gift`] is an opaque resource given to a peer by handle, and [`Cite`]
4//! if a reference to such a resource.  This can be used to represent any
5//! sort of resource that does not pass between client and server directly,
6//! such as open files.  They are only valid for a particular RPC session.
7
8use serde::{Deserialize, Deserializer, Serialize, Serializer};
9#[cfg(unix)]
10use std::os::fd::OwnedFd;
11use std::{
12    any::{Any, TypeId},
13    collections::{HashMap, hash_map::Entry},
14    fmt, io,
15    marker::PhantomData,
16    sync::{
17        Arc, Mutex, Weak,
18        atomic::{AtomicU64, Ordering},
19    },
20};
21
22#[cfg(unix)]
23use crate::{handle::TakeHandle, transport::ReceivedHandles};
24use crate::{
25    handle::{ErasedHandle, PutHandle},
26    transport::EncodeHandles,
27};
28
29/// The owner bit rides the low bit of the id.
30pub(crate) fn pack_wire(owner: u8, id: u64) -> u64 {
31    debug_assert!(id < (1 << 63), "opaque identifier is too large to pack");
32    (id << 1) | u64::from(owner & 1)
33}
34
35pub(crate) fn unpack_wire(packed: u64) -> (u8, u64) {
36    ((packed & 1) as u8, packed >> 1)
37}
38
39/// Wire discriminant: the resource belongs to the sender.
40const WIRE_GIFT: u8 = 0;
41/// Wire discriminant: the resource belongs to the receiver.
42const WIRE_CITATION: u8 = 1;
43
44/// Collapse a remote grant total before it can approach the integer ceiling.
45/// One reference is retained for the live local handle; the rest are returned
46/// to the owner in one counted release.
47const GRANT_RELEASE_THRESHOLD: u32 = u32::MAX / 2;
48
49/// A value that can be registered in a session's opaque-object table.
50///
51/// `Marker` is the public protocol-level type carried by [`Gift`] and [`Cite`];
52/// it is nothing but a name, so that the concrete resource type may remain
53/// private.
54/// The mapping from marker to resource must be injective, and
55/// `Session::register` panics if an application ever registers two concrete
56/// types under one marker — otherwise a marker would not identify a type and
57/// the wire could not be typechecked at all.
58pub trait OpaqueResource: Send + Sync + 'static {
59    type Marker: 'static;
60}
61
62/// Emits release frames for opaques whose last local handle has dropped.
63///
64/// Implemented on each endpoint's `WeakUnboundedSender` for its own outgoing
65/// message type. Sending must not block or fail loudly: this is called from
66/// `Drop`. Deliberately weak: a writer task treats "every sender dropped" as
67/// its shutdown signal and transitively holds the `Session` that owns its
68/// sink, so a strong sender here would be a cycle — the writer waiting on a
69/// channel it is itself keeping open.
70pub(crate) trait ReleaseSink: Send + Sync + 'static {
71    fn release(&self, id: u64, count: u32);
72}
73
74/// A handle on a resource this endpoint owns.
75///
76/// The `Arc` around it is the local handle count. The resource itself lives in
77/// the session table, never in here, so that [`Session::unregister`] can empty
78/// the slot and have every outstanding handle observe the revocation. That
79/// is the whole reason `Session::acquire` is fallible: resolving an opaque is
80/// `open()` on a descriptor number, not a pointer dereference.
81pub(crate) struct LocalRef {
82    id: u64,
83    /// Which session minted this handle. An id means nothing outside the
84    /// session that issued it, so every redemption checks it.
85    serial: u64,
86    session: Weak<Session>,
87}
88
89/// A handle on a resource the peer owns.
90///
91/// The protocol count lives in the table entry, not here: it is the total the
92/// peer has granted for the id, and whichever handle is alive owns that whole
93/// total. See [`RemoteRef::drop`] for how a handle that loses a race forfeits
94/// it rather than splitting it.
95pub(crate) struct RemoteRef {
96    id: u64,
97    /// See [`LocalRef::serial`].
98    serial: u64,
99    session: Weak<Session>,
100}
101
102/// The handle behind a [`Gift`] or a [`Cite`], which differ only in the wire
103/// position they are legal in — this carries everything either of them does.
104pub(crate) enum Inner {
105    Local(Arc<LocalRef>),
106    Remote(Arc<RemoteRef>),
107}
108
109impl Clone for Inner {
110    fn clone(&self) -> Self {
111        // Purely a local handle count bump; the protocol count is untouched.
112        match self {
113            Self::Local(local) => Self::Local(local.clone()),
114            Self::Remote(remote) => Self::Remote(remote.clone()),
115        }
116    }
117}
118
119impl Inner {
120    fn id(&self) -> u64 {
121        match self {
122            Self::Local(local) => local.id,
123            Self::Remote(remote) => remote.id,
124        }
125    }
126
127    fn owner(&self) -> u8 {
128        match self {
129            Self::Local(_) => WIRE_GIFT,
130            Self::Remote(_) => WIRE_CITATION,
131        }
132    }
133}
134
135impl Drop for LocalRef {
136    fn drop(&mut self) {
137        // A dead `Weak<Session>` means the connection itself is tearing down,
138        // which retires every table wholesale. Nothing to do.
139        let Some(session) = self.session.upgrade() else {
140            return;
141        };
142        let mut tables = session.tables.lock().unwrap();
143        let Some(entry) = tables.local.get(&self.id) else {
144            return;
145        };
146        // Only act if the entry still points at *us*: a citation that arrived
147        // while this handle was dying installed a fresh one (see `cite`), and
148        // that one now owns the registration.
149        if !entry.points_at(self) {
150            return;
151        }
152        // The peer may still name this resource even though we no longer hold
153        // a handle on it, in which case the entry (and the resource) has to
154        // outlive us and is retired by the final release instead.
155        if entry.granted == 0 {
156            tables.local.remove(&self.id);
157        }
158    }
159}
160
161impl Drop for RemoteRef {
162    fn drop(&mut self) {
163        let Some(session) = self.session.upgrade() else {
164            return;
165        };
166        let granted = {
167            let mut tables = session.tables.lock().unwrap();
168            // Only act if the slot still points at *us*. A gift that failed to
169            // upgrade this handle mid-drop installed a fresh one and folded
170            // our references into the entry's running total; that handle now
171            // owns the whole total, so this one releases nothing.
172            if !tables
173                .remote
174                .get(&self.id)
175                .is_some_and(|entry| entry.points_at(self))
176            {
177                return;
178            }
179            tables
180                .remote
181                .remove(&self.id)
182                .expect("just matched")
183                .granted
184        };
185        if granted > 0 {
186            session.sink.release(self.id, granted);
187        }
188    }
189}
190
191/// Panic message shared by the two places that catch the same mistake.
192const CITE_OWNED: &str = "cannot cite a resource this endpoint owns; \
193                          gift it again to name it to the peer";
194
195/// An opaque handle that is granted to the client.  The server
196/// owns the resource, and the client obtains a handle to it for
197/// subsequent use with [`Gift::cite`].
198pub struct Gift<M> {
199    pub(crate) inner: Inner,
200    marker: PhantomData<fn() -> M>,
201}
202
203/// An reference to a previously-granted opaque handle.
204///
205/// Produced by [`Gift::cite`].
206pub struct Cite<M> {
207    pub(crate) inner: Inner,
208    marker: PhantomData<fn() -> M>,
209}
210
211impl<M> Cite<M> {
212    pub(crate) fn new(inner: Inner) -> Self {
213        Self {
214            inner,
215            marker: PhantomData,
216        }
217    }
218}
219
220impl<M> Gift<M> {
221    pub(crate) fn new(inner: Inner) -> Self {
222        Self {
223            inner,
224            marker: PhantomData,
225        }
226    }
227
228    /// Creates a citation handle for sending back to the server.
229    ///
230    /// # Panics
231    ///
232    /// If used by the server on a handle it registered itself.
233    pub fn cite(&self) -> Cite<M> {
234        assert!(matches!(self.inner, Inner::Remote(_)), "{CITE_OWNED}");
235        Cite {
236            inner: self.inner.clone(),
237            marker: PhantomData,
238        }
239    }
240}
241
242/// The two handle types differ only in the wire position they are legal in, so
243/// everything that does not touch the wire is identical between them.
244macro_rules! opaque_handle {
245    ($name:ident) => {
246        impl<M> Clone for $name<M> {
247            fn clone(&self) -> Self {
248                Self {
249                    inner: self.inner.clone(),
250                    marker: PhantomData,
251                }
252            }
253        }
254
255        impl<M> PartialEq for $name<M> {
256            fn eq(&self, other: &Self) -> bool {
257                self.inner.owner() == other.inner.owner() && self.inner.id() == other.inner.id()
258            }
259        }
260
261        impl<M> Eq for $name<M> {}
262
263        impl<M> fmt::Debug for $name<M> {
264            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
265                f.debug_struct(stringify!($name))
266                    .field("owner", &self.inner.owner())
267                    .field("id", &self.inner.id())
268                    .finish_non_exhaustive()
269            }
270        }
271    };
272}
273
274opaque_handle!(Gift);
275opaque_handle!(Cite);
276
277impl<M> Serialize for Gift<M> {
278    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
279        assert!(
280            matches!(self.inner, Inner::Local(_)),
281            "cannot gift a resource this endpoint does not own; \
282             use `Gift::cite` to name it back to its owner"
283        );
284        crate::serde::serialize_opaque(&self.inner, serializer)
285    }
286}
287
288impl<'de, M> Deserialize<'de> for Gift<M> {
289    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
290        // No marker check: the peer's table is the authority on the type of a
291        // resource the peer owns, and this side has nothing to check it against.
292        crate::serde::deserialize_gift(deserializer).map(Gift::new)
293    }
294}
295
296impl<M> Serialize for Cite<M> {
297    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
298        assert!(matches!(self.inner, Inner::Remote(_)), "{CITE_OWNED}");
299        crate::serde::serialize_opaque(&self.inner, serializer)
300    }
301}
302
303impl<'de, M: 'static> Deserialize<'de> for Cite<M> {
304    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
305        // The marker travels with the request so the session can check an
306        // arriving citation against the type it registered the id under.
307        crate::serde::deserialize_cite(deserializer, TypeId::of::<M>()).map(Cite::new)
308    }
309}
310
311/// Smart pointer to a registered opaque resource.
312///
313/// The resource remains valid until every guard is dropped, even if
314/// unregistered concurrently.
315pub struct OpaqueGuard<T>(Arc<T>);
316impl<T> std::ops::Deref for OpaqueGuard<T> {
317    type Target = T;
318    fn deref(&self) -> &T {
319        &self.0
320    }
321}
322
323/// A stale opaque handle.
324#[derive(Clone, Copy, Debug, thiserror::Error)]
325#[error("invalid opaque object")]
326pub struct InvalidOpaque;
327
328struct LocalEntry {
329    ty: TypeId,
330    /// The marker the resource was registered under, checked against the one
331    /// the wire position declares when a citation arrives.
332    marker: TypeId,
333    /// `None` once [`Session::unregister`] has emptied the handle. The entry
334    /// itself survives so that a citation still in flight from the peer is
335    /// distinguishable from an unknown id, and so the id cannot be reused
336    /// while the peer might still name it.
337    resource: Option<Arc<dyn Any + Send + Sync>>,
338    /// Protocol count: references handed to the peer.
339    granted: u32,
340    handle: Weak<LocalRef>,
341}
342
343impl LocalEntry {
344    fn points_at(&self, handle: &LocalRef) -> bool {
345        std::ptr::eq(self.handle.as_ptr(), handle as *const LocalRef)
346    }
347}
348
349struct RemoteEntry {
350    /// Protocol count: references the peer has granted for this id. Owned by
351    /// the entry rather than by any one handle, so a gift racing the last
352    /// handle's drop folds into a single running total.
353    granted: u32,
354    handle: Weak<RemoteRef>,
355}
356
357impl RemoteEntry {
358    fn points_at(&self, handle: &RemoteRef) -> bool {
359        std::ptr::eq(self.handle.as_ptr(), handle as *const RemoteRef)
360    }
361}
362
363#[derive(Default)]
364struct Tables {
365    next: u64,
366    local: HashMap<u64, LocalEntry>,
367    remote: HashMap<u64, RemoteEntry>,
368}
369
370/// One endpoint's half of a session's opaque bookkeeping.
371///
372/// Both endpoints run the same structure: `local` holds resources this side
373/// owns, `remote` mirrors references the peer has granted this side. A client
374/// that only ever receives opaques still needs `remote`, because dropping
375/// those references is what frees the peer's resources.
376pub(crate) struct Session {
377    /// Distinguishes this session from every other one in the process, so that
378    /// an [`Opaque`] redeemed against the wrong endpoint is caught rather than
379    /// silently resolving to whatever that endpoint happens to hold under the
380    /// same id.
381    serial: u64,
382    tables: Mutex<Tables>,
383    /// Marker type -> the concrete resource type registered under it, with its
384    /// name for diagnostics. Kept out of `tables` so that the conflict panic
385    /// cannot poison the table lock.
386    markers: Mutex<HashMap<TypeId, (TypeId, &'static str)>>,
387    sink: Box<dyn ReleaseSink>,
388}
389
390impl Session {
391    pub(crate) fn new(sink: Box<dyn ReleaseSink>) -> Arc<Self> {
392        static NEXT_SERIAL: AtomicU64 = AtomicU64::new(0);
393        Arc::new(Self {
394            serial: NEXT_SERIAL.fetch_add(1, Ordering::Relaxed),
395            tables: Mutex::new(Tables::default()),
396            markers: Mutex::new(HashMap::new()),
397            sink,
398        })
399    }
400
401    /// Rejects an [`Opaque`] minted by a different session.
402    ///
403    /// A panic rather than an error: ids are session-scoped, so a foreign one
404    /// is a pure local logic error that no peer and no race can produce, and
405    /// the alternative is resolving it against an unrelated resource that
406    /// happens to share the id.
407    fn check_serial(&self, serial: u64) {
408        assert_eq!(
409            serial, self.serial,
410            "opaque reference redeemed against a different session"
411        );
412    }
413
414    /// Records the marker under which `T` is registered, panicking if the
415    /// application has already used that marker for a different resource type.
416    ///
417    /// The wire carries only `(owner, id)`; a marker is what a protocol
418    /// declares a position to hold. Two resource types behind one marker would
419    /// leave [`take`](Self::take) unable to tell a well-typed citation from a
420    /// peer naming the wrong object, so the ambiguity is refused outright.
421    fn record_marker<T: OpaqueResource>(&self) {
422        let marker = TypeId::of::<T::Marker>();
423        let previous = {
424            let mut markers = self.markers.lock().unwrap();
425            match markers.entry(marker) {
426                Entry::Occupied(entry) if entry.get().0 != TypeId::of::<T>() => Some(entry.get().1),
427                Entry::Occupied(_) => None,
428                Entry::Vacant(entry) => {
429                    entry.insert((TypeId::of::<T>(), std::any::type_name::<T>()));
430                    None
431                }
432            }
433        };
434        // Outside the lock: a panic here must not poison the map for the rest
435        // of the session.
436        if let Some(previous) = previous {
437            panic!(
438                "opaque marker `{}` is already registered for resource type `{}`; \
439                 it cannot also name `{}`",
440                std::any::type_name::<T::Marker>(),
441                previous,
442                std::any::type_name::<T>(),
443            );
444        }
445    }
446
447    pub(crate) fn register<T: OpaqueResource>(self: &Arc<Self>, value: T) -> Gift<T::Marker> {
448        self.record_marker::<T>();
449        let mut tables = self.tables.lock().unwrap();
450        let id = tables.next;
451        tables.next = tables
452            .next
453            .checked_add(1)
454            .expect("opaque identifiers exhausted");
455        let handle = Arc::new(LocalRef {
456            id,
457            serial: self.serial,
458            session: Arc::downgrade(self),
459        });
460        tables.local.insert(
461            id,
462            LocalEntry {
463                ty: TypeId::of::<T>(),
464                marker: TypeId::of::<T::Marker>(),
465                resource: Some(Arc::new(value)),
466                granted: 0,
467                handle: Arc::downgrade(&handle),
468            },
469        );
470        Gift::new(Inner::Local(handle))
471    }
472
473    pub(crate) fn acquire<T: OpaqueResource>(
474        &self,
475        value: Cite<T::Marker>,
476    ) -> Result<OpaqueGuard<T>, InvalidOpaque> {
477        // Unreachable for a citation that arrived over the wire: a `Cite` is
478        // decoded only from `WIRE_CITATION`, which resolves against this
479        // endpoint's own table and so is always local. Only a locally minted
480        // one can be remote, and `Gift::cite` refuses to mint that.
481        let Inner::Local(local) = &value.inner else {
482            return Err(InvalidOpaque);
483        };
484        self.check_serial(local.serial);
485        let tables = self.tables.lock().unwrap();
486        // Unreachable while this handle is alive: the entry is retired only
487        // once no handle points at it, and `cite` refuses an id with no entry
488        // rather than minting one.
489        let entry = tables.local.get(&local.id).ok_or(InvalidOpaque)?;
490        // Likewise unreachable: `cite` rejects a citation whose marker does not
491        // match the entry, and `record_marker` makes the marker determine the
492        // type. Kept as an integer compare guarding the downcast.
493        if entry.ty != TypeId::of::<T>() {
494            return Err(InvalidOpaque);
495        }
496        // The live case: `unregister` emptied the slot while the peer still
497        // held a reference, so a citation already in flight lands here.
498        let resource = entry.resource.as_ref().ok_or(InvalidOpaque)?;
499        Ok(OpaqueGuard(
500            resource
501                .clone()
502                .downcast::<T>()
503                .map_err(|_| InvalidOpaque)?,
504        ))
505    }
506
507    /// Empties the handle, returning the resource if this call held the last
508    /// reference to it.
509    ///
510    /// The registration itself survives until the peer has released every
511    /// reference; only the resource slot is cleared. On the `None` path the
512    /// resource is *not* restored to the table — outstanding [`OpaqueGuard`]s
513    /// keep it alive and it dies with the last one. Restoring it would
514    /// resurrect the table's own reference so the resource outlived every
515    /// guard, silently turning a close that races an in-flight write into a
516    /// no-op; on a pipe's send end that is a missing EOF and a hung reader.
517    pub(crate) fn unregister<T: OpaqueResource>(
518        &self,
519        value: Cite<T::Marker>,
520    ) -> Result<Option<T>, InvalidOpaque> {
521        // Unreachable for a decoded citation, as in `acquire`.
522        let Inner::Local(local) = &value.inner else {
523            return Err(InvalidOpaque);
524        };
525        self.check_serial(local.serial);
526        let mut tables = self.tables.lock().unwrap();
527        let entry = tables.local.get_mut(&local.id).ok_or(InvalidOpaque)?;
528        if entry.ty != TypeId::of::<T>() {
529            return Err(InvalidOpaque);
530        }
531        let resource = entry.resource.take().ok_or(InvalidOpaque)?;
532        let resource = resource.downcast::<T>().map_err(|_| InvalidOpaque)?;
533        Ok(Arc::try_unwrap(resource).ok())
534    }
535
536    /// Empties the handle, returning the resource if this call held the last
537    /// reference to it and *restoring* it if it did not.
538    ///
539    /// The recovering counterpart of [`unregister`](Self::unregister). On the
540    /// `None` path the resource goes back into the table under the same lock
541    /// that took it, so nothing observed it missing and the handle the peer
542    /// holds keeps working; the caller reports the operation busy without
543    /// having destroyed anything.
544    ///
545    /// That is only sound for an operation which does nothing else on the busy
546    /// path. `unregister` deliberately does not restore, because a close that
547    /// races an in-flight write must still take effect once the write finishes
548    /// — resurrecting the table's reference there would turn the close into a
549    /// silent no-op. Use this one where failing is genuinely a no-op, such as a
550    /// consuming conversion that the caller may retry.
551    pub(crate) fn try_unregister<T: OpaqueResource>(
552        &self,
553        value: Cite<T::Marker>,
554    ) -> Result<Option<T>, InvalidOpaque> {
555        // Unreachable for a decoded citation, as in `acquire`.
556        let Inner::Local(local) = &value.inner else {
557            return Err(InvalidOpaque);
558        };
559        self.check_serial(local.serial);
560        let mut tables = self.tables.lock().unwrap();
561        let entry = tables.local.get_mut(&local.id).ok_or(InvalidOpaque)?;
562        if entry.ty != TypeId::of::<T>() {
563            return Err(InvalidOpaque);
564        }
565        let resource = entry.resource.take().ok_or(InvalidOpaque)?;
566        let resource = match resource.downcast::<T>() {
567            Ok(resource) => resource,
568            Err(resource) => {
569                entry.resource = Some(resource);
570                return Err(InvalidOpaque);
571            }
572        };
573        match Arc::try_unwrap(resource) {
574            Ok(value) => Ok(Some(value)),
575            Err(shared) => {
576                entry.resource = Some(shared);
577                Ok(None)
578            }
579        }
580    }
581
582    /// Applies a release frame from the peer. Unknown ids are ignored: a
583    /// consuming operation races the peer's release by construction.
584    pub(crate) fn release(&self, id: u64, count: u32) {
585        let mut tables = self.tables.lock().unwrap();
586        let Some(entry) = tables.local.get_mut(&id) else {
587            return;
588        };
589        // Saturation deliberately immortalizes the entry for this session.
590        // Decrementing it could make a wrapped or otherwise unrepresentable
591        // grant total appear finite again.
592        if entry.granted != u32::MAX {
593            entry.granted = entry.granted.saturating_sub(count);
594        }
595        if entry.granted == 0 && entry.handle.upgrade().is_none() {
596            tables.local.remove(&id);
597        }
598    }
599
600    /// Records that a gift for `id` is being serialized, and returns the
601    /// escrow item holding the reference until the payload is committed.
602    fn gift(&self, handle: &Arc<LocalRef>) -> Escrowed {
603        let mut tables = self.tables.lock().unwrap();
604        if let Some(entry) = tables.local.get_mut(&handle.id) {
605            entry.granted = entry.granted.saturating_add(1);
606        }
607        Escrowed::Gift(handle.clone())
608    }
609
610    /// Mirrors an arriving gift for `id`, merging into the handle this
611    /// endpoint already holds when there is one.
612    fn receive(self: &Arc<Self>, id: u64) -> Inner {
613        let (handle, release) = {
614            let mut tables = self.tables.lock().unwrap();
615            let session = Arc::downgrade(self);
616            let entry = tables.remote.entry(id).or_insert_with(|| RemoteEntry {
617                granted: 0,
618                handle: Weak::new(),
619            });
620            entry.granted = entry.granted.saturating_add(1);
621            let release = if entry.granted >= GRANT_RELEASE_THRESHOLD {
622                let release = entry.granted - 1;
623                entry.granted = 1;
624                Some(release)
625            } else {
626                None
627            };
628            // A failed upgrade means the previous handle is mid-`Drop`. It will
629            // find the slot no longer pointing at it and leave the running total —
630            // including its own references and the one arriving now — to the fresh
631            // handle installed here.
632            let handle = if let Some(handle) = entry.handle.upgrade() {
633                handle
634            } else {
635                let handle = Arc::new(RemoteRef {
636                    id,
637                    serial: self.serial,
638                    session,
639                });
640                entry.handle = Arc::downgrade(&handle);
641                handle
642            };
643            (handle, release)
644        };
645        if let Some(count) = release {
646            self.sink.release(id, count);
647        }
648        Inner::Remote(handle)
649    }
650
651    /// Resolves an arriving citation back to a handle on the resource this
652    /// endpoint owns.
653    ///
654    /// Both failures mean the peer has named something it cannot name, and
655    /// both are refused rather than papered over.
656    ///
657    /// An entry registered under a marker other than the one the wire position
658    /// declares is the only thing standing between a peer and a guard on the
659    /// wrong resource.
660    ///
661    /// An unknown id means the counts have diverged. It cannot arise from a
662    /// race: `granted` rises when a gift is *serialized* and falls only on a
663    /// release, the entry is retired only once it reaches zero with no live
664    /// handle, and [`Escrowed::Citation`] holds the peer's reference until the
665    /// citing payload is fully written — so the release for a cited id is
666    /// always generated after the last fragment of the message citing it. A
667    /// peer citing a retired id is therefore counting differently than we are,
668    /// and nothing it says about this table can be trusted afterwards.
669    fn cite(self: &Arc<Self>, id: u64, marker: TypeId) -> Result<Inner, InvalidOpaque> {
670        let mut tables = self.tables.lock().unwrap();
671        let entry = tables.local.get_mut(&id).ok_or(InvalidOpaque)?;
672        if entry.marker != marker {
673            return Err(InvalidOpaque);
674        }
675        if let Some(handle) = entry.handle.upgrade() {
676            return Ok(Inner::Local(handle));
677        }
678        // The owner has dropped its last handle but the peer still holds
679        // references, so the entry outlived it. Install a fresh handle.
680        let handle = Arc::new(LocalRef {
681            id,
682            serial: self.serial,
683            session: Arc::downgrade(self),
684        });
685        entry.handle = Arc::downgrade(&handle);
686        Ok(Inner::Local(handle))
687    }
688
689    /// Resolves an arriving `(owner, id)` pair for a wire position declared to
690    /// hold a [`Gift`]: the sender owns the resource and this side mirrors it.
691    ///
692    /// Nothing is typechecked, because there is nothing here to check against —
693    /// the peer's table is the authority on the type of the peer's own
694    /// resource. The owner bit is, though: a citation in a gift position names
695    /// something this endpoint owns, which is not a reference the peer can
696    /// grant.
697    pub(crate) fn take_gift(self: &Arc<Self>, owner: u8, id: u64) -> Result<Inner, InvalidOpaque> {
698        if owner != WIRE_GIFT {
699            return Err(InvalidOpaque);
700        }
701        Ok(self.receive(id))
702    }
703
704    /// Resolves an arriving `(owner, id)` pair for a wire position declared to
705    /// hold a [`Cite`] of `marker`: a citation coming home, typechecked as such.
706    ///
707    /// Both a wrong owner bit and a citation the table cannot account for fail
708    /// the decode, which ends the connection — see [`Session::cite`]. Neither
709    /// can arise from a race, so tolerating either would mean accepting that
710    /// this endpoint and its peer disagree about the table.
711    pub(crate) fn take_cite(
712        self: &Arc<Self>,
713        owner: u8,
714        id: u64,
715        marker: TypeId,
716    ) -> Result<Inner, InvalidOpaque> {
717        if owner != WIRE_CITATION {
718            return Err(InvalidOpaque);
719        }
720        self.cite(id, marker)
721    }
722}
723
724/// A reference held on behalf of a message that is still being written.
725enum Escrowed {
726    /// A gift whose protocol increment is provisional until the payload is
727    /// fully written.
728    Gift(Arc<LocalRef>),
729    /// A citation. Never read: holding the `Arc` *is* the point, since that
730    /// is what keeps the last local handle alive and so orders any resulting
731    /// release strictly after the last payload fragment of the message that
732    /// cited it. Without it a small release frame could overtake a large body
733    /// under round-robin fragmentation, and the peer would retire the entry
734    /// before reassembling the message naming it.
735    Citation(#[allow(dead_code)] Arc<RemoteRef>),
736}
737
738/// The opaque references one outgoing message is holding.
739///
740/// Serializing moves references in here; the message's terminal outcome
741/// decides between [`commit`](Self::commit) and [`rescind`](Self::rescind).
742#[derive(Default)]
743pub(crate) struct Ledger {
744    items: Vec<Escrowed>,
745}
746
747impl Ledger {
748    /// Records an opaque encountered during serialization, returning its wire
749    /// `(owner, id)`.
750    pub(crate) fn put(&mut self, value: &Inner, session: &Arc<Session>) -> (u8, u64) {
751        match value {
752            Inner::Local(local) => {
753                // Writing a foreign id onto this session's wire would grant the
754                // peer a reference to whatever *this* session holds under that
755                // id, so the check matters more here than at redemption.
756                session.check_serial(local.serial);
757                self.items.push(session.gift(local));
758                (WIRE_GIFT, local.id)
759            }
760            Inner::Remote(remote) => {
761                session.check_serial(remote.serial);
762                self.items.push(Escrowed::Citation(remote.clone()));
763                (WIRE_CITATION, remote.id)
764            }
765        }
766    }
767
768    /// The payload was fully written, so every gift in it is irrevocably
769    /// transmitted. Dropping the citations here is what orders any release
770    /// they were suppressing after the message that cited them.
771    pub(crate) fn commit(self) {
772        // Every held reference drops here, on the far side of the payload.
773    }
774
775    /// The message was abandoned before its payload completed, so the peer
776    /// cannot have decoded it and no gift in it ever landed.
777    ///
778    /// Only ever correct for an abort that precedes payload completion.
779    /// Guessing "delivered" when it was not strands a reference until the
780    /// session ends; guessing "not delivered" when it was leaves the peer
781    /// holding a freed handle. Leak beats corruption.
782    pub(crate) fn rescind(self) {
783        for item in &self.items {
784            let Escrowed::Gift(handle) = item else {
785                continue;
786            };
787            let Some(session) = handle.session.upgrade() else {
788                continue;
789            };
790            let mut tables = session.tables.lock().unwrap();
791            if let Some(entry) = tables.local.get_mut(&handle.id)
792                && entry.granted != u32::MAX
793            {
794                entry.granted = entry.granted.saturating_sub(1);
795            }
796        }
797    }
798}
799
800/// Wraps a transport's handle sink with the session context an [`Opaque`]
801/// needs, so that serialization has exactly one threaded context rather than
802/// two parallel ones.
803pub(crate) struct SessionFrame<'a> {
804    pub(crate) inner: EncodeHandles,
805    pub(crate) session: &'a Arc<Session>,
806    pub(crate) ledger: &'a mut Ledger,
807}
808
809impl PutHandle for SessionFrame<'_> {
810    #[cfg(unix)]
811    fn put_handle(&mut self, handle: &dyn ErasedHandle) -> io::Result<u32> {
812        self.inner.put_handle(handle)
813    }
814
815    #[cfg(windows)]
816    fn put_handle(&mut self, handle: &dyn ErasedHandle) -> io::Result<usize> {
817        self.inner.put_handle(handle)
818    }
819
820    fn put_opaque(&mut self, opaque: &Inner) -> io::Result<(u8, u64)> {
821        Ok(self.ledger.put(opaque, self.session))
822    }
823}
824
825/// The deserialization counterpart of [`SessionFrame`].
826#[cfg(unix)]
827pub(crate) struct SessionHandles<'a> {
828    pub(crate) inner: ReceivedHandles,
829    pub(crate) session: &'a Arc<Session>,
830}
831
832#[cfg(unix)]
833impl TakeHandle for SessionHandles<'_> {
834    fn take_handle(&mut self, index: u32) -> io::Result<OwnedFd> {
835        self.inner.take_handle(index)
836    }
837
838    fn finish(&mut self) -> io::Result<()> {
839        self.inner.finish()
840    }
841    fn take_gift(&mut self, owner: u8, id: u64) -> io::Result<Inner> {
842        self.session
843            .take_gift(owner, id)
844            .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "invalid opaque reference"))
845    }
846
847    fn take_cite(&mut self, owner: u8, id: u64, marker: TypeId) -> io::Result<Inner> {
848        self.session
849            .take_cite(owner, id, marker)
850            .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "invalid opaque reference"))
851    }
852}
853
854#[cfg(test)]
855mod tests {
856    use super::*;
857    use std::sync::atomic::{AtomicBool, Ordering};
858
859    #[derive(Default)]
860    struct Recorder(Mutex<Vec<(u64, u32)>>);
861    impl ReleaseSink for Arc<Recorder> {
862        fn release(&self, id: u64, count: u32) {
863            self.0.lock().unwrap().push((id, count));
864        }
865    }
866
867    /// A session whose emitted releases the test can inspect.
868    fn session() -> (Arc<Session>, Arc<Recorder>) {
869        let recorder = Arc::new(Recorder::default());
870        (Session::new(Box::new(recorder.clone())), recorder)
871    }
872
873    struct Marker;
874    struct OtherMarker;
875    struct DropMarker;
876    struct Value(u32);
877    struct OtherValue;
878    struct DropValue(Arc<AtomicBool>);
879    impl OpaqueResource for Value {
880        type Marker = Marker;
881    }
882    impl OpaqueResource for OtherValue {
883        type Marker = OtherMarker;
884    }
885    impl OpaqueResource for DropValue {
886        type Marker = DropMarker;
887    }
888    impl Drop for DropValue {
889        fn drop(&mut self) {
890            self.0.store(true, Ordering::Relaxed);
891        }
892    }
893
894    /// The citation an owner gets back when the peer names one of its
895    /// resources to it, which is the only way a `Cite` legitimately reaches
896    /// `acquire`.
897    fn cited<M: 'static>(session: &Arc<Session>, gift: &Gift<M>) -> Cite<M> {
898        Cite::new(
899            session
900                .take_cite(WIRE_CITATION, gift.inner.id(), TypeId::of::<M>())
901                .unwrap(),
902        )
903    }
904
905    /// A second resource type laying claim to `Value`'s marker.
906    struct Impostor;
907    impl OpaqueResource for Impostor {
908        type Marker = Marker;
909    }
910
911    #[test]
912    #[should_panic(expected = "is already registered for resource type")]
913    fn two_resource_types_under_one_marker_panic_at_registration() {
914        let (session, _) = session();
915        let _opaque = session.register(Value(42));
916        let _conflict = session.register(Impostor);
917    }
918
919    #[test]
920    fn a_citation_naming_a_differently_typed_entry_is_rejected() {
921        let (session, _) = session();
922        let opaque = session.register(Value(42));
923        let id = opaque.inner.id();
924        assert!(
925            session
926                .take_cite(WIRE_CITATION, id, TypeId::of::<Marker>())
927                .is_ok()
928        );
929        assert!(
930            session
931                .take_cite(WIRE_CITATION, id, TypeId::of::<OtherMarker>())
932                .is_err()
933        );
934    }
935
936    #[test]
937    #[should_panic(expected = "different session")]
938    fn redeeming_an_opaque_against_another_session_panics() {
939        let (first, _) = session();
940        let (second, _) = session();
941        let opaque = first.register(Value(42));
942        let _ = second.acquire::<Value>(cited(&first, &opaque));
943    }
944
945    #[test]
946    #[should_panic(expected = "different session")]
947    fn serializing_an_opaque_into_another_session_panics() {
948        let (first, _) = session();
949        let (second, _) = session();
950        let opaque = first.register(Value(42));
951        Ledger::default().put(&opaque.inner, &second);
952    }
953
954    #[test]
955    fn guards_outlive_registration() {
956        let (session, _) = session();
957        let opaque = session.register(Value(42));
958        let guard = session.acquire::<Value>(cited(&session, &opaque)).unwrap();
959        assert!(
960            session
961                .unregister::<Value>(cited(&session, &opaque))
962                .unwrap()
963                .is_none()
964        );
965        assert_eq!(guard.0.0, 42);
966        assert!(session.acquire::<Value>(cited(&session, &opaque)).is_err());
967    }
968
969    #[test]
970    fn unregister_returns_exclusively_owned_value() {
971        let (session, _) = session();
972        let opaque = session.register(Value(42));
973        let value = session
974            .unregister::<Value>(cited(&session, &opaque))
975            .unwrap()
976            .unwrap();
977        assert_eq!(value.0, 42);
978    }
979
980    #[test]
981    fn try_unregister_restores_a_shared_value() {
982        let (session, _) = session();
983        let opaque = session.register(Value(42));
984        let guard = session.acquire::<Value>(cited(&session, &opaque)).unwrap();
985        assert!(
986            session
987                .try_unregister::<Value>(cited(&session, &opaque))
988                .unwrap()
989                .is_none()
990        );
991        drop(guard);
992        // Unlike `unregister`, the handle is still live afterwards, so a retry
993        // once the guard is gone succeeds.
994        assert_eq!(
995            session
996                .try_unregister::<Value>(cited(&session, &opaque))
997                .unwrap()
998                .unwrap()
999                .0,
1000            42
1001        );
1002    }
1003
1004    #[test]
1005    fn try_unregister_returns_exclusively_owned_value() {
1006        let (session, _) = session();
1007        let opaque = session.register(Value(42));
1008        let value = session
1009            .try_unregister::<Value>(cited(&session, &opaque))
1010            .unwrap()
1011            .unwrap();
1012        assert_eq!(value.0, 42);
1013        assert!(session.acquire::<Value>(cited(&session, &opaque)).is_err());
1014    }
1015
1016    #[test]
1017    fn wrong_type_does_not_remove_value() {
1018        let (session, _) = session();
1019        let opaque = session.register(Value(42));
1020        let wrong = Cite::<OtherMarker>::new(opaque.inner.clone());
1021        assert!(session.unregister::<OtherValue>(wrong).is_err());
1022        assert_eq!(
1023            session
1024                .acquire::<Value>(cited(&session, &opaque))
1025                .unwrap()
1026                .0
1027                .0,
1028            42
1029        );
1030    }
1031
1032    #[test]
1033    fn dropping_session_drops_registered_values() {
1034        let dropped = Arc::new(AtomicBool::new(false));
1035        let (session, _) = session();
1036        let opaque = session.register(DropValue(dropped.clone()));
1037        drop(opaque);
1038        drop(session);
1039        assert!(dropped.load(Ordering::Relaxed));
1040    }
1041
1042    #[test]
1043    fn dropping_the_last_local_handle_retires_an_ungifted_entry() {
1044        let (session, _) = session();
1045        let opaque = session.register(Value(42));
1046        drop(opaque);
1047        assert!(session.tables.lock().unwrap().local.is_empty());
1048    }
1049
1050    #[test]
1051    fn a_gifted_entry_outlives_its_local_handle_until_released() {
1052        let (session, _) = session();
1053        let opaque = session.register(Value(42));
1054        let Inner::Local(handle) = &opaque.inner else {
1055            unreachable!()
1056        };
1057        let escrow = session.gift(handle);
1058        drop(escrow);
1059        drop(opaque);
1060        // The peer still holds the reference, so the resource must survive.
1061        assert_eq!(session.tables.lock().unwrap().local.len(), 1);
1062        session.release(0, 1);
1063        assert!(session.tables.lock().unwrap().local.is_empty());
1064    }
1065
1066    #[test]
1067    fn cloning_an_opaque_does_not_grant_a_protocol_reference() {
1068        let (session, _) = session();
1069        let opaque = session.register(Value(42));
1070        let clones: Vec<_> = (0..8).map(|_| opaque.clone()).collect();
1071        assert_eq!(session.tables.lock().unwrap().local[&0].granted, 0);
1072        drop(clones);
1073        drop(opaque);
1074        assert!(session.tables.lock().unwrap().local.is_empty());
1075    }
1076
1077    #[test]
1078    fn rescinding_undoes_the_gift_increment() {
1079        let (session, _) = session();
1080        let opaque = session.register(Value(42));
1081        let mut ledger = Ledger::default();
1082        assert_eq!(ledger.put(&opaque.inner, &session), (WIRE_GIFT, 0));
1083        assert_eq!(session.tables.lock().unwrap().local[&0].granted, 1);
1084        ledger.rescind();
1085        assert_eq!(session.tables.lock().unwrap().local[&0].granted, 0);
1086    }
1087
1088    #[test]
1089    fn saturated_owner_grant_count_is_immortal() {
1090        let (session, _) = session();
1091        let opaque = session.register(Value(42));
1092        let Inner::Local(handle) = &opaque.inner else {
1093            unreachable!()
1094        };
1095        session
1096            .tables
1097            .lock()
1098            .unwrap()
1099            .local
1100            .get_mut(&0)
1101            .unwrap()
1102            .granted = u32::MAX - 1;
1103
1104        let escrow = session.gift(handle);
1105        assert_eq!(session.tables.lock().unwrap().local[&0].granted, u32::MAX);
1106        session.release(0, u32::MAX);
1107        assert_eq!(session.tables.lock().unwrap().local[&0].granted, u32::MAX);
1108
1109        let ledger = Ledger {
1110            items: vec![escrow],
1111        };
1112        ledger.rescind();
1113        assert_eq!(session.tables.lock().unwrap().local[&0].granted, u32::MAX);
1114        drop(opaque);
1115        assert!(session.tables.lock().unwrap().local.contains_key(&0));
1116    }
1117
1118    #[test]
1119    fn remote_grants_are_collapsed_at_the_high_threshold() {
1120        let (session, recorder) = session();
1121        let first: Gift<Marker> = Gift::new(session.take_gift(WIRE_GIFT, 7).unwrap());
1122        session
1123            .tables
1124            .lock()
1125            .unwrap()
1126            .remote
1127            .get_mut(&7)
1128            .unwrap()
1129            .granted = GRANT_RELEASE_THRESHOLD - 1;
1130
1131        let second: Gift<Marker> = Gift::new(session.take_gift(WIRE_GIFT, 7).unwrap());
1132        assert_eq!(first, second);
1133        assert_eq!(session.tables.lock().unwrap().remote[&7].granted, 1);
1134        assert_eq!(
1135            *recorder.0.lock().unwrap(),
1136            vec![(7, GRANT_RELEASE_THRESHOLD - 1)]
1137        );
1138
1139        drop(first);
1140        drop(second);
1141        assert_eq!(
1142            *recorder.0.lock().unwrap(),
1143            vec![(7, GRANT_RELEASE_THRESHOLD - 1), (7, 1)]
1144        );
1145    }
1146
1147    #[test]
1148    fn committing_leaves_the_gift_increment_in_place() {
1149        let (session, _) = session();
1150        let opaque = session.register(Value(42));
1151        let mut ledger = Ledger::default();
1152        ledger.put(&opaque.inner, &session);
1153        ledger.commit();
1154        assert_eq!(session.tables.lock().unwrap().local[&0].granted, 1);
1155    }
1156
1157    #[test]
1158    fn citing_an_opaque_has_no_protocol_effect() {
1159        let (session, recorder) = session();
1160        let opaque: Gift<Marker> = Gift::new(session.take_gift(WIRE_GIFT, 7).unwrap());
1161        let mut ledger = Ledger::default();
1162        assert_eq!(
1163            ledger.put(&opaque.cite().inner, &session),
1164            (WIRE_CITATION, 7)
1165        );
1166        ledger.commit();
1167        // Still exactly the one reference the gift granted.
1168        drop(opaque);
1169        assert_eq!(*recorder.0.lock().unwrap(), vec![(7, 1)]);
1170    }
1171
1172    #[test]
1173    fn repeated_gifts_of_one_id_accumulate_into_a_single_release() {
1174        let (session, recorder) = session();
1175        let first: Gift<Marker> = Gift::new(session.take_gift(WIRE_GIFT, 3).unwrap());
1176        let second: Gift<Marker> = Gift::new(session.take_gift(WIRE_GIFT, 3).unwrap());
1177        assert_eq!(first, second);
1178        drop(first);
1179        assert!(recorder.0.lock().unwrap().is_empty());
1180        drop(second);
1181        assert_eq!(*recorder.0.lock().unwrap(), vec![(3, 2)]);
1182    }
1183
1184    /// The owner bit is the peer's to choose, so a wire position declared to
1185    /// hold one kind must refuse the other where the expectation is still
1186    /// known — at decode, not at redemption.
1187    #[test]
1188    fn a_gift_in_a_citation_position_is_rejected() {
1189        let (session, _) = session();
1190        let opaque = session.register(Value(42));
1191        let id = opaque.inner.id();
1192        assert!(
1193            session
1194                .take_cite(WIRE_GIFT, id, TypeId::of::<Marker>())
1195                .is_err()
1196        );
1197    }
1198
1199    #[test]
1200    fn a_citation_in_a_gift_position_is_rejected() {
1201        let (session, _) = session();
1202        assert!(session.take_gift(WIRE_CITATION, 7).is_err());
1203    }
1204
1205    /// A citation says "the reference you granted me", so pointing one at
1206    /// one's own table is a local logic error rather than a protocol event.
1207    #[test]
1208    #[should_panic(expected = "cannot cite a resource this endpoint owns")]
1209    fn citing_a_resource_this_endpoint_owns_panics() {
1210        let (session, _) = session();
1211        let _ = session.register(Value(42)).cite();
1212    }
1213
1214    #[test]
1215    #[should_panic(expected = "cannot gift a resource this endpoint does not own")]
1216    fn gifting_a_resource_the_peer_owns_panics() {
1217        let (session, _) = session();
1218        let mirrored: Gift<Marker> = Gift::new(session.take_gift(WIRE_GIFT, 7).unwrap());
1219        let _ = postcard::to_allocvec(&mirrored);
1220    }
1221
1222    /// The mirror image: a citation decoded on the owning side holds a local
1223    /// handle, and putting it back on the wire would name it to a peer that
1224    /// never granted it.
1225    #[test]
1226    #[should_panic(expected = "cannot cite a resource this endpoint owns")]
1227    fn re_serializing_a_citation_that_came_home_panics() {
1228        let (session, _) = session();
1229        let opaque = session.register(Value(42));
1230        let _ = postcard::to_allocvec(&cited(&session, &opaque));
1231    }
1232
1233    #[test]
1234    fn a_citation_for_an_unknown_id_is_rejected() {
1235        let (session, _) = session();
1236        assert!(
1237            session
1238                .take_cite(WIRE_CITATION, 99, TypeId::of::<Marker>())
1239                .is_err()
1240        );
1241    }
1242
1243    /// The peer may still be citing a resource the owner has closed, so the
1244    /// entry has to outlive the resource: emptied is a redemption failure,
1245    /// absent is a protocol violation.
1246    #[test]
1247    fn a_citation_for_an_unregistered_but_still_granted_id_resolves() {
1248        let (session, _) = session();
1249        let opaque = session.register(Value(42));
1250        let id = opaque.inner.id();
1251        // Grant the peer a reference, as sending the opaque would.
1252        Ledger::default().put(&opaque.inner, &session);
1253        session
1254            .unregister::<Value>(cited(&session, &opaque))
1255            .unwrap();
1256        let cite = Cite::<Marker>::new(
1257            session
1258                .take_cite(WIRE_CITATION, id, TypeId::of::<Marker>())
1259                .unwrap(),
1260        );
1261        assert!(session.acquire::<Value>(cite).is_err());
1262    }
1263
1264    #[test]
1265    fn plain_postcard_use_panics() {
1266        let (session, _) = session();
1267        let opaque = session.register(Value(42));
1268        assert!(
1269            std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
1270                postcard::to_allocvec(&opaque)
1271            }))
1272            .is_err()
1273        );
1274        assert!(std::panic::catch_unwind(|| postcard::from_bytes::<Gift<Marker>>(&[0])).is_err());
1275    }
1276
1277    #[test]
1278    fn wire_form_survives_packing_both_owners() {
1279        for owner in [WIRE_GIFT, WIRE_CITATION] {
1280            for id in [0, 1, 42, u32::MAX as u64, (1 << 62) - 1] {
1281                assert_eq!(unpack_wire(pack_wire(owner, id)), (owner, id));
1282            }
1283        }
1284    }
1285
1286    #[test]
1287    fn releasing_an_unknown_id_is_ignored() {
1288        let (session, _) = session();
1289        session.release(1234, 5);
1290    }
1291}