1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
use std::sync::Arc;
use rkyv::util::AlignedVec;
use crate::cache::{
call_filter_hook_scoped, call_observe_hook, call_shape_hook_scoped,
call_transform_response_hook_scoped, call_transform_sse_event_hook_scoped,
};
use crate::cell::{LoadedPluginSlot, PluginCell};
use crate::error::WasmtimeRuntimeError;
use crate::policy::{PluginWireBounds, ShapeOriginPolicy};
use crate::{HotEngineConfig, SlotKind};
/// Wire-level dispatch handle for a single loaded plugin slot.
///
/// Snapshots the slot's [`PluginCell`] at construction time so
/// hot-swap replacements on the same [`RuntimeSlotKey`] cannot
/// disturb an in-flight view — each `DynamicView` keeps the cells
/// it was built with until it is itself replaced.
///
/// All methods take and return raw rkyv-encoded bytes; callers own
/// the host↔wire type conversion.
pub struct WasmPluginWireDispatch {
pub(crate) cell: Arc<PluginCell>,
pub(crate) kind: SlotKind,
pub(crate) config: Arc<HotEngineConfig>,
}
impl WasmPluginWireDispatch {
/// Snapshot the current cell from `slot` for the lifetime of this handle.
pub fn from_slot(slot: Arc<LoadedPluginSlot>, config: Arc<HotEngineConfig>) -> Self {
let cell = slot.current.load_full();
let kind = slot.kind;
Self { cell, kind, config }
}
/// The [`SlotKind`] this dispatch handle was built for.
pub fn kind(&self) -> SlotKind {
self.kind
}
/// Shareable config handle (wire bounds, origin policy, etc.).
pub fn config(&self) -> &Arc<HotEngineConfig> {
&self.config
}
/// Wire bounds from the runtime config.
pub fn wire_bounds(&self) -> &PluginWireBounds {
&self.config.wire_bounds
}
/// Shape origin policy from the runtime config.
pub fn shape_origin_policy(&self) -> ShapeOriginPolicy {
self.config.shape_origin_policy
}
/// Whether cookie headers should be stripped before crossing the plugin boundary.
pub fn cookie_redaction(&self) -> bool {
self.config.cookie_redaction
}
/// Dispatch a filter call. `input` must be rkyv-encoded `FilterRequest` bytes.
/// Returns rkyv-encoded `FilterResponse` bytes.
pub fn call_filter(&self, input: &[u8]) -> Result<Vec<u8>, WasmtimeRuntimeError> {
self.call_filter_scoped(input, <[u8]>::to_vec)
}
/// Dispatch a filter call and consume its checked output while guest memory is borrowed.
///
/// The callback receives only the bounded guest-memory slice. Its higher-ranked lifetime
/// prevents that slice from escaping in the return value:
///
/// ```compile_fail
/// use cc_lb_runtime_wasmtime::{WasmPluginWireDispatch, WasmtimeRuntimeError};
///
/// fn escape_guest_output<'a>(
/// dispatch: &'a WasmPluginWireDispatch,
/// input: &'a [u8],
/// ) -> Result<&'a [u8], WasmtimeRuntimeError> {
/// dispatch.call_filter_scoped(input, |guest_output| guest_output)
/// }
/// ```
pub fn call_filter_scoped<R, F>(
&self,
input: &[u8],
with_output: F,
) -> Result<R, WasmtimeRuntimeError>
where
F: for<'a> FnOnce(&'a [u8]) -> R,
{
call_filter_hook_scoped(&self.cell, input, with_output)
}
/// Dispatch a shape call. `input` must be rkyv-encoded `ShapeRequest` bytes.
/// Returns rkyv-encoded `ShapeResponse` bytes.
pub fn call_shape(&self, input: &[u8]) -> Result<Vec<u8>, WasmtimeRuntimeError> {
self.call_shape_scoped(input, <[u8]>::to_vec)
}
/// Dispatch a shape call and consume its checked output before the fresh Store is dropped.
pub fn call_shape_scoped<R, F>(
&self,
input: &[u8],
with_output: F,
) -> Result<R, WasmtimeRuntimeError>
where
F: for<'a> FnOnce(&'a [u8]) -> R,
{
call_shape_hook_scoped(&self.cell, input, with_output)
}
/// Dispatch an observe call. `input` must be rkyv-encoded `ObserveEvent` bytes.
/// The guest returns `(0, 0)` for observe; the returned `Vec<u8>` is always empty.
pub fn call_observe(&self, input: &[u8]) -> Result<Vec<u8>, WasmtimeRuntimeError> {
call_observe_hook(&self.cell, input)
}
/// Dispatch a transform_response call. Returns aligned bytes for rkyv access.
pub fn call_transform_response(
&self,
input: &[u8],
) -> Result<AlignedVec<16>, WasmtimeRuntimeError> {
self.call_transform_response_scoped(input, |output| {
let mut aligned = AlignedVec::with_capacity(output.len());
aligned.extend_from_slice(output);
aligned
})
}
/// Dispatch a response transform and consume its checked output before the Store is dropped.
pub fn call_transform_response_scoped<R, F>(
&self,
input: &[u8],
with_output: F,
) -> Result<R, WasmtimeRuntimeError>
where
F: for<'a> FnOnce(&'a [u8]) -> R,
{
call_transform_response_hook_scoped(&self.cell, input, with_output)
}
/// Dispatch a transform_sse_event call. Returns aligned bytes for rkyv access.
pub fn call_transform_sse_event(
&self,
input: &[u8],
) -> Result<AlignedVec<16>, WasmtimeRuntimeError> {
self.call_transform_sse_event_scoped(input, |output| {
let mut aligned = AlignedVec::with_capacity(output.len());
aligned.extend_from_slice(output);
aligned
})
}
/// Dispatch an SSE transform and consume its checked output before the Store is dropped.
pub fn call_transform_sse_event_scoped<R, F>(
&self,
input: &[u8],
with_output: F,
) -> Result<R, WasmtimeRuntimeError>
where
F: for<'a> FnOnce(&'a [u8]) -> R,
{
call_transform_sse_event_hook_scoped(&self.cell, input, with_output)
}
/// Wire version for the filter hook, if declared in plugin metadata.
pub fn filter_wire_version(&self) -> Option<cc_lb_plugin_wire::schema::WireVersion> {
self.cell
.metadata
.hooks
.get(cc_lb_plugin_wire::schema::HookKind::Filter.as_str())
.and_then(|m| cc_lb_plugin_wire::schema::WireVersion::from_u8(m.wire_version))
}
/// Wire version for the shape hook, if declared in plugin metadata.
pub fn shape_wire_version(&self) -> Option<cc_lb_plugin_wire::schema::WireVersion> {
self.cell
.metadata
.hooks
.get(cc_lb_plugin_wire::schema::HookKind::Shape.as_str())
.and_then(|m| cc_lb_plugin_wire::schema::WireVersion::from_u8(m.wire_version))
}
/// Wire version for the observe hook, if declared in plugin metadata.
pub fn observe_wire_version(&self) -> Option<cc_lb_plugin_wire::schema::WireVersion> {
self.cell
.metadata
.hooks
.get(cc_lb_plugin_wire::schema::HookKind::Observe.as_str())
.and_then(|m| cc_lb_plugin_wire::schema::WireVersion::from_u8(m.wire_version))
}
/// Whether the transform_response hook is non-noop (active).
pub fn has_response_transform(&self) -> bool {
self.cell
.metadata
.hooks
.get(cc_lb_plugin_wire::schema::HookKind::TransformResponse.as_str())
.is_some_and(|m| !m.mode.is_noop())
}
/// Whether the transform_sse_event hook is non-noop (active).
pub fn has_sse_event_transform(&self) -> bool {
self.cell
.metadata
.hooks
.get(cc_lb_plugin_wire::schema::HookKind::TransformSseEvent.as_str())
.is_some_and(|m| !m.mode.is_noop())
}
}