Skip to main content

sim/loaders/
binary_pack.rs

1use sim_kernel::{Cx, Lib, LibLoader, LibSource, Result};
2
3use crate::loaders::{
4    reexport::{ReexportKind, ReexportLib, ReexportSpec},
5    shared::{expr_kind, parse_symbol_text},
6};
7
8const BINARY_PACK_MAGIC: &[u8; 4] = b"L8PK";
9const BINARY_PACK_VERSION: u32 = 1;
10
11/// Loader for `.l8b` binary lib packs, recognized by path extension or magic.
12pub struct BinaryPackLoader;
13
14impl Default for BinaryPackLoader {
15    fn default() -> Self {
16        Self
17    }
18}
19
20impl LibLoader for BinaryPackLoader {
21    fn can_load(&self, source: &LibSource) -> bool {
22        match source {
23            LibSource::Path(path) => path.extension().is_some_and(|ext| ext == "l8b"),
24            LibSource::Bytes(bytes) => has_binary_pack_magic(bytes),
25            LibSource::Url(_) => false,
26            LibSource::Symbol(_) | LibSource::Host(_) => false,
27        }
28    }
29
30    fn load(&self, _cx: &mut Cx, source: LibSource) -> Result<Box<dyn Lib>> {
31        let bytes = read_pack_source(source)?;
32        let pack = decode_binary_lib_pack(&bytes)?;
33        Ok(Box::new(ReexportLib::new(pack.manifest, pack.exports)))
34    }
35
36    fn inspect_manifest(
37        &self,
38        _cx: &mut Cx,
39        source: &LibSource,
40    ) -> Result<Option<sim_kernel::LibManifest>> {
41        let bytes = match source {
42            LibSource::Path(path) => std::fs::read(path).map_err(|err| {
43                sim_kernel::Error::HostError(format!(
44                    "failed to read binary lib pack {}: {err}",
45                    path.display()
46                ))
47            })?,
48            LibSource::Bytes(bytes) => bytes.clone(),
49            LibSource::Url(url) => {
50                return Err(sim_kernel::Error::HostError(format!(
51                    "url inspection is not implemented for binary lib pack {url}"
52                )));
53            }
54            _ => return Ok(None),
55        };
56        Ok(Some(decode_binary_lib_pack(&bytes)?.manifest))
57    }
58}
59
60/// Decoded contents of a binary lib pack: a manifest plus its re-export specs.
61#[derive(Clone, Debug, PartialEq, Eq)]
62pub struct BinaryLibPack {
63    /// Manifest of the packed lib.
64    pub manifest: sim_kernel::LibManifest,
65    /// Re-export entries the pack links when loaded.
66    pub exports: Vec<ReexportSpec>,
67}
68
69/// Encodes a binary lib pack into its byte representation.
70pub fn encode_binary_lib_pack(pack: &BinaryLibPack) -> Result<Vec<u8>> {
71    let manifest_expr = manifest_to_expr(&pack.manifest);
72    let exports_expr = reexports_to_expr(&pack.exports);
73    let manifest = sim_codec_binary::encode_frame(&manifest_expr)?.0;
74    let exports = sim_codec_binary::encode_frame(&exports_expr)?.0;
75
76    let mut bytes = Vec::with_capacity(16 + manifest.len() + exports.len());
77    bytes.extend_from_slice(BINARY_PACK_MAGIC);
78    bytes.extend_from_slice(&BINARY_PACK_VERSION.to_le_bytes());
79    bytes.extend_from_slice(
80        &u32::try_from(manifest.len())
81            .map_err(|_| sim_kernel::Error::HostError("manifest frame too large".to_owned()))?
82            .to_le_bytes(),
83    );
84    bytes.extend_from_slice(
85        &u32::try_from(exports.len())
86            .map_err(|_| sim_kernel::Error::HostError("export frame too large".to_owned()))?
87            .to_le_bytes(),
88    );
89    bytes.extend_from_slice(&manifest);
90    bytes.extend_from_slice(&exports);
91    Ok(bytes)
92}
93
94/// Decodes a binary lib pack from its byte representation, validating the
95/// header.
96pub fn decode_binary_lib_pack(bytes: &[u8]) -> Result<BinaryLibPack> {
97    if !has_binary_pack_magic(bytes) {
98        return Err(sim_kernel::Error::HostError(
99            "invalid binary lib pack magic".to_owned(),
100        ));
101    }
102    if bytes.len() < 16 {
103        return Err(sim_kernel::Error::HostError(
104            "binary lib pack header is truncated".to_owned(),
105        ));
106    }
107    let version = read_u32(bytes, 4)?;
108    if version != BINARY_PACK_VERSION {
109        return Err(sim_kernel::Error::HostError(format!(
110            "unsupported binary lib pack version {version}"
111        )));
112    }
113    let manifest_len = read_u32(bytes, 8)? as usize;
114    let exports_len = read_u32(bytes, 12)? as usize;
115    let manifest_start: usize = 16;
116    let manifest_end = manifest_start
117        .checked_add(manifest_len)
118        .ok_or_else(|| sim_kernel::Error::HostError("manifest frame length overflow".to_owned()))?;
119    let exports_end = manifest_end
120        .checked_add(exports_len)
121        .ok_or_else(|| sim_kernel::Error::HostError("export frame length overflow".to_owned()))?;
122    if exports_end != bytes.len() {
123        return Err(sim_kernel::Error::HostError(
124            "binary lib pack length does not match header".to_owned(),
125        ));
126    }
127    let (_, manifest_expr) = sim_codec_binary::decode_frame(
128        sim_kernel::CodecId(0),
129        &bytes[manifest_start..manifest_end],
130    )?;
131    let (_, exports_expr) =
132        sim_codec_binary::decode_frame(sim_kernel::CodecId(0), &bytes[manifest_end..exports_end])?;
133    Ok(BinaryLibPack {
134        manifest: expr_to_manifest(manifest_expr)?,
135        exports: expr_to_reexports(exports_expr)?,
136    })
137}
138
139fn read_pack_source(source: LibSource) -> Result<Vec<u8>> {
140    match source {
141        LibSource::Path(path) => std::fs::read(&path).map_err(|err| {
142            sim_kernel::Error::HostError(format!(
143                "failed to read binary lib pack {}: {err}",
144                path.display()
145            ))
146        }),
147        LibSource::Bytes(bytes) => Ok(bytes),
148        LibSource::Url(url) => Err(sim_kernel::Error::HostError(format!(
149            "url loading is not implemented for binary lib pack {url}"
150        ))),
151        _ => Err(sim_kernel::Error::HostError(
152            "binary pack loader received unsupported source".to_owned(),
153        )),
154    }
155}
156
157pub(super) fn manifest_to_expr(manifest: &sim_kernel::LibManifest) -> sim_kernel::Expr {
158    sim_kernel::Expr::Map(vec![
159        symbol_entry("id", sim_kernel::Expr::Symbol(manifest.id.clone())),
160        symbol_entry(
161            "version",
162            sim_kernel::Expr::String(manifest.version.0.clone()),
163        ),
164        symbol_entry("abi-major", number_expr(manifest.abi.major)),
165        symbol_entry("abi-minor", number_expr(manifest.abi.minor)),
166        symbol_entry(
167            "target",
168            sim_kernel::Expr::String(lib_target_name(&manifest.target)),
169        ),
170        symbol_entry(
171            "requires",
172            sim_kernel::Expr::List(
173                manifest
174                    .requires
175                    .iter()
176                    .map(|dependency| {
177                        sim_kernel::Expr::Map(vec![
178                            symbol_entry("id", sim_kernel::Expr::Symbol(dependency.id.clone())),
179                            symbol_entry(
180                                "minimum-version",
181                                dependency
182                                    .minimum_version
183                                    .as_ref()
184                                    .map(|version| sim_kernel::Expr::String(version.0.clone()))
185                                    .unwrap_or(sim_kernel::Expr::Nil),
186                            ),
187                        ])
188                    })
189                    .collect(),
190            ),
191        ),
192        symbol_entry(
193            "capabilities",
194            sim_kernel::Expr::List(
195                manifest
196                    .capabilities
197                    .iter()
198                    .map(|capability| sim_kernel::Expr::String(capability.as_str().to_owned()))
199                    .collect(),
200            ),
201        ),
202        symbol_entry(
203            "exports",
204            sim_kernel::Expr::List(
205                manifest
206                    .exports
207                    .iter()
208                    .map(|export| {
209                        let kind = match export {
210                            sim_kernel::Export::Class { symbol, .. } => ("class", symbol),
211                            sim_kernel::Export::Function { symbol, .. } => ("function", symbol),
212                            sim_kernel::Export::Macro { symbol, .. } => ("macro", symbol),
213                            sim_kernel::Export::Shape { symbol, .. } => ("shape", symbol),
214                            sim_kernel::Export::Codec { symbol, .. } => ("codec", symbol),
215                            sim_kernel::Export::NumberDomain { symbol, .. } => {
216                                ("number-domain", symbol)
217                            }
218                            sim_kernel::Export::Value { symbol } => ("value", symbol),
219                            sim_kernel::Export::Site { symbol, .. } => ("site", symbol),
220                        };
221                        sim_kernel::Expr::Map(vec![
222                            symbol_entry("kind", sim_kernel::Expr::String(kind.0.to_owned())),
223                            symbol_entry("symbol", sim_kernel::Expr::Symbol(kind.1.clone())),
224                        ])
225                    })
226                    .collect(),
227            ),
228        ),
229    ])
230}
231
232pub(super) fn expr_to_manifest(expr: sim_kernel::Expr) -> Result<sim_kernel::LibManifest> {
233    Ok(sim_kernel::LibManifest {
234        id: expect_symbol_field(&expr, "id")?,
235        version: sim_kernel::Version(expect_string_field(&expr, "version")?),
236        abi: sim_kernel::AbiVersion {
237            major: expect_u16_field(&expr, "abi-major")?,
238            minor: expect_u16_field(&expr, "abi-minor")?,
239        },
240        target: parse_lib_target(&expect_string_field(&expr, "target")?)?,
241        requires: expect_list_field(&expr, "requires")?
242            .into_iter()
243            .map(|entry| {
244                Ok(sim_kernel::Dependency {
245                    id: expect_symbol_field(&entry, "id")?,
246                    minimum_version: expect_optional_string_field(&entry, "minimum-version")?
247                        .map(sim_kernel::Version),
248                })
249            })
250            .collect::<Result<Vec<_>>>()?,
251        capabilities: expect_list_field(&expr, "capabilities")?
252            .into_iter()
253            .map(|entry| match entry {
254                sim_kernel::Expr::String(capability) => {
255                    Ok(sim_kernel::CapabilityName::new(capability))
256                }
257                other => Err(sim_kernel::Error::Lib(format!(
258                    "expected capability string, found {:?}",
259                    expr_kind(&other)
260                ))),
261            })
262            .collect::<Result<Vec<_>>>()?,
263        exports: expect_list_field(&expr, "exports")?
264            .into_iter()
265            .map(expr_to_manifest_export)
266            .collect::<Result<Vec<_>>>()?,
267    })
268}
269
270fn reexports_to_expr(exports: &[ReexportSpec]) -> sim_kernel::Expr {
271    sim_kernel::Expr::List(
272        exports
273            .iter()
274            .map(|export| {
275                sim_kernel::Expr::Map(vec![
276                    symbol_entry(
277                        "kind",
278                        sim_kernel::Expr::String(
279                            match export.kind {
280                                ReexportKind::Class => "class",
281                                ReexportKind::Function => "function",
282                                ReexportKind::Macro => "macro",
283                                ReexportKind::Shape => "shape",
284                                ReexportKind::Codec => "codec",
285                                ReexportKind::NumberDomain => "number-domain",
286                                ReexportKind::Value => "value",
287                            }
288                            .to_owned(),
289                        ),
290                    ),
291                    symbol_entry("export", sim_kernel::Expr::Symbol(export.export.clone())),
292                    symbol_entry("target", sim_kernel::Expr::Symbol(export.target.clone())),
293                ])
294            })
295            .collect(),
296    )
297}
298
299fn expr_to_reexports(expr: sim_kernel::Expr) -> Result<Vec<ReexportSpec>> {
300    let sim_kernel::Expr::List(entries) = expr else {
301        return Err(sim_kernel::Error::Lib(
302            "expected binary lib pack reexports list".to_owned(),
303        ));
304    };
305    entries
306        .into_iter()
307        .map(|entry| {
308            let kind = match expect_string_field(&entry, "kind")?.as_str() {
309                "class" => ReexportKind::Class,
310                "function" => ReexportKind::Function,
311                "macro" => ReexportKind::Macro,
312                "shape" => ReexportKind::Shape,
313                "codec" => ReexportKind::Codec,
314                "number-domain" => ReexportKind::NumberDomain,
315                "value" => ReexportKind::Value,
316                other => {
317                    return Err(sim_kernel::Error::Lib(format!(
318                        "unknown reexport kind {other}"
319                    )));
320                }
321            };
322            Ok(ReexportSpec {
323                kind,
324                export: expect_symbol_field(&entry, "export")?,
325                target: expect_symbol_field(&entry, "target")?,
326            })
327        })
328        .collect()
329}
330
331fn expr_to_manifest_export(expr: sim_kernel::Expr) -> Result<sim_kernel::Export> {
332    let kind = expect_string_field(&expr, "kind")?;
333    let symbol = expect_symbol_field(&expr, "symbol")?;
334    match kind.as_str() {
335        "class" => Ok(sim_kernel::Export::Class {
336            symbol,
337            class_id: None,
338        }),
339        "function" => Ok(sim_kernel::Export::Function {
340            symbol,
341            function_id: None,
342        }),
343        "macro" => Ok(sim_kernel::Export::Macro {
344            symbol,
345            macro_id: None,
346        }),
347        "shape" => Ok(sim_kernel::Export::Shape {
348            symbol,
349            shape_id: None,
350        }),
351        "codec" => Ok(sim_kernel::Export::Codec {
352            symbol,
353            codec_id: None,
354        }),
355        "number-domain" => Ok(sim_kernel::Export::NumberDomain {
356            symbol,
357            number_domain_id: None,
358        }),
359        "value" => Ok(sim_kernel::Export::Value { symbol }),
360        other => Err(sim_kernel::Error::Lib(format!(
361            "unknown manifest export kind {other}"
362        ))),
363    }
364}
365
366fn symbol_entry(key: &str, value: sim_kernel::Expr) -> (sim_kernel::Expr, sim_kernel::Expr) {
367    (
368        sim_kernel::Expr::Symbol(sim_kernel::Symbol::new(key)),
369        value,
370    )
371}
372
373fn number_expr(value: impl ToString) -> sim_kernel::Expr {
374    sim_kernel::Expr::Number(sim_kernel::NumberLiteral {
375        domain: sim_kernel::Symbol::qualified("numbers", "f64"),
376        canonical: value.to_string(),
377    })
378}
379
380fn lib_target_name(target: &sim_kernel::LibTarget) -> String {
381    target.to_symbol().as_qualified_str()
382}
383
384fn parse_lib_target(name: &str) -> Result<sim_kernel::LibTarget> {
385    Ok(sim_kernel::LibTarget::from_symbol(&parse_symbol_text(name)))
386}
387
388fn expect_map_field<'a>(expr: &'a sim_kernel::Expr, field: &str) -> Result<&'a sim_kernel::Expr> {
389    let sim_kernel::Expr::Map(entries) = expr else {
390        return Err(sim_kernel::Error::Lib(format!(
391            "expected map expr for field lookup, found {:?}",
392            expr_kind(expr)
393        )));
394    };
395    entries
396        .iter()
397        .find_map(|(key, value)| match key {
398            sim_kernel::Expr::Symbol(symbol)
399                if symbol.name.as_ref() == field && symbol.namespace.is_none() =>
400            {
401                Some(value)
402            }
403            _ => None,
404        })
405        .ok_or_else(|| sim_kernel::Error::Lib(format!("missing field {field}")))
406}
407
408fn expect_list_field(expr: &sim_kernel::Expr, field: &str) -> Result<Vec<sim_kernel::Expr>> {
409    match expect_map_field(expr, field)? {
410        sim_kernel::Expr::List(items) => Ok(items.clone()),
411        other => Err(sim_kernel::Error::Lib(format!(
412            "expected list field {field}, found {:?}",
413            expr_kind(other)
414        ))),
415    }
416}
417
418fn expect_symbol_field(expr: &sim_kernel::Expr, field: &str) -> Result<sim_kernel::Symbol> {
419    match expect_map_field(expr, field)? {
420        sim_kernel::Expr::Symbol(symbol) => Ok(symbol.clone()),
421        sim_kernel::Expr::String(value) => Ok(parse_symbol_text(value)),
422        other => Err(sim_kernel::Error::Lib(format!(
423            "expected symbol field {field}, found {:?}",
424            expr_kind(other)
425        ))),
426    }
427}
428
429fn expect_string_field(expr: &sim_kernel::Expr, field: &str) -> Result<String> {
430    match expect_map_field(expr, field)? {
431        sim_kernel::Expr::String(value) => Ok(value.clone()),
432        other => Err(sim_kernel::Error::Lib(format!(
433            "expected string field {field}, found {:?}",
434            expr_kind(other)
435        ))),
436    }
437}
438
439fn expect_optional_string_field(expr: &sim_kernel::Expr, field: &str) -> Result<Option<String>> {
440    match expect_map_field(expr, field)? {
441        sim_kernel::Expr::Nil => Ok(None),
442        sim_kernel::Expr::String(value) => Ok(Some(value.clone())),
443        other => Err(sim_kernel::Error::Lib(format!(
444            "expected optional string field {field}, found {:?}",
445            expr_kind(other)
446        ))),
447    }
448}
449
450fn expect_u16_field(expr: &sim_kernel::Expr, field: &str) -> Result<u16> {
451    match expect_map_field(expr, field)? {
452        sim_kernel::Expr::Number(number) => number
453            .canonical
454            .parse::<u16>()
455            .map_err(|err| sim_kernel::Error::Lib(format!("invalid {field} number: {err}"))),
456        other => Err(sim_kernel::Error::Lib(format!(
457            "expected numeric field {field}, found {:?}",
458            expr_kind(other)
459        ))),
460    }
461}
462
463fn has_binary_pack_magic(bytes: &[u8]) -> bool {
464    bytes.get(..4) == Some(BINARY_PACK_MAGIC.as_slice())
465}
466
467fn read_u32(bytes: &[u8], offset: usize) -> Result<u32> {
468    let raw = bytes.get(offset..offset + 4).ok_or_else(|| {
469        sim_kernel::Error::HostError("binary lib pack header is truncated".to_owned())
470    })?;
471    let mut buf = [0u8; 4];
472    buf.copy_from_slice(raw);
473    Ok(u32::from_le_bytes(buf))
474}