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