Skip to main content

vox_core/
lib.rs

1//! Core implementations for the vox connectivity layer.
2//!
3//! This crate provides concrete implementations of the traits defined in
4//! [`vox_types`]:
5//!
6//! - [`BareConduit`]: wraps a raw `Link` with postcard serialization.
7//!   No reconnect, no reliability. For localhost, SHM, testing.
8//! - `StableConduit` (TODO): wraps a Link + seq/ack/replay with
9//!   bytes-based replay buffer. Handles reconnect transparently.
10
11mod bare_conduit;
12pub use bare_conduit::*;
13pub use vox_types::TransportMode;
14
15mod handshake;
16pub use handshake::*;
17
18mod into_conduit;
19pub use into_conduit::*;
20
21mod operation_store;
22pub use operation_store::*;
23
24mod transport_prologue;
25pub use transport_prologue::*;
26
27mod stable_conduit;
28pub use stable_conduit::*;
29
30#[cfg(not(target_arch = "wasm32"))]
31mod memory_link;
32#[cfg(not(target_arch = "wasm32"))]
33pub use memory_link::*;
34
35mod session;
36pub use session::*;
37
38mod driver;
39pub use driver::*;
40
41use facet_reflect::Partial;
42use vox_types::{Backing, SelfRef};
43
44/// Pre-built translation plan for deserializing the `Message` wire type.
45///
46/// Built once from the peer's schema (received during handshake) and our
47/// local schema. Stored in the conduit's Rx half and used for every
48/// incoming message.
49pub struct MessagePlan {
50    pub plan: vox_postcard::plan::TranslationPlan,
51    pub registry: vox_types::SchemaRegistry,
52}
53
54impl MessagePlan {
55    /// Build a message plan from the handshake result's schema exchange.
56    pub fn from_handshake(result: &vox_types::HandshakeResult) -> Result<Self, String> {
57        use vox_postcard::plan::{PlanInput, SchemaSet, build_plan};
58
59        if result.peer_schema.is_empty() || result.our_schema.is_empty() {
60            // No schemas exchanged — fall back to identity plan
61            let plan = vox_postcard::build_identity_plan(
62                <vox_types::Message<'static> as facet::Facet<'static>>::SHAPE,
63            );
64            return Ok(MessagePlan {
65                plan,
66                registry: vox_types::SchemaRegistry::new(),
67            });
68        }
69
70        let remote = SchemaSet::from_schemas(result.peer_schema.clone());
71        let local = SchemaSet::from_schemas(result.our_schema.clone());
72
73        let plan = build_plan(&PlanInput {
74            remote: &remote,
75            local: &local,
76        })
77        .map_err(|e| format!("failed to build message translation plan: {e}"))?;
78
79        Ok(MessagePlan {
80            plan,
81            registry: remote.registry,
82        })
83    }
84}
85
86/// Deserialize postcard-encoded `backing` bytes into `T` in place, returning a
87/// [`vox_types::SelfRef`] that keeps the backing storage alive for the value.
88// r[impl zerocopy.framing.value]
89pub(crate) fn deserialize_postcard<T: facet::Facet<'static>>(
90    backing: Backing,
91) -> Result<SelfRef<T>, vox_postcard::DeserializeError> {
92    let plan = vox_postcard::build_identity_plan(T::SHAPE);
93    let registry = vox_types::SchemaRegistry::new();
94    deserialize_postcard_with_plan(backing, &plan, &registry)
95}
96
97/// Deserialize postcard-encoded `backing` bytes into `T` using a pre-built
98/// translation plan and schema registry for the remote peer's type layout.
99// r[impl zerocopy.framing.value]
100pub(crate) fn deserialize_postcard_with_plan<T: facet::Facet<'static>>(
101    backing: Backing,
102    plan: &vox_postcard::plan::TranslationPlan,
103    registry: &vox_types::SchemaRegistry,
104) -> Result<SelfRef<T>, vox_postcard::DeserializeError> {
105    // SAFETY: backing is heap-allocated with a stable address.
106    // The SelfRef::try_new contract guarantees value is dropped before backing.
107    SelfRef::try_new(backing, |bytes| {
108        let mut value = std::mem::MaybeUninit::<T>::uninit();
109        let ptr = facet_core::PtrUninit::from_maybe_uninit(&mut value);
110
111        // SAFETY: ptr points to valid, aligned, properly-sized memory for T.
112        #[allow(unsafe_code)]
113        let partial: Partial<'_, true> = unsafe { Partial::from_raw_with_shape(ptr, T::SHAPE) }
114            .map_err(|e| vox_postcard::DeserializeError::ReflectError(e.to_string()))?;
115
116        let partial = vox_postcard::deserialize_into(partial, bytes, plan, registry)?;
117
118        partial
119            .finish_in_place()
120            .map_err(|e| vox_postcard::DeserializeError::ReflectError(e.to_string()))?;
121
122        // SAFETY: finish_in_place succeeded, so value is fully initialized.
123        #[allow(unsafe_code)]
124        Ok(unsafe { value.assume_init() })
125    })
126}
127
128pub mod testing;
129
130#[cfg(test)]
131mod tests;