nichlink_plugin_host/
lazy_wasm.rs1use std::collections::BTreeMap;
5use std::sync::Mutex;
6use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
7
8use arc_swap::ArcSwapOption;
9use nichlink_run_method::{FlowContract, FrameworkId, PluginMode, VerifiedPluginArtifact};
10
11use crate::{HostError, PluginInstance, WasmBackend};
12
13#[path = "lazy_wasm/slot_state.rs"]
14mod slot_state;
15use slot_state::SlotState;
16
17pub use nichlink_run_method::PluginChannel as ValidationChannel;
25
26#[derive(Clone, Copy, Debug)]
29pub struct WasmPluginSlot {
30 pub name: &'static str,
33 pub framework: FrameworkId,
36 pub mode: PluginMode,
39 pub contract: FlowContract,
42 pub channels: &'static [ValidationChannel],
45}
46
47impl WasmPluginSlot {
48 pub const fn new(
51 name: &'static str,
52 framework: FrameworkId,
53 mode: PluginMode,
54 contract: FlowContract,
55 channels: &'static [ValidationChannel],
56 ) -> Self {
57 Self {
58 name,
59 framework,
60 mode,
61 contract,
62 channels,
63 }
64 }
65}
66
67pub struct WasmPluginTable {
70 backend: WasmBackend,
71 slots: BTreeMap<&'static str, SlotState>,
72}
73
74impl WasmPluginTable {
75 pub fn new(slots: &'static [WasmPluginSlot]) -> Result<Self, HostError> {
78 Self::with_backend(slots, WasmBackend::default())
79 }
80
81 pub fn with_backend(
84 slots: &'static [WasmPluginSlot],
85 backend: WasmBackend,
86 ) -> Result<Self, HostError> {
87 let mut states = BTreeMap::new();
88 for definition in slots {
89 if definition.name.trim().is_empty() {
90 return Err(HostError::Slot("plugin slot name is empty".to_owned()));
91 }
92 if definition.channels.is_empty() {
93 return Err(HostError::Slot(format!(
94 "plugin slot `{}` has no validation channel",
95 definition.name
96 )));
97 }
98 if states
99 .insert(
100 definition.name,
101 SlotState {
102 definition: *definition,
103 active: ArcSwapOption::empty(),
104 pending: Mutex::new(None),
105 has_pending: AtomicBool::new(false),
106 next_generation: AtomicU64::new(0),
107 activation_error: Mutex::new(None),
108 },
109 )
110 .is_some()
111 {
112 return Err(HostError::Slot(format!(
113 "duplicate plugin slot `{}`",
114 definition.name
115 )));
116 }
117 }
118 Ok(Self {
119 backend,
120 slots: states,
121 })
122 }
123
124 pub fn install(
127 &self,
128 slot: &str,
129 channel: ValidationChannel,
130 artifact: VerifiedPluginArtifact,
131 ) -> Result<u64, HostError> {
132 self.slot(slot)?.install(channel, artifact)
133 }
134
135 pub fn call(&self, slot: &str, operation: &str, input: &[u8]) -> Result<Vec<u8>, HostError> {
138 let state = self.slot(slot)?;
139 state.activate(self.backend)?;
140 let active = state
141 .active
142 .load_full()
143 .ok_or_else(|| HostError::Slot(format!("plugin slot `{slot}` is not installed")))?;
144 active.instance.call(operation, input)
145 }
146
147 pub fn is_loaded(&self, slot: &str) -> Result<bool, HostError> {
150 let state = self.slot(slot)?;
151 Ok(!state.has_pending.load(Ordering::Acquire) && state.active.load().is_some())
152 }
153
154 pub fn generation(&self, slot: &str) -> Result<Option<u64>, HostError> {
157 Ok(self
158 .slot(slot)?
159 .active
160 .load_full()
161 .map(|active| active.generation))
162 }
163
164 pub fn activation_error(&self, slot: &str) -> Result<Option<String>, HostError> {
178 self.slot(slot)?
179 .activation_error
180 .lock()
181 .map(|error| error.clone())
182 .map_err(|_| HostError::State("plugin slot lock was poisoned".to_owned()))
183 }
184
185 fn slot(&self, name: &str) -> Result<&SlotState, HostError> {
186 self.slots
187 .get(name)
188 .ok_or_else(|| HostError::Slot(format!("unknown plugin slot `{name}`")))
189 }
190}
191
192fn validate_artifact(
193 slot: WasmPluginSlot,
194 channel: ValidationChannel,
195 artifact: &VerifiedPluginArtifact,
196) -> Result<(), HostError> {
197 nichlink_run_method::validate_artifact(
198 slot.name,
199 slot.framework,
200 slot.mode,
201 slot.contract,
202 slot.channels,
203 channel,
204 artifact,
205 )
206 .map_err(|error| match error {
207 nichlink_run_method::SlotValidationError::Policy(message) => HostError::Policy(message),
208 nichlink_run_method::SlotValidationError::Contract(message) => HostError::Contract(message),
209 })
210}