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
11pub 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#[derive(Clone, Debug, PartialEq, Eq)]
62pub struct BinaryLibPack {
63 pub manifest: sim_kernel::LibManifest,
65 pub exports: Vec<ReexportSpec>,
67}
68
69pub 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
94pub 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}