Skip to main content

cc_lb_plugin_wire/
schema.rs

1//! Wire versioning contract shared between host and plugin authors.
2//!
3//! The host and each plugin agree on a `WireVersion` per hook. Plugin
4//! authors declare their hook's version in `#[hook(...)]` metadata, and
5//! the host verifies at admission time that the declared version is in
6//! the host's supported list AND that the wasm's embedded layout
7//! fingerprint matches the host's computed fingerprint.
8
9use core::fmt;
10
11/// Wire schema version. Distinct enum values indicate incompatible
12/// struct layouts. Bump when adding/removing/reordering fields of ANY
13/// hook's wire type. Once V2 is added, V1 stays as a supported legacy
14/// version until deprecated by explicit host-list removal.
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
16#[repr(u8)]
17pub enum WireVersion {
18    V1 = 1,
19}
20
21impl WireVersion {
22    pub const fn as_u8(self) -> u8 {
23        self as u8
24    }
25
26    pub const fn from_u8(v: u8) -> Option<Self> {
27        match v {
28            1 => Some(Self::V1),
29            _ => None,
30        }
31    }
32
33    pub const fn as_str(self) -> &'static str {
34        match self {
35            Self::V1 => "v1",
36        }
37    }
38}
39
40impl fmt::Display for WireVersion {
41    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42        f.write_str(self.as_str())
43    }
44}
45
46/// Plugin hook kind. Each hook is a distinct wire contract.
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
48pub enum HookKind {
49    Filter,
50    Shape,
51    Observe,
52}
53
54impl HookKind {
55    pub const fn as_str(self) -> &'static str {
56        match self {
57            Self::Filter => "filter",
58            Self::Shape => "shape",
59            Self::Observe => "observe",
60        }
61    }
62
63    /// Wasm function export name for this hook (guest-side).
64    pub const fn export_name(self) -> &'static str {
65        match self {
66            Self::Filter => "cc_lb_filter",
67            Self::Shape => "cc_lb_shape",
68            Self::Observe => "cc_lb_observe",
69        }
70    }
71
72    /// Custom section name prefix. The full section name for a specific
73    /// version is `{prefix}.{version_str}` (e.g., `cc_lb.schema.filter.v1`).
74    pub const fn section_prefix(self) -> &'static str {
75        match self {
76            Self::Filter => "cc_lb.schema.filter",
77            Self::Shape => "cc_lb.schema.shape",
78            Self::Observe => "cc_lb.schema.observe",
79        }
80    }
81
82    /// All hook kinds — for iteration.
83    pub const ALL: &'static [HookKind] = &[Self::Filter, Self::Shape, Self::Observe];
84
85    pub fn parse(s: &str) -> Option<Self> {
86        match s {
87            "filter" => Some(Self::Filter),
88            "shape" => Some(Self::Shape),
89            "observe" => Some(Self::Observe),
90            _ => None,
91        }
92    }
93}
94
95impl fmt::Display for HookKind {
96    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97        f.write_str(self.as_str())
98    }
99}
100
101/// Host-supported versions per hook. Update when adding new versions.
102pub const HOST_SUPPORTED_FILTER_VERSIONS: &[WireVersion] = &[WireVersion::V1];
103pub const HOST_SUPPORTED_SHAPE_VERSIONS: &[WireVersion] = &[WireVersion::V1];
104pub const HOST_SUPPORTED_OBSERVE_VERSIONS: &[WireVersion] = &[WireVersion::V1];
105
106pub fn host_supported_versions(hook: HookKind) -> &'static [WireVersion] {
107    match hook {
108        HookKind::Filter => HOST_SUPPORTED_FILTER_VERSIONS,
109        HookKind::Shape => HOST_SUPPORTED_SHAPE_VERSIONS,
110        HookKind::Observe => HOST_SUPPORTED_OBSERVE_VERSIONS,
111    }
112}
113
114pub fn host_supports(hook: HookKind, version: WireVersion) -> bool {
115    host_supported_versions(hook).contains(&version)
116}
117
118/// Layout fingerprint published by each wire struct via the
119/// `#[derive(WireSchema)]` proc-macro (defined in
120/// `cc-lb-pdk-wasmtime-macros`). The macro walks the struct AST,
121/// serialises fields to a canonical descriptor string, then embeds
122/// `BLAKE3(descriptor)` as a 32-byte const.
123pub trait WireSchema {
124    const FINGERPRINT: [u8; 32];
125    const DESCRIPTOR: &'static str;
126}
127
128/// Packed `(out_ptr, out_len)` return value used by every guest hook export.
129pub const fn pack_ret(ptr: u32, len: u32) -> u64 {
130    ((ptr as u64) << 32) | (len as u64)
131}
132
133/// Inverse of [`pack_ret`].
134pub const fn unpack_ret(packed: u64) -> (u32, u32) {
135    let ptr = (packed >> 32) as u32;
136    let len = (packed & 0xFFFF_FFFF) as u32;
137    (ptr, len)
138}