Skip to main content

hara_native/runtime/
session_live.rs

1#[cfg(all(feature = "bytecode-observation", feature = "bytecode-instrumentation"))]
2use crate::live_session::InstrumentedHbcLiveSession;
3use crate::live_session::{
4    InstrumentedInterpreterLiveSession, LiveSession, LiveSessionCapabilities, LiveSessionCommand,
5    LiveSessionError, LiveSessionReply, LiveSessionRequest, LiveSessionState, LiveSource,
6};
7
8#[derive(Default)]
9struct SessionLiveRegistry {
10    entries: HashMap<String, Box<dyn LiveSession>>,
11}
12
13impl SessionLiveRegistry {
14    fn dispose_all(&mut self) {
15        for live_session in self.entries.values_mut() {
16            let _ = live_session.dispatch_command(LiveSessionCommand::Dispose);
17        }
18        self.entries.clear();
19    }
20}
21
22impl Drop for SessionLiveRegistry {
23    fn drop(&mut self) {
24        self.dispose_all();
25    }
26}
27
28impl Session {
29    fn ensure_live_owner_active(&self) -> Result<(), LiveSessionError> {
30        self.ensure_active()
31            .map_err(|message| LiveSessionError::new("live-session/owner-closed", message))
32    }
33
34    fn ensure_live_session_identity_available(
35        &self,
36        live_session_id: &str,
37    ) -> Result<(), LiveSessionError> {
38        if self.live_sessions.entries.contains_key(live_session_id) {
39            Err(live_session_already_exists(live_session_id))
40        } else {
41            Ok(())
42        }
43    }
44
45    /// Transfers one backend-neutral live session into this Session's private
46    /// lifecycle. Live-session identities cannot be reused, including after a
47    /// nested session has been cancelled or disposed.
48    pub fn register_live_session(
49        &mut self,
50        mut live_session: Box<dyn LiveSession>,
51    ) -> Result<LiveSessionState, LiveSessionError> {
52        self.ensure_live_owner_active()?;
53        let state = live_session.state();
54        if state.session_id.trim().is_empty() {
55            let _ = live_session.dispatch_command(LiveSessionCommand::Dispose);
56            return Err(LiveSessionError::new(
57                "live-session/invalid-identity",
58                "live-session id must not be empty",
59            ));
60        }
61        if self.live_sessions.entries.contains_key(&state.session_id) {
62            let _ = live_session.dispatch_command(LiveSessionCommand::Dispose);
63            return Err(live_session_already_exists(&state.session_id));
64        }
65        self.live_sessions
66            .entries
67            .insert(state.session_id.clone(), live_session);
68        Ok(state)
69    }
70
71    /// Starts the authoritative interpreter target as a built-in controlling
72    /// instrument over this Session's Runtime-owned instrumentation hub.
73    pub fn start_interpreter_live_session(
74        &mut self,
75        live_session_id: impl Into<String>,
76        source: LiveSource,
77    ) -> Result<LiveSessionState, LiveSessionError> {
78        self.ensure_live_owner_active()?;
79        let live_session_id = live_session_id.into();
80        self.ensure_live_session_identity_available(&live_session_id)?;
81        let owner_session_id = self.name().to_owned();
82        let live_session = InstrumentedInterpreterLiveSession::start(
83            self.runtime()
84                .map_err(|message| LiveSessionError::new("live-session/owner-closed", message))?,
85            owner_session_id,
86            live_session_id,
87            source,
88        )?;
89        self.register_live_session(Box::new(live_session))
90    }
91
92    /// Starts the authoritative HBC Machine as a built-in controlling
93    /// instrument over this Session's Runtime-owned instrumentation hub.
94    #[cfg(all(feature = "bytecode-observation", feature = "bytecode-instrumentation"))]
95    pub fn start_hbc_live_session(
96        &mut self,
97        live_session_id: impl Into<String>,
98        source: LiveSource,
99    ) -> Result<LiveSessionState, LiveSessionError> {
100        self.ensure_live_owner_active()?;
101        let live_session_id = live_session_id.into();
102        self.ensure_live_session_identity_available(&live_session_id)?;
103        let owner_session_id = self.name().to_owned();
104        let live_session = InstrumentedHbcLiveSession::start(
105            self.runtime()
106                .map_err(|message| LiveSessionError::new("live-session/owner-closed", message))?,
107            owner_session_id,
108            live_session_id,
109            source,
110        )?;
111        self.register_live_session(Box::new(live_session))
112    }
113
114    /// Starts the authoritative HBC Machine from an already validated HBC0
115    /// artifact. Source metadata is retained for revision fencing, while the
116    /// live target avoids a second source compilation during startup.
117    #[cfg(all(feature = "bytecode-observation", feature = "bytecode-instrumentation"))]
118    pub fn start_hbc_live_session_from_artifact(
119        &mut self,
120        live_session_id: impl Into<String>,
121        source: LiveSource,
122        artifact: &[u8],
123    ) -> Result<LiveSessionState, LiveSessionError> {
124        self.ensure_live_owner_active()?;
125        let live_session_id = live_session_id.into();
126        self.ensure_live_session_identity_available(&live_session_id)?;
127        let owner_session_id = self.name().to_owned();
128        let live_session = InstrumentedHbcLiveSession::start_from_artifact(
129            self.runtime()
130                .map_err(|message| LiveSessionError::new("live-session/owner-closed", message))?,
131            owner_session_id,
132            live_session_id,
133            source,
134            artifact,
135        )?;
136        self.register_live_session(Box::new(live_session))
137    }
138
139    /// Starts a prepared whole-Wasm session. Whole-Wasm exposes only the
140    /// operations its synchronous prepared backend can implement honestly.
141    #[cfg(all(feature = "whole-wasm", not(target_arch = "wasm32")))]
142    pub fn start_whole_wasm_live_session(
143        &mut self,
144        live_session_id: impl Into<String>,
145        source: LiveSource,
146    ) -> Result<LiveSessionState, LiveSessionError> {
147        self.ensure_live_owner_active()?;
148        let live_session_id = live_session_id.into();
149        self.ensure_live_session_identity_available(&live_session_id)?;
150        let owner_session_id = self.name().to_owned();
151        let runtime = self
152            .runtime()
153            .map_err(|message| LiveSessionError::new("live-session/owner-closed", message))?;
154        let live_session = crate::live_session::WholeWasmLiveSession::start(
155            runtime,
156            owner_session_id,
157            live_session_id,
158            source,
159        )?;
160        self.register_live_session(Box::new(live_session))
161    }
162
163    /// Starts a prepared whole-Wasm session from an already compiled HNW0
164    /// artifact, avoiding source compilation at session startup.
165    #[cfg(all(feature = "whole-wasm", not(target_arch = "wasm32")))]
166    pub fn start_whole_wasm_live_session_from_artifact(
167        &mut self,
168        live_session_id: impl Into<String>,
169        source: LiveSource,
170        artifact: &[u8],
171    ) -> Result<LiveSessionState, LiveSessionError> {
172        self.ensure_live_owner_active()?;
173        let live_session_id = live_session_id.into();
174        self.ensure_live_session_identity_available(&live_session_id)?;
175        let owner_session_id = self.name().to_owned();
176        let runtime = self
177            .runtime()
178            .map_err(|message| LiveSessionError::new("live-session/owner-closed", message))?;
179        let live_session = crate::live_session::WholeWasmLiveSession::from_artifact(
180            runtime,
181            owner_session_id,
182            live_session_id,
183            source,
184            artifact.to_vec(),
185        )?;
186        self.register_live_session(Box::new(live_session))
187    }
188
189    /// Compatibility-only feature slice for builds that explicitly enable the
190    /// old observation feature without the shared instrumentation probe.
191    #[cfg(all(
192        feature = "bytecode-observation",
193        not(feature = "bytecode-instrumentation")
194    ))]
195    pub fn start_hbc_live_session(
196        &mut self,
197        live_session_id: impl Into<String>,
198        source: LiveSource,
199    ) -> Result<LiveSessionState, LiveSessionError> {
200        self.ensure_live_owner_active()?;
201        let live_session_id = live_session_id.into();
202        self.ensure_live_session_identity_available(&live_session_id)?;
203        let live_session =
204            crate::live_session::BytecodeLiveSession::compile(live_session_id, source)?;
205        self.register_live_session(Box::new(live_session))
206    }
207
208    /// Compatibility-only artifact constructor for observation builds that do
209    /// not include the shared instrumentation probe.
210    #[cfg(all(
211        feature = "bytecode-observation",
212        not(feature = "bytecode-instrumentation")
213    ))]
214    pub fn start_hbc_live_session_from_artifact(
215        &mut self,
216        live_session_id: impl Into<String>,
217        source: LiveSource,
218        artifact: &[u8],
219    ) -> Result<LiveSessionState, LiveSessionError> {
220        self.ensure_live_owner_active()?;
221        let live_session_id = live_session_id.into();
222        self.ensure_live_session_identity_available(&live_session_id)?;
223        let live_session = crate::live_session::BytecodeLiveSession::from_artifact(
224            live_session_id,
225            source.source_id(),
226            source.revision(),
227            artifact,
228        )?;
229        self.register_live_session(Box::new(live_session))
230    }
231
232    pub fn live_session_ids(&self) -> Vec<String> {
233        let mut ids = self
234            .live_sessions
235            .entries
236            .keys()
237            .cloned()
238            .collect::<Vec<_>>();
239        ids.sort();
240        ids
241    }
242
243    pub fn live_session_count(&self) -> usize {
244        self.live_sessions.entries.len()
245    }
246
247    pub fn live_session_state(
248        &self,
249        live_session_id: &str,
250    ) -> Result<LiveSessionState, LiveSessionError> {
251        self.ensure_live_owner_active()?;
252        self.live_sessions
253            .entries
254            .get(live_session_id)
255            .map(|live_session| live_session.state())
256            .ok_or_else(|| live_session_not_found(live_session_id))
257    }
258
259    pub fn live_session_capabilities(
260        &self,
261        live_session_id: &str,
262    ) -> Result<LiveSessionCapabilities, LiveSessionError> {
263        self.ensure_live_owner_active()?;
264        self.live_sessions
265            .entries
266            .get(live_session_id)
267            .map(|live_session| live_session.capabilities())
268            .ok_or_else(|| live_session_not_found(live_session_id))
269    }
270
271    /// Dispatches one fenced command to a live session owned by this Session.
272    /// The request is never routed through Sandbox and backend objects never
273    /// leave the owning Session.
274    pub fn dispatch_live_session(
275        &mut self,
276        request: LiveSessionRequest,
277    ) -> Result<LiveSessionReply, LiveSessionError> {
278        self.ensure_live_owner_active()?;
279        let live_session_id = request.session_id.clone();
280        self.live_sessions
281            .entries
282            .get_mut(&live_session_id)
283            .ok_or_else(|| live_session_not_found(&live_session_id))?
284            .dispatch(request)
285    }
286}
287
288fn live_session_already_exists(live_session_id: &str) -> LiveSessionError {
289    LiveSessionError::new(
290        "live-session/already-exists",
291        format!("live session identity cannot be reused: {live_session_id}"),
292    )
293}
294
295fn live_session_not_found(live_session_id: &str) -> LiveSessionError {
296    LiveSessionError::new(
297        "live-session/not-found",
298        format!("unknown live session: {live_session_id}"),
299    )
300}
301
302fn private_sandbox_session(entry_namespace: &str, mut runtime: Runtime) -> Session {
303    runtime.use_namespace(entry_namespace);
304    let id = SessionId::parse("SANDBOX").expect("SANDBOX is a valid session identifier");
305    Session::open(SessionSpec::new(id, SessionAuthorityPolicy::ZERO), runtime)
306}
307
308/// Constructs the zero-authority private Session owned by an external sandbox
309/// provider. The returned Session may own live sessions, while Sandbox itself
310/// retains only its coarse eval/call/cancel/close contract.
311#[cfg(not(target_arch = "wasm32"))]
312pub fn restricted_sandbox_session(entry_namespace: &str) -> Session {
313    private_sandbox_session(entry_namespace, Runtime::sandbox())
314}
315
316/// Constructs a zero-authority private sandbox Session with exactly one
317/// caller-supplied fully-qualified Host/call authority boundary.
318#[cfg(not(target_arch = "wasm32"))]
319pub fn restricted_sandbox_session_with_host(
320    entry_namespace: &str,
321    handler: Rc<dyn Fn(String, String, Vec<core::Value>) -> Result<core::Value, String>>,
322) -> Session {
323    private_sandbox_session(
324        entry_namespace,
325        restricted_sandbox_runtime_with_host(handler),
326    )
327}