Skip to main content

freenet_stdlib/
delegate_manifest.rs

1//! The delegate manifest: what a delegate asks the node for, declared inside
2//! its own WASM.
3//!
4//! A delegate that wants more than the request/response model it has always
5//! had (lifecycle events, periodic wake-ups; later perhaps notifications) says
6//! so in a manifest. The `#[delegate(manifest(...))]` attribute writes it into a WASM
7//! custom section named [`MANIFEST_SECTION_NAME`]. The node reads that section
8//! when the delegate is registered, without running any delegate code.
9//!
10//! # Why a manifest at all
11//!
12//! Two jobs, both of which need the node to know what a delegate wants
13//! *before* it runs:
14//!
15//! 1. **Backward compatibility of new inbound messages.** An
16//!    [`InboundDelegateMsg`](crate::prelude::InboundDelegateMsg) variant added
17//!    in a later stdlib is a hard decode error in a delegate built against an
18//!    earlier one (see `WIRE-FORMAT.md`). So the node sends a new kind of
19//!    inbound message — [`LifecycleEvent`] today — **only** to a delegate
20//!    whose manifest lists it. A delegate without a manifest never receives
21//!    one, and behaves exactly as it did before manifests existed.
22//! 2. **Consent.** Some capabilities are gated by the node on the user's
23//!    permission ("run in the background"). The node reads the capabilities
24//!    from the manifest and asks the user once, at registration time, for the
25//!    ones not yet granted.
26//!
27//! # Why it cannot be swapped
28//!
29//! The section is part of the WASM module, and a delegate's key is derived
30//! from the hash of that module. Changing the manifest changes the delegate
31//! key, exactly as changing any line of code does.
32//!
33//! # Encoding: JSON, deliberately
34//!
35//! The payload is UTF-8 JSON, not bincode. The manifest is the one piece of
36//! delegate metadata the node must read from delegates built against *any*
37//! stdlib, including ones newer than the node. bincode is positional and
38//! cannot skip what it does not know (`WIRE-FORMAT.md`), so a newer stdlib
39//! adding a field or a capability would make older nodes reject the whole
40//! manifest. JSON names its fields, so an older reader ignores fields it does
41//! not know, and unknown capability or lifecycle names decode as
42//! [`Capability::Unknown`] / [`LifecycleKind::Unknown`] and are ignored rather
43//! than rejecting the manifest. (`#[serde(other)]` is safe here because JSON
44//! is self-describing; `WIRE-FORMAT.md`'s warning against it is about
45//! bincode.)
46//!
47//! # Emitting a manifest adds a section, and so changes the delegate key
48//!
49//! Only delegates that write `manifest(...)` get the section. Upgrading stdlib
50//! does not add one to delegates that do not ask for it.
51
52use serde::{Deserialize, Serialize};
53
54/// Name of the WASM custom section holding the manifest.
55pub const MANIFEST_SECTION_NAME: &str = "freenet-manifest";
56
57/// The manifest format version this stdlib writes.
58///
59/// Informational only. Readers accept any version `>= 1` and never gate on it,
60/// because a reader that refused newer versions would drop every capability it
61/// does understand the moment one it does not is added. That works only under
62/// two rules, which are permanent:
63///
64/// - the meaning of an existing field or name never changes; a changed meaning
65///   gets a new field or a new name;
66/// - `lifecycle` and `capabilities` entries are what a reader looks up by
67///   name. A later format that needs parameters for a capability adds a new
68///   top-level field for them. (A reader still tolerates a non-string entry:
69///   it decodes as `Unknown`, see [`DelegateManifest::from_bytes`].)
70pub const MANIFEST_VERSION: u16 = 1;
71
72/// Largest manifest payload a reader accepts, in bytes. A manifest is a
73/// handful of short names; anything bigger is not a manifest.
74pub const MAX_MANIFEST_BYTES: usize = 4096;
75
76/// Shortest wake-up interval a node honours, in seconds. A manifest asking for
77/// less is treated as asking for this (see [`DelegateManifest::effective_wakeups`]).
78///
79/// The floor is what stops a delegate waking itself into a storm: a wake-up is
80/// work nothing outside the node asked for, on an idle node, forever.
81pub const MIN_WAKEUP_INTERVAL_SECS: u64 = 60;
82
83/// Longest wake-up interval, in seconds (7 days). A longer one is treated as
84/// this. Wake-ups are re-armed when a node starts rather than remembered across
85/// restarts, so a very long interval would mostly measure node uptime anyway.
86pub const MAX_WAKEUP_INTERVAL_SECS: u64 = 7 * 24 * 3600;
87
88/// Longest wake-up tag, in bytes. Entries with a longer (or empty) tag are
89/// ignored.
90pub const MAX_WAKEUP_TAG_BYTES: usize = 64;
91
92/// Most wake-up schedules one delegate may declare. Entries past this are
93/// ignored.
94pub const MAX_WAKEUPS: usize = 4;
95
96/// What a delegate declares it wants from the node.
97///
98/// `#[non_exhaustive]` so fields can be added without a source break; build one
99/// with [`DelegateManifest::new`].
100#[non_exhaustive]
101#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
102pub struct DelegateManifest {
103    /// Format version; see [`MANIFEST_VERSION`].
104    pub manifest_version: u16,
105    /// Lifecycle events the delegate wants delivered. The node never sends a
106    /// [`LifecycleEvent`] of a kind that is not listed here.
107    #[serde(default, deserialize_with = "lenient_list")]
108    pub lifecycle: Vec<LifecycleKind>,
109    /// Node-enforced capabilities the delegate asks the user for.
110    #[serde(default, deserialize_with = "lenient_list")]
111    pub capabilities: Vec<Capability>,
112    /// Periodic wake-ups the delegate asks for; see [`WakeupSchedule`].
113    ///
114    /// Added after the first manifest release (stdlib 0.12.1). Omitted from
115    /// the JSON when empty, so the manifest SECTION of a delegate without
116    /// wake-ups is byte-identical to what 0.12.0 wrote. (Rebuilding a delegate
117    /// against a different stdlib still changes its WASM, and so its key, for
118    /// the usual reasons, e.g. version strings in panic locations; plan a
119    /// migration as for any rebuild.) A reader that predates the field ignores
120    /// it (unknown JSON fields are skipped), so a delegate declaring wake-ups
121    /// still loads, and still gets its lifecycle events, on a node that cannot
122    /// deliver them.
123    ///
124    /// Such a reader also DROPS the field if it re-serializes the manifest
125    /// ([`DelegateManifest::to_bytes`] writes only the fields it knows). A
126    /// node that keeps a re-serialized copy must re-read the manifest from the
127    /// delegate's code after it learns a new field, or it will not see what
128    /// delegates registered under the older version declared. And a node that
129    /// predates wake-ups asks for `Background` only when a lifecycle kind is
130    /// listed: declare one alongside `wakeups` (e.g. `NodeStarted`) so the user
131    /// is asked, and the delegate recorded, on those nodes too.
132    #[serde(
133        default,
134        skip_serializing_if = "Vec::is_empty",
135        deserialize_with = "lenient_wakeups"
136    )]
137    pub wakeups: Vec<WakeupSchedule>,
138}
139
140/// One periodic wake-up a delegate asks for.
141///
142/// The node delivers
143/// [`InboundDelegateMsg::WakeupFired`](crate::prelude::InboundDelegateMsg::WakeupFired)
144/// with `tag`'s bytes about every `every_secs` seconds, with no app open,
145/// under the same conditions as lifecycle events: the manifest lists it and
146/// the delegate's app holds the user's [`Capability::Background`] grant. The
147/// run gets the delegate's registered parameters and no origin.
148///
149/// A node honours an interval between [`MIN_WAKEUP_INTERVAL_SECS`] and
150/// [`MAX_WAKEUP_INTERVAL_SECS`] (clamping one outside that range), a tag of 1
151/// to [`MAX_WAKEUP_TAG_BYTES`] bytes, and at most [`MAX_WAKEUPS`] entries; see
152/// [`DelegateManifest::effective_wakeups`], which is the node's reading.
153///
154/// Why a manifest entry and not a call the delegate makes at run time: a new
155/// host import makes the WASM fail to instantiate on every node that does not
156/// provide it, and a new `OutboundDelegateMsg` variant makes older nodes fail
157/// to decode the whole batch it is in. A manifest field is ignored by older
158/// nodes, so ONE delegate build works on nodes with and without wake-ups.
159///
160/// Timing is the node's business; freenet-core arms a schedule when the
161/// delegate is registered, when its app is granted `Background`, and at each
162/// node start (the first fire comes within about a minute), then fires every
163/// `every_secs` plus a little jitter. A fire that cannot start (delegate busy,
164/// its time budget spent) is retried for up to 45 s and otherwise skipped.
165/// Missed fires (node down, skipped) are not replayed: the next one simply
166/// comes on schedule.
167///
168/// # Rules for this struct's fields (permanent)
169///
170/// Readers ignore fields they do not know, INCLUDING inside an entry. So:
171///
172/// - a new field must be advisory: a reader that ignores it must still do
173///   something acceptable. A field that RESTRICTS the schedule (a quiet
174///   window, a cap) would be silently ignored by older nodes; put such a
175///   thing in a new top-level manifest field instead, whose absence older
176///   readers cannot misread as consent;
177/// - a new field must carry `#[serde(default)]`, or every entry written
178///   without it (every existing delegate) fails to decode and is dropped;
179/// - `tag` and `every_secs` keep their meaning forever.
180#[non_exhaustive]
181#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
182pub struct WakeupSchedule {
183    /// Echoed back as the `tag` of `WakeupFired`, so a delegate with several
184    /// schedules can tell them apart.
185    pub tag: String,
186    /// Interval between wake-ups, in seconds.
187    pub every_secs: u64,
188}
189
190impl WakeupSchedule {
191    pub fn new(tag: impl Into<String>, every_secs: u64) -> Self {
192        Self {
193            tag: tag.into(),
194            every_secs,
195        }
196    }
197}
198
199/// A kind of [`LifecycleEvent`] a delegate can ask to receive.
200///
201/// Adding a kind later means adding a variant here **and** a matching
202/// [`LifecycleEvent`] variant. A delegate built before the addition cannot
203/// list the new kind, so it never receives the new event — which is what makes
204/// appending a `LifecycleEvent` variant safe for already-deployed delegates.
205#[non_exhaustive]
206#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash)]
207#[serde(rename_all = "snake_case")]
208pub enum LifecycleKind {
209    /// [`LifecycleEvent::Installed`].
210    Installed,
211    /// [`LifecycleEvent::NodeStarted`].
212    NodeStarted,
213    /// A name this stdlib does not know, written by a newer one. Readers
214    /// ignore it. The macro never writes it; re-serializing a manifest read
215    /// from a newer stdlib does (as `"unknown"`).
216    #[serde(other)]
217    Unknown,
218}
219
220/// A node-enforced capability, granted by the user once per app.
221///
222/// A node that implements capabilities refuses one until the user has granted
223/// it, and remembers the answer per app, so the user is asked once. How a node
224/// identifies an app is the node's business, not part of this format.
225#[non_exhaustive]
226#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, Hash)]
227#[serde(rename_all = "snake_case")]
228pub enum Capability {
229    /// Run without an open app: receive lifecycle events. Required by any
230    /// manifest that lists a lifecycle kind (the macro enforces it).
231    Background,
232    /// A name this stdlib does not know, written by a newer one. Readers
233    /// ignore it. The macro never writes it; re-serializing a manifest read
234    /// from a newer stdlib does (as `"unknown"`).
235    #[serde(other)]
236    Unknown,
237}
238
239/// A lifecycle event, delivered as
240/// [`InboundDelegateMsg::Lifecycle`](crate::prelude::InboundDelegateMsg::Lifecycle).
241///
242/// Only sent to a delegate whose manifest lists the matching
243/// [`LifecycleKind`]. A node that implements delivery also requires the user's
244/// [`Capability::Background`] grant for the delegate's app. The run gets the
245/// delegate's registered parameters and no origin.
246///
247/// # Wire format
248///
249/// bincode, nested inside `InboundDelegateMsg`. Variants are appended, never
250/// inserted or reordered (pinned by `lifecycle_event_tags_are_pinned`). A
251/// variant's fields are frozen once released: `WIRE-FORMAT.md` rule 1 forbids
252/// appending a field to a struct already on the wire, so new information
253/// arrives as a new variant.
254#[non_exhaustive]
255#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
256pub enum LifecycleEvent {
257    /// This delegate was installed on this node: its first registration here,
258    /// or the first time its app was granted [`Capability::Background`] after
259    /// that. Delivered at most once per delegate key per node.
260    ///
261    /// A delegate typically uses it to subscribe to the contracts it watches
262    /// and to do any one-time setup.
263    Installed,
264    /// The node started. Delivered once per node start, after the node has
265    /// finished whatever restore work it does at start-up.
266    ///
267    /// Contract notifications that arrived while the node was down were not
268    /// delivered and are never replayed, so a delegate should re-read any
269    /// contract it depends on rather than assume it saw every change.
270    NodeStarted {
271        /// When the node was last known to be running (milliseconds since the
272        /// Unix epoch), if it knows. Everything between this and now was
273        /// missed. `None` means the node does not know, not that nothing was
274        /// missed.
275        down_since_ms: Option<u64>,
276    },
277}
278
279impl LifecycleEvent {
280    /// The manifest kind that must be listed for this event to be delivered.
281    pub fn kind(&self) -> LifecycleKind {
282        match self {
283            LifecycleEvent::Installed => LifecycleKind::Installed,
284            LifecycleEvent::NodeStarted { .. } => LifecycleKind::NodeStarted,
285        }
286    }
287}
288
289/// Why a manifest could not be read.
290#[derive(Debug, thiserror::Error, PartialEq, Eq)]
291#[non_exhaustive]
292pub enum ManifestError {
293    #[error("not a WASM module (bad magic or version)")]
294    NotWasm,
295    #[error("truncated or malformed WASM section structure")]
296    Malformed,
297    #[error("more than one `{MANIFEST_SECTION_NAME}` custom section")]
298    Duplicate,
299    #[error("manifest is {0} bytes, over the {MAX_MANIFEST_BYTES}-byte limit")]
300    TooLarge(usize),
301    #[error("manifest is not valid JSON for this schema: {0}")]
302    Decode(String),
303    #[error("manifest_version 0 is not a valid version")]
304    BadVersion,
305}
306
307impl DelegateManifest {
308    /// A manifest at the current [`MANIFEST_VERSION`].
309    pub fn new(lifecycle: Vec<LifecycleKind>, capabilities: Vec<Capability>) -> Self {
310        Self {
311            manifest_version: MANIFEST_VERSION,
312            lifecycle,
313            capabilities,
314            wakeups: Vec::new(),
315        }
316    }
317
318    /// This manifest plus a wake-up schedule.
319    pub fn with_wakeup(mut self, tag: impl Into<String>, every_secs: u64) -> Self {
320        self.wakeups.push(WakeupSchedule::new(tag, every_secs));
321        self
322    }
323
324    /// The wake-ups a node honours, as `(tag bytes, interval)`: entries with an
325    /// empty or over-long tag are dropped, a repeated tag keeps its first
326    /// entry, intervals are clamped to
327    /// `[MIN_WAKEUP_INTERVAL_SECS, MAX_WAKEUP_INTERVAL_SECS]`, and only the
328    /// first [`MAX_WAKEUPS`] survivors count.
329    ///
330    /// This is only the manifest's side. It does not check the `Background`
331    /// grant (the node's state, not the manifest's): a node must also require
332    /// that before firing any of these.
333    ///
334    /// Clamped rather than refused: a node that later lowers the floor must
335    /// not make delegates built for it dead on older nodes, and a longer
336    /// interval than asked is the safe direction.
337    pub fn effective_wakeups(&self) -> Vec<(Vec<u8>, std::time::Duration)> {
338        let mut out: Vec<(Vec<u8>, std::time::Duration)> = Vec::new();
339        for w in &self.wakeups {
340            if out.len() >= MAX_WAKEUPS {
341                break;
342            }
343            let tag = w.tag.as_bytes();
344            if tag.is_empty() || tag.len() > MAX_WAKEUP_TAG_BYTES {
345                continue;
346            }
347            if out.iter().any(|(t, _)| t.as_slice() == tag) {
348                continue;
349            }
350            let secs = w
351                .every_secs
352                .clamp(MIN_WAKEUP_INTERVAL_SECS, MAX_WAKEUP_INTERVAL_SECS);
353            out.push((tag.to_vec(), std::time::Duration::from_secs(secs)));
354        }
355        out
356    }
357
358    /// Whether the manifest asks for at least one wake-up a node honours.
359    pub fn wants_wakeups(&self) -> bool {
360        !self.effective_wakeups().is_empty()
361    }
362
363    /// Whether the manifest asks for lifecycle events of this kind.
364    pub fn wants_lifecycle(&self, kind: LifecycleKind) -> bool {
365        kind != LifecycleKind::Unknown && self.lifecycle.contains(&kind)
366    }
367
368    /// Whether the manifest asks for this capability.
369    pub fn wants_capability(&self, cap: Capability) -> bool {
370        cap != Capability::Unknown && self.capabilities.contains(&cap)
371    }
372
373    /// Known capabilities this manifest asks for, deduplicated, in declaration
374    /// order. Unknown names are dropped.
375    pub fn known_capabilities(&self) -> Vec<Capability> {
376        let mut out = Vec::new();
377        for c in &self.capabilities {
378            if *c != Capability::Unknown && !out.contains(c) {
379                out.push(*c);
380            }
381        }
382        out
383    }
384
385    /// Serialize to the section payload.
386    pub fn to_bytes(&self) -> Vec<u8> {
387        serde_json::to_vec(self).expect("a manifest always serializes")
388    }
389
390    /// Parse a section payload.
391    ///
392    /// Tolerant of manifests written by newer stdlibs: unknown fields are
393    /// ignored, a list field that is not an array (`null`, or any other shape)
394    /// reads as empty, and a list entry that is not a
395    /// name this reader knows (an unknown name, or a non-string value) reads as
396    /// `Unknown` rather than failing the manifest, so the known entries next
397    /// to it still count.
398    pub fn from_bytes(bytes: &[u8]) -> Result<Self, ManifestError> {
399        if bytes.len() > MAX_MANIFEST_BYTES {
400            return Err(ManifestError::TooLarge(bytes.len()));
401        }
402        let m: DelegateManifest =
403            serde_json::from_slice(bytes).map_err(|e| ManifestError::Decode(e.to_string()))?;
404        if m.manifest_version == 0 {
405            return Err(ManifestError::BadVersion);
406        }
407        Ok(m)
408    }
409
410    /// Read the manifest from a raw WASM module (no version prefix).
411    ///
412    /// `Ok(None)` means the module has no manifest section: a delegate that
413    /// asked for nothing, which is every delegate built before manifests
414    /// existed. Only the section headers are walked; nothing is executed or
415    /// validated beyond what locating the section needs.
416    pub fn from_wasm(module: &[u8]) -> Result<Option<Self>, ManifestError> {
417        let mut found: Option<&[u8]> = None;
418        for section in custom_sections(module)? {
419            let (name, payload) = section?;
420            if name == MANIFEST_SECTION_NAME.as_bytes() {
421                if found.is_some() {
422                    return Err(ManifestError::Duplicate);
423                }
424                found = Some(payload);
425            }
426        }
427        found.map(Self::from_bytes).transpose()
428    }
429}
430
431/// Decode a list whose entries are enum names, mapping any entry this reader
432/// cannot decode to the enum's `#[serde(other)]` variant.
433fn lenient_list<'de, D, T>(d: D) -> Result<Vec<T>, D::Error>
434where
435    D: serde::Deserializer<'de>,
436    T: serde::de::DeserializeOwned + Unknownable,
437{
438    // Anything but an array (`null`, or a shape a later format might use)
439    // reads as an empty list: asking for nothing is the safe direction.
440    let serde_json::Value::Array(raw) = serde_json::Value::deserialize(d)? else {
441        return Ok(Vec::new());
442    };
443    Ok(raw
444        .into_iter()
445        .map(|v| serde_json::from_value(v).unwrap_or_else(|_| T::unknown()))
446        .collect())
447}
448
449/// Decode the wake-up list, dropping any entry that is valid JSON but not a
450/// schedule this reader knows (a later format might add a shape it does not)
451/// instead of failing the manifest. Input the JSON parser itself rejects
452/// inside `wakeups` (malformed text, a lone surrogate, a number out of range,
453/// a duplicate `wakeups` key) fails the whole manifest; a 0.12.0 reader
454/// skipped the field without parsing it. Only the delegate's author can write
455/// such input, and the macro never does.
456fn lenient_wakeups<'de, D>(d: D) -> Result<Vec<WakeupSchedule>, D::Error>
457where
458    D: serde::Deserializer<'de>,
459{
460    let serde_json::Value::Array(raw) = serde_json::Value::deserialize(d)? else {
461        return Ok(Vec::new());
462    };
463    Ok(raw
464        .into_iter()
465        .filter_map(|v| serde_json::from_value(v).ok())
466        .collect())
467}
468
469trait Unknownable {
470    fn unknown() -> Self;
471}
472impl Unknownable for LifecycleKind {
473    fn unknown() -> Self {
474        LifecycleKind::Unknown
475    }
476}
477impl Unknownable for Capability {
478    fn unknown() -> Self {
479        Capability::Unknown
480    }
481}
482
483/// Used by `#[delegate(manifest(...))]` to check, at compile time, that the
484/// section name and version it writes are the ones this stdlib reads. Not
485/// part of the public API.
486#[doc(hidden)]
487pub const fn __manifest_macro_agrees(section: &str, version: u16) -> bool {
488    let (a, b) = (section.as_bytes(), MANIFEST_SECTION_NAME.as_bytes());
489    if a.len() != b.len() || version != MANIFEST_VERSION {
490        return false;
491    }
492    let mut i = 0;
493    while i < a.len() {
494        if a[i] != b[i] {
495            return false;
496        }
497        i += 1;
498    }
499    true
500}
501
502/// A custom section's `(name, payload)`.
503type CustomSection<'a> = (&'a [u8], &'a [u8]);
504
505/// Iterate over a WASM module's custom sections.
506fn custom_sections(
507    module: &[u8],
508) -> Result<impl Iterator<Item = Result<CustomSection<'_>, ManifestError>>, ManifestError> {
509    const HEADER: [u8; 8] = [0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00];
510    if module.len() < HEADER.len() || module[..HEADER.len()] != HEADER {
511        return Err(ManifestError::NotWasm);
512    }
513    let mut pos = HEADER.len();
514    let mut failed = false;
515    Ok(std::iter::from_fn(move || loop {
516        if failed || pos >= module.len() {
517            return None;
518        }
519        let parsed = (|| {
520            let id = module[pos];
521            let mut p = pos + 1;
522            let size = read_leb_u32(module, &mut p)? as usize;
523            let end = p.checked_add(size).ok_or(ManifestError::Malformed)?;
524            if end > module.len() {
525                return Err(ManifestError::Malformed);
526            }
527            let custom = if id == 0 {
528                let name_len = read_leb_u32(module, &mut p)? as usize;
529                let name_end = p.checked_add(name_len).ok_or(ManifestError::Malformed)?;
530                if name_end > end {
531                    return Err(ManifestError::Malformed);
532                }
533                Some((&module[p..name_end], &module[name_end..end]))
534            } else {
535                None
536            };
537            Ok((end, custom))
538        })();
539        match parsed {
540            Ok((end, custom)) => {
541                pos = end;
542                if let Some(c) = custom {
543                    return Some(Ok(c));
544                }
545            }
546            Err(e) => {
547                failed = true;
548                return Some(Err(e));
549            }
550        }
551    }))
552}
553
554fn read_leb_u32(buf: &[u8], pos: &mut usize) -> Result<u32, ManifestError> {
555    let mut result: u32 = 0;
556    for i in 0..5 {
557        let byte = *buf.get(*pos).ok_or(ManifestError::Malformed)?;
558        *pos += 1;
559        if i == 4 && byte & 0xf0 != 0 {
560            return Err(ManifestError::Malformed);
561        }
562        result |= u32::from(byte & 0x7f) << (7 * i);
563        if byte & 0x80 == 0 {
564            return Ok(result);
565        }
566    }
567    Err(ManifestError::Malformed)
568}
569
570#[cfg(test)]
571mod tests {
572    use super::*;
573
574    fn leb(mut v: u32) -> Vec<u8> {
575        let mut out = Vec::new();
576        loop {
577            let mut b = (v & 0x7f) as u8;
578            v >>= 7;
579            if v != 0 {
580                b |= 0x80;
581            }
582            out.push(b);
583            if v == 0 {
584                return out;
585            }
586        }
587    }
588
589    fn custom_section(name: &str, payload: &[u8]) -> Vec<u8> {
590        let mut body = leb(name.len() as u32);
591        body.extend_from_slice(name.as_bytes());
592        body.extend_from_slice(payload);
593        let mut out = vec![0u8];
594        out.extend(leb(body.len() as u32));
595        out.extend(body);
596        out
597    }
598
599    fn module(sections: &[Vec<u8>]) -> Vec<u8> {
600        let mut m = vec![0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00];
601        // A non-custom section first (type section, empty vec), so the walker
602        // has to step over a section it does not care about.
603        m.extend([0x01, 0x01, 0x00]);
604        for s in sections {
605            m.extend_from_slice(s);
606        }
607        m
608    }
609
610    fn sample() -> DelegateManifest {
611        DelegateManifest::new(
612            vec![LifecycleKind::Installed, LifecycleKind::NodeStarted],
613            vec![Capability::Background],
614        )
615    }
616
617    #[test]
618    fn round_trips_through_a_wasm_custom_section() {
619        let m = module(&[
620            custom_section("name", b"whatever"),
621            custom_section(MANIFEST_SECTION_NAME, &sample().to_bytes()),
622        ]);
623        assert_eq!(DelegateManifest::from_wasm(&m).unwrap(), Some(sample()));
624    }
625
626    #[test]
627    fn a_module_without_the_section_has_no_manifest() {
628        let m = module(&[custom_section("producers", b"rustc")]);
629        assert_eq!(DelegateManifest::from_wasm(&m).unwrap(), None);
630    }
631
632    /// The exact JSON the macro writes. If this changes, every delegate that
633    /// declares a manifest re-keys on its next build, and older nodes must
634    /// still read it.
635    #[test]
636    fn json_shape_is_pinned() {
637        assert_eq!(
638            String::from_utf8(sample().to_bytes()).unwrap(),
639            r#"{"manifest_version":1,"lifecycle":["installed","node_started"],"capabilities":["background"]}"#
640        );
641    }
642
643    /// A manifest from a newer stdlib — an unknown field, an unknown
644    /// capability, an unknown lifecycle kind — must still be read, keeping
645    /// what this reader knows. Otherwise one new capability name would strip
646    /// every older node of the kinds it does understand.
647    #[test]
648    fn a_newer_manifest_is_read_keeping_known_entries() {
649        let json = br#"{"manifest_version":3,"lifecycle":["installed","woke_up"],
650            "capabilities":["background","teleport"],"brand_new_field":{"x":1}}"#;
651        let m = DelegateManifest::from_bytes(json).unwrap();
652        assert!(m.wants_lifecycle(LifecycleKind::Installed));
653        assert!(!m.wants_lifecycle(LifecycleKind::NodeStarted));
654        assert!(!m.wants_lifecycle(LifecycleKind::Unknown));
655        assert!(!m.wants_capability(Capability::Unknown));
656        assert_eq!(m.known_capabilities(), vec![Capability::Background]);
657    }
658
659    /// With wake-ups the JSON gains one trailing field, and only then: the
660    /// no-wake-up form above stays byte-identical to 0.12.0's.
661    #[test]
662    fn json_shape_with_wakeups_is_pinned() {
663        let m = sample().with_wakeup("heartbeat", 300);
664        assert_eq!(
665            String::from_utf8(m.to_bytes()).unwrap(),
666            r#"{"manifest_version":1,"lifecycle":["installed","node_started"],"capabilities":["background"],"wakeups":[{"tag":"heartbeat","every_secs":300}]}"#
667        );
668        assert_eq!(DelegateManifest::from_bytes(&m.to_bytes()).unwrap(), m);
669    }
670
671    /// What a node honours, at every boundary.
672    #[test]
673    fn effective_wakeups_clamp_and_filter_at_the_boundaries() {
674        let secs = |m: &DelegateManifest| {
675            m.effective_wakeups()
676                .into_iter()
677                .map(|(t, d)| (String::from_utf8(t).unwrap(), d.as_secs()))
678                .collect::<Vec<_>>()
679        };
680        let one = |every: u64| secs(&sample().with_wakeup("t", every));
681        assert_eq!(one(0), vec![("t".into(), MIN_WAKEUP_INTERVAL_SECS)]);
682        assert_eq!(
683            one(MIN_WAKEUP_INTERVAL_SECS - 1),
684            vec![("t".into(), MIN_WAKEUP_INTERVAL_SECS)]
685        );
686        assert_eq!(
687            one(MIN_WAKEUP_INTERVAL_SECS),
688            vec![("t".into(), MIN_WAKEUP_INTERVAL_SECS)]
689        );
690        assert_eq!(
691            one(MIN_WAKEUP_INTERVAL_SECS + 1),
692            vec![("t".into(), MIN_WAKEUP_INTERVAL_SECS + 1)]
693        );
694        assert_eq!(
695            one(MAX_WAKEUP_INTERVAL_SECS),
696            vec![("t".into(), MAX_WAKEUP_INTERVAL_SECS)]
697        );
698        assert_eq!(
699            one(MAX_WAKEUP_INTERVAL_SECS + 1),
700            vec![("t".into(), MAX_WAKEUP_INTERVAL_SECS)]
701        );
702        assert_eq!(one(u64::MAX), vec![("t".into(), MAX_WAKEUP_INTERVAL_SECS)]);
703
704        // Tags: empty and over-long are dropped, the longest allowed is kept.
705        let max_tag = "x".repeat(MAX_WAKEUP_TAG_BYTES);
706        let long_tag = "x".repeat(MAX_WAKEUP_TAG_BYTES + 1);
707        let m = sample()
708            .with_wakeup("", 120)
709            .with_wakeup(long_tag, 120)
710            .with_wakeup(max_tag.clone(), 120);
711        assert_eq!(secs(&m), vec![(max_tag, 120)]);
712
713        // The limit is in BYTES, not characters: 32 two-byte chars is exactly
714        // the limit, one more ASCII byte is over it.
715        let utf8_max = "é".repeat(MAX_WAKEUP_TAG_BYTES / 2);
716        assert_eq!(utf8_max.len(), MAX_WAKEUP_TAG_BYTES);
717        let utf8_over = format!("{utf8_max}a");
718        let m = sample()
719            .with_wakeup(utf8_over, 120)
720            .with_wakeup(utf8_max.clone(), 120);
721        assert_eq!(secs(&m), vec![(utf8_max, 120)]);
722
723        // A repeated tag keeps its first entry.
724        let m = sample().with_wakeup("a", 120).with_wakeup("a", 600);
725        assert_eq!(secs(&m), vec![("a".into(), 120)]);
726
727        // At most MAX_WAKEUPS, counting only entries that survive the filter.
728        let mut m = sample().with_wakeup("", 60);
729        for i in 0..MAX_WAKEUPS + 1 {
730            m = m.with_wakeup(format!("w{i}"), 60);
731        }
732        let got = secs(&m);
733        assert_eq!(got.len(), MAX_WAKEUPS);
734        assert_eq!(got[0].0, "w0");
735        assert_eq!(got[MAX_WAKEUPS - 1].0, format!("w{}", MAX_WAKEUPS - 1));
736
737        assert!(!sample().wants_wakeups());
738        assert!(!sample().with_wakeup("", 60).wants_wakeups());
739        assert!(sample().with_wakeup("a", 60).wants_wakeups());
740    }
741
742    /// A malformed or future-shaped wake-up entry is dropped, not fatal: the
743    /// lifecycle kinds and capabilities next to it must still count.
744    #[test]
745    fn a_malformed_wakeup_entry_is_dropped_not_fatal() {
746        let json = br#"{"manifest_version":2,"lifecycle":["node_started"],
747            "capabilities":["background"],
748            "wakeups":[{"tag":"ok","every_secs":90},{"tag":7},"junk",{"cron":"* * *"},
749                       {"tag":"extra","every_secs":120,"jitter":5}]}"#;
750        let m = DelegateManifest::from_bytes(json).unwrap();
751        assert!(m.wants_lifecycle(LifecycleKind::NodeStarted));
752        assert_eq!(m.known_capabilities(), vec![Capability::Background]);
753        assert_eq!(
754            m.wakeups,
755            vec![
756                WakeupSchedule::new("ok", 90),
757                WakeupSchedule::new("extra", 120)
758            ]
759        );
760        for shape in [r#"null"#, r#""heartbeat""#, r#"{"heartbeat":300}"#] {
761            let json = format!(r#"{{"manifest_version":1,"wakeups":{shape}}}"#);
762            let m = DelegateManifest::from_bytes(json.as_bytes()).unwrap();
763            assert!(m.wakeups.is_empty(), "{shape}");
764        }
765    }
766
767    /// The reader that shipped in 0.12.0 (and so in every node that
768    /// understands manifests but not wake-ups) has no `wakeups` field. This
769    /// replicates its schema exactly and shows it reads a wake-up manifest,
770    /// keeping everything else: the property that lets one delegate build run
771    /// on nodes with and without wake-ups.
772    #[test]
773    fn a_reader_without_the_wakeups_field_still_reads_the_manifest() {
774        #[derive(Deserialize)]
775        struct ReaderV0120 {
776            manifest_version: u16,
777            #[serde(default, deserialize_with = "lenient_list")]
778            lifecycle: Vec<LifecycleKind>,
779            #[serde(default, deserialize_with = "lenient_list")]
780            capabilities: Vec<Capability>,
781        }
782        let m = sample().with_wakeup("heartbeat", 300);
783        let old: ReaderV0120 = serde_json::from_slice(&m.to_bytes()).unwrap();
784        assert_eq!(old.manifest_version, 1);
785        assert_eq!(old.lifecycle, m.lifecycle);
786        assert_eq!(old.capabilities, m.capabilities);
787    }
788
789    #[test]
790    fn missing_lists_default_to_empty() {
791        let m = DelegateManifest::from_bytes(br#"{"manifest_version":1}"#).unwrap();
792        assert!(m.lifecycle.is_empty() && m.capabilities.is_empty());
793        let m = DelegateManifest::from_bytes(
794            br#"{"manifest_version":1,"lifecycle":null,"capabilities":null}"#,
795        )
796        .unwrap();
797        assert!(m.lifecycle.is_empty() && m.capabilities.is_empty());
798        let m = DelegateManifest::from_bytes(
799            br#"{"manifest_version":1,"lifecycle":"installed","capabilities":{"background":{}}}"#,
800        )
801        .unwrap();
802        assert!(m.lifecycle.is_empty() && m.capabilities.is_empty());
803    }
804
805    /// A later format might write an entry that is not a bare name. That entry
806    /// is unknown to this reader; the known ones next to it must still count.
807    #[test]
808    fn a_non_string_entry_reads_as_unknown_not_as_an_error() {
809        let json = br#"{"manifest_version":2,
810            "lifecycle":[{"woke_up":{"every_s":60}},"node_started",7],
811            "capabilities":[{"notify":{"max_per_hour":4}},"background",null]}"#;
812        let m = DelegateManifest::from_bytes(json).unwrap();
813        assert_eq!(
814            m.lifecycle,
815            vec![
816                LifecycleKind::Unknown,
817                LifecycleKind::NodeStarted,
818                LifecycleKind::Unknown
819            ]
820        );
821        assert_eq!(m.known_capabilities(), vec![Capability::Background]);
822    }
823
824    #[test]
825    fn macro_agreement_check() {
826        assert!(__manifest_macro_agrees(
827            MANIFEST_SECTION_NAME,
828            MANIFEST_VERSION
829        ));
830        assert!(!__manifest_macro_agrees(
831            "freenet-manifesT",
832            MANIFEST_VERSION
833        ));
834        assert!(!__manifest_macro_agrees(
835            "freenet-manifest2",
836            MANIFEST_VERSION
837        ));
838        assert!(!__manifest_macro_agrees(
839            MANIFEST_SECTION_NAME,
840            MANIFEST_VERSION + 1
841        ));
842    }
843
844    #[test]
845    fn rejects_version_zero_oversize_and_garbage() {
846        assert_eq!(
847            DelegateManifest::from_bytes(br#"{"manifest_version":0}"#),
848            Err(ManifestError::BadVersion)
849        );
850        let big = vec![b' '; MAX_MANIFEST_BYTES + 1];
851        assert_eq!(
852            DelegateManifest::from_bytes(&big),
853            Err(ManifestError::TooLarge(MAX_MANIFEST_BYTES + 1))
854        );
855        assert!(matches!(
856            DelegateManifest::from_bytes(b"not json"),
857            Err(ManifestError::Decode(_))
858        ));
859    }
860
861    /// Two sections would be ambiguous. It also catches two crates in one
862    /// build each emitting a manifest: the linker would normally concatenate
863    /// same-named sections into one, which fails to decode instead, but a
864    /// post-link tool could leave them separate.
865    #[test]
866    fn rejects_a_duplicate_section() {
867        let payload = sample().to_bytes();
868        let m = module(&[
869            custom_section(MANIFEST_SECTION_NAME, &payload),
870            custom_section(MANIFEST_SECTION_NAME, &payload),
871        ]);
872        assert_eq!(
873            DelegateManifest::from_wasm(&m),
874            Err(ManifestError::Duplicate)
875        );
876    }
877
878    #[test]
879    fn concatenated_manifests_fail_to_decode() {
880        let mut payload = sample().to_bytes();
881        payload.extend(sample().to_bytes());
882        let m = module(&[custom_section(MANIFEST_SECTION_NAME, &payload)]);
883        assert!(matches!(
884            DelegateManifest::from_wasm(&m),
885            Err(ManifestError::Decode(_))
886        ));
887    }
888
889    #[test]
890    fn rejects_non_wasm_and_truncated_modules() {
891        assert_eq!(
892            DelegateManifest::from_wasm(b"\0asm"),
893            Err(ManifestError::NotWasm)
894        );
895        assert_eq!(
896            DelegateManifest::from_wasm(b"hello world, not wasm"),
897            Err(ManifestError::NotWasm)
898        );
899        let mut m = module(&[custom_section(MANIFEST_SECTION_NAME, &sample().to_bytes())]);
900        m.truncate(m.len() - 3);
901        assert_eq!(
902            DelegateManifest::from_wasm(&m),
903            Err(ManifestError::Malformed)
904        );
905        // A section whose declared size runs past the end.
906        let mut m = module(&[]);
907        m.extend([0x00, 0xff, 0xff, 0x03]);
908        assert_eq!(
909            DelegateManifest::from_wasm(&m),
910            Err(ManifestError::Malformed)
911        );
912        // An over-long LEB128 (more than 5 bytes).
913        let mut m = module(&[]);
914        m.extend([0x00, 0x80, 0x80, 0x80, 0x80, 0x80, 0x00]);
915        assert_eq!(
916            DelegateManifest::from_wasm(&m),
917            Err(ManifestError::Malformed)
918        );
919    }
920
921    /// Wire pin for the nested enum. Appending a variant is fine; reordering
922    /// or inserting one silently reinterprets deployed delegates' input.
923    #[test]
924    fn lifecycle_event_tags_are_pinned() {
925        fn tag(e: &LifecycleEvent) -> u32 {
926            match e {
927                LifecycleEvent::Installed => 0,
928                LifecycleEvent::NodeStarted { .. } => 1,
929            }
930        }
931        let all = [
932            LifecycleEvent::Installed,
933            LifecycleEvent::NodeStarted {
934                down_since_ms: Some(0x0102_0304_0506_0708),
935            },
936        ];
937        for e in &all {
938            let enc = bincode::serialize(e).unwrap();
939            assert_eq!(u32::from_le_bytes(enc[..4].try_into().unwrap()), tag(e));
940            assert_eq!(&bincode::deserialize::<LifecycleEvent>(&enc).unwrap(), e);
941        }
942        // Full byte layout of NodeStarted: tag 1, Option tag 1, u64 LE.
943        assert_eq!(
944            bincode::serialize(&all[1]).unwrap(),
945            vec![1, 0, 0, 0, 1, 8, 7, 6, 5, 4, 3, 2, 1]
946        );
947        // `kind()` agrees with the manifest kinds.
948        assert_eq!(all[0].kind(), LifecycleKind::Installed);
949        assert_eq!(all[1].kind(), LifecycleKind::NodeStarted);
950    }
951}