firmion_extension/lib.rs
1// Extension API for the Firmion binary image compiler.
2//
3// This crate defines the public API for Firmion extension authors.
4//
5// FirmionExtension -- the single trait all extensions implement.
6//
7// ExtArg -- typed argument passed to execute(). Each parameter passed
8// to an extension in Firmion code maps to one ExtArg passed to the extension
9// by the compiler.
10//
11// Argument types:
12// Int(u64) -- numeric expression (u64, i64, or integer const)
13// Str(&str) -- quoted string const
14// Slice { start, len, data } -- Data slice from the in-flight output data.
15// Firmion automatically converts sections names
16// into slices.
17//
18// Args may appear in any order. Firmion resolves the section name to its
19// location and provides the image bytes without extension authors having to
20// compute offsets.
21//
22// Firmion registers extensions at startup via the ExtensionRegistry.
23// Firmion calls size() exactly once during registration and caches
24// the result. The `firmion` and `std` namespaces are reserved.
25//
26// Named parameters:
27// An extension declares named parameters via params(), returning a slice of
28// ParamDesc values. Each ParamDesc pairs a name with a ParamKind.
29// ParamKind::Slice declares that a parameter accepts a sequence of bytes
30// (supplied at call sites as a section name). ParamKind::Int and
31// ParamKind::Str declare numeric and string parameters respectively.
32
33// Don't clutter upstream docs.rs for an otherwise private library.
34#![doc(hidden)]
35
36pub mod extension_registry;
37pub mod test_mocks;
38
39/// The kind of a declared extension parameter.
40///
41/// Used in [`ParamDesc`] to specify what type of argument a parameter accepts.
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub enum ParamKind {
44 /// A numeric argument: u64, i64, or an untyped integer constant.
45 Int,
46 /// A quoted string constant.
47 Str,
48 /// A sequence of bytes with starting offset and length.
49 Slice,
50}
51
52/// Describes one declared parameter of an extension.
53///
54/// An extension returns a slice of `ParamDesc` from [`FirmionExtension::params`]
55/// to opt into named-argument call sites.
56#[derive(Debug, Clone, Copy)]
57pub struct ParamDesc {
58 /// The parameter name used at call sites: `foo::bar(name=value)`.
59 pub name: &'static str,
60 /// The kind of value the parameter accepts.
61 pub kind: ParamKind,
62}
63
64/// The argument type passed to an extension at execution time.
65///
66/// Each Firmion call-site argument produces exactly one `ExtArg`.
67#[derive(Debug)]
68pub enum ParamArg<'a> {
69 /// A numeric argument: u64, i64, or an untyped integer constant.
70 Int(u64),
71 /// A quoted string constant.
72 Str(&'a str),
73 /// A slice which includes an immutable slice of the output data.
74 Slice {
75 /// Read-only view of the output image at `start..start+len`.
76 data: &'a [u8],
77 },
78}
79
80/// Implement this trait to create a Firmion extension.
81///
82/// An extension writes a fixed number of bytes into the output image.
83/// Arguments arrive as typed [`ExtArg`] values corresponding 1:1 to the
84/// Firmion call-site arguments. A section name argument delivers the image
85/// bytes at that section directly in the [`ExtArg::Section`] variant.
86///
87/// # Example: numeric argument
88///
89/// ```rust
90/// use firmion_extension::{FirmionExtension, ParamArg};
91///
92/// pub struct MyCrc;
93///
94/// impl FirmionExtension for MyCrc {
95/// fn name(&self) -> &str { "my_org::crc" }
96/// fn size(&self) -> usize { 4 }
97/// fn execute<'a>(&self, args: &[ParamArg<'a>], out: &mut [u8]) -> Result<(), String> {
98/// let val = match args.first() {
99/// Some(ParamArg::Int(v)) => *v as u32,
100/// _ => return Err("Expected one numeric argument".to_string()),
101/// };
102/// out.copy_from_slice(&val.to_be_bytes());
103/// Ok(())
104/// }
105/// }
106/// ```
107///
108/// # Example: section argument
109///
110/// ```rust
111/// use firmion_extension::{FirmionExtension, ParamArg};
112///
113/// pub struct MyChecksum;
114///
115/// impl FirmionExtension for MyChecksum {
116/// fn name(&self) -> &str { "my_org::checksum" }
117/// fn size(&self) -> usize { 8 }
118/// fn execute<'a>(&self, args: &[ParamArg<'a>], out: &mut [u8]) -> Result<(), String> {
119/// let data = match args.first() {
120/// Some(ParamArg::Slice { data }) => *data,
121/// _ => return Err("Expected a slice argument".to_string()),
122/// };
123/// let sum: u64 = data.iter().map(|&b| b as u64).sum();
124/// out.copy_from_slice(&sum.to_le_bytes());
125/// Ok(())
126/// }
127/// }
128/// ```
129pub trait FirmionExtension {
130 /// Returns the namespace-qualified name used to invoke the extension.
131 /// For example, `"my_org::crc"`. The `firmion` and `std` namespaces are
132 /// reserved for internal use.
133 fn name(&self) -> &str;
134
135 /// Returns the exact number of bytes the extension writes to `out`.
136 ///
137 /// Firmion calls this method once at registration and caches the result.
138 /// All layout calculations use the cached value.
139 fn size(&self) -> usize;
140
141 /// Returns the declared parameters for this extension.
142 ///
143 /// Each entry in the slice declares one parameter: its name and kind.
144 /// Returns the declared parameters for this extension.
145 ///
146 /// Each entry declares one parameter: its name and kind. Firmion validates
147 /// argument names against this slice and reorders call-site args to
148 /// declaration order before passing them to [`execute`](Self::execute).
149 fn params(&self) -> &[ParamDesc] {
150 &[]
151 }
152
153 /// Produces the extension output bytes.
154 ///
155 /// * `args` -- one [`ExtArg`] per Firmion call-site argument, in declaration order.
156 /// * `out` -- pre-allocated buffer of exactly [`size`](Self::size) bytes.
157 ///
158 /// Return `Err(message)` to abort compilation with a diagnostic.
159 fn execute<'a>(&self, args: &[ParamArg<'a>], out: &mut [u8]) -> Result<(), String>;
160}