Skip to main content

sim_codec_binary/
codec.rs

1//! The `BinaryCodec` runtime object, `Lib` registration, and frame functions.
2//!
3//! Exposes the free `encode_*` / `decode_*` frame helpers and wires the binary
4//! reader and writer into the codec decoder/encoder, located, and tree traits.
5
6use std::sync::Arc;
7
8use sim_codec::{
9    CodecDefaultDecode, CodecRuntime, Decoder, Encoder, Input, LocatedDecoder, LocatedEncoder,
10    Output, ReadCx, TreeDecoder, TreeEncoder, codec_value, validate_expr_tree,
11};
12use sim_kernel::{
13    AbiVersion, DefaultFactory, Dependency, Error, Export, Expr, Lib, LibManifest, LibTarget,
14    Linker, LocatedExpr, LocatedExprTree, Result, Symbol, Version, WriteCx,
15};
16
17use crate::cookbook::{BinaryRoundtripReport, roundtrip_report_symbol};
18use crate::reader::BinaryReader;
19use crate::writer::BinaryWriter;
20use crate::{BinaryFrame, DecodeLimits, FLAG_NONE, FLAG_ORIGIN, FLAG_TREE_ORIGIN, FrameTables};
21
22/// Binary codec runtime object that round-trips kernel `Expr` values as compact
23/// tagged frames.
24///
25/// As a domain codec it speaks exactly its own byte frame format: it implements
26/// every codec role -- [`Decoder`]/[`Encoder`], located
27/// [`LocatedDecoder`]/[`LocatedEncoder`], and tree
28/// [`TreeDecoder`]/[`TreeEncoder`] -- over the shared `Expr` graph, and fails
29/// closed (under [`DecodeLimits`]) on any input that is not a well-formed frame.
30/// Decoded bytes are treated strictly as data, never as executable input.
31pub struct BinaryCodec;
32
33impl Decoder for BinaryCodec {
34    fn decode(&self, cx: &mut ReadCx<'_>, input: Input) -> Result<Expr> {
35        let bytes = match input {
36            Input::Text(text) => text.into_bytes(),
37            Input::Bytes(bytes) => bytes,
38        };
39        decode_located_tree_frame_with_limits(cx.codec, &bytes, DecodeLimits::from(cx.limits))
40            .map(|(_, tree)| tree.located().expr)
41    }
42}
43
44impl Encoder for BinaryCodec {
45    fn encode(&self, _cx: &mut WriteCx<'_>, expr: &Expr) -> Result<Output> {
46        Ok(Output::Bytes(encode_frame(expr)?.0))
47    }
48}
49
50impl LocatedDecoder for BinaryCodec {
51    fn decode_located(
52        &self,
53        cx: &mut ReadCx<'_>,
54        input: Input,
55        _source_id: String,
56    ) -> Result<LocatedExpr> {
57        let bytes = match input {
58            Input::Text(text) => text.into_bytes(),
59            Input::Bytes(bytes) => bytes,
60        };
61        decode_located_tree_frame_with_limits(cx.codec, &bytes, DecodeLimits::from(cx.limits))
62            .map(|(_, tree)| tree.located())
63    }
64}
65
66impl LocatedEncoder for BinaryCodec {
67    fn encode_located(&self, cx: &mut WriteCx<'_>, expr: &LocatedExpr) -> Result<Output> {
68        Ok(Output::Bytes(
69            encode_located_frame(expr, cx.options.lossless_origin)?.0,
70        ))
71    }
72}
73
74impl TreeDecoder for BinaryCodec {
75    fn decode_tree(
76        &self,
77        cx: &mut ReadCx<'_>,
78        input: Input,
79        _source_id: String,
80    ) -> Result<LocatedExprTree> {
81        let bytes = match input {
82            Input::Text(text) => text.into_bytes(),
83            Input::Bytes(bytes) => bytes,
84        };
85        decode_located_tree_frame_with_limits(cx.codec, &bytes, DecodeLimits::from(cx.limits))
86            .map(|(_, tree)| tree)
87    }
88}
89
90impl TreeEncoder for BinaryCodec {
91    fn encode_tree(&self, cx: &mut WriteCx<'_>, expr: &LocatedExprTree) -> Result<Output> {
92        validate_expr_tree(cx.codec, expr)?;
93        Ok(Output::Bytes(
94            encode_located_tree_frame(expr, cx.options.lossless_origin)?.0,
95        ))
96    }
97}
98
99/// Encodes a bare [`Expr`] into a [`BinaryFrame`], without source origins.
100pub fn encode_frame(expr: &Expr) -> Result<BinaryFrame> {
101    encode_located_frame(
102        &LocatedExpr {
103            expr: expr.clone(),
104            origin: None,
105        },
106        false,
107    )
108}
109
110/// Encodes a [`LocatedExpr`] into a [`BinaryFrame`].
111///
112/// When `include_origin` is set and `located` carries an origin, the frame is
113/// flagged to carry that single origin so the located form round-trips.
114pub fn encode_located_frame(located: &LocatedExpr, include_origin: bool) -> Result<BinaryFrame> {
115    let tables = FrameTables::collect(&located.expr);
116    let mut writer = BinaryWriter::new(tables)?;
117    writer.flags = if include_origin && located.origin.is_some() {
118        FLAG_ORIGIN
119    } else {
120        FLAG_NONE
121    };
122    writer.write_header()?;
123    writer.write_expr(&located.expr)?;
124    if writer.flags & FLAG_ORIGIN != 0 {
125        writer.write_origin(
126            located
127                .origin
128                .as_ref()
129                .expect("origin flag requires origin payload"),
130        )?;
131    }
132    Ok(BinaryFrame(writer.bytes))
133}
134
135/// Encodes a [`LocatedExprTree`] into a [`BinaryFrame`].
136///
137/// When `include_origin` is set the frame carries the per-node origin tree so
138/// that the full located tree round-trips; otherwise only the `Expr` body is
139/// written. The tree is validated before encoding and rejected if malformed.
140pub fn encode_located_tree_frame(
141    tree: &LocatedExprTree,
142    include_origin: bool,
143) -> Result<BinaryFrame> {
144    validate_expr_tree(sim_kernel::CodecId(0), tree)?;
145    let tables = FrameTables::collect(&tree.expr);
146    let mut writer = BinaryWriter::new(tables)?;
147    writer.flags = if include_origin {
148        FLAG_TREE_ORIGIN
149    } else {
150        FLAG_NONE
151    };
152    writer.write_header()?;
153    writer.write_expr(&tree.expr)?;
154    if writer.flags & FLAG_TREE_ORIGIN != 0 {
155        writer.write_origin_tree(tree)?;
156    }
157    Ok(BinaryFrame(writer.bytes))
158}
159
160/// Decodes frame `bytes` into its side [`FrameTables`] and bare [`Expr`].
161///
162/// Any source origins carried by the frame are dropped. Decoding is bounded by
163/// the default [`DecodeLimits`] and fails closed on malformed or oversize input.
164pub fn decode_frame(codec: sim_kernel::CodecId, bytes: &[u8]) -> Result<(FrameTables, Expr)> {
165    let located = decode_located_frame(codec, bytes)?;
166    Ok((located.0, located.1.expr))
167}
168
169/// Decodes frame `bytes` into its side [`FrameTables`] and a [`LocatedExpr`].
170///
171/// The top-level origin is recovered when the frame carries one. Decoding is
172/// bounded by the default [`DecodeLimits`] and fails closed on bad input.
173pub fn decode_located_frame(
174    codec: sim_kernel::CodecId,
175    bytes: &[u8],
176) -> Result<(FrameTables, LocatedExpr)> {
177    let (tables, tree) = decode_located_tree_frame(codec, bytes)?;
178    Ok((tables, tree.located()))
179}
180
181/// Decodes frame `bytes` into its side [`FrameTables`] and a full
182/// [`LocatedExprTree`], using the default [`DecodeLimits`].
183///
184/// The per-node origin tree is recovered when the frame carries one. This is
185/// the most complete decode entry point; see
186/// [`decode_located_tree_frame_with_limits`] to supply explicit limits.
187pub fn decode_located_tree_frame(
188    codec: sim_kernel::CodecId,
189    bytes: &[u8],
190) -> Result<(FrameTables, LocatedExprTree)> {
191    decode_located_tree_frame_with_limits(codec, bytes, DecodeLimits::default())
192}
193
194/// Decodes frame `bytes` into its side [`FrameTables`] and a
195/// [`LocatedExprTree`], enforcing the supplied `limits`.
196///
197/// This is the bounded decode primitive the codec roles call. It rejects bad
198/// magic/version/flags, out-of-range table indices, oversize counts, and any
199/// trailing bytes after the payload, failing closed on untrusted input.
200pub fn decode_located_tree_frame_with_limits(
201    codec: sim_kernel::CodecId,
202    bytes: &[u8],
203    limits: DecodeLimits,
204) -> Result<(FrameTables, LocatedExprTree)> {
205    let mut reader = BinaryReader::new(codec, bytes, limits)?;
206    let tables = reader.read_header()?;
207    let expr = reader.read_expr()?;
208    let mut tree = if reader.flags & FLAG_TREE_ORIGIN != 0 {
209        reader.read_origin_tree(expr)?
210    } else {
211        LocatedExprTree::from_expr_recursive(expr)
212    };
213    if reader.flags & FLAG_ORIGIN != 0 {
214        tree.origin = Some(reader.read_origin()?);
215    }
216    if !reader.is_empty() {
217        return Err(Error::CodecError {
218            codec,
219            message: "trailing bytes after binary payload".to_owned(),
220        });
221    }
222    Ok((tables, tree))
223}
224
225/// [`Lib`] that registers the binary codec with the runtime.
226///
227/// Its manifest exports the `codec/binary` codec, and loading wires a
228/// [`BinaryCodec`] into the linker as the decode and encode surface for all
229/// codec roles.
230pub struct BinaryCodecLib {
231    symbol: Symbol,
232    codec_id: sim_kernel::CodecId,
233}
234
235impl BinaryCodecLib {
236    /// Creates the codec lib bound to the runtime-assigned `id` for
237    /// `codec/binary`.
238    pub fn new(id: sim_kernel::CodecId) -> Self {
239        Self {
240            symbol: Symbol::qualified("codec", "binary"),
241            codec_id: id,
242        }
243    }
244}
245
246impl Lib for BinaryCodecLib {
247    fn manifest(&self) -> LibManifest {
248        LibManifest {
249            id: self.symbol.clone(),
250            version: Version(env!("CARGO_PKG_VERSION").to_owned()),
251            abi: AbiVersion { major: 0, minor: 1 },
252            target: LibTarget::HostRegistered,
253            requires: Vec::<Dependency>::new(),
254            capabilities: Vec::new(),
255            exports: vec![
256                Export::Codec {
257                    symbol: self.symbol.clone(),
258                    codec_id: Some(self.codec_id),
259                },
260                Export::Function {
261                    symbol: roundtrip_report_symbol(),
262                    function_id: None,
263                },
264            ],
265        }
266    }
267
268    fn load(&self, cx: &mut sim_kernel::LoadCx, linker: &mut Linker) -> Result<()> {
269        let _factory = DefaultFactory;
270        let expr_shape =
271            sim_codec::resolve_expr_shape(linker, &Symbol::qualified("codec", "BinaryFrame"))?;
272        let options_shape = sim_codec::resolve_options_shape(linker)?;
273
274        linker.codec_value(
275            self.symbol.clone(),
276            codec_value(CodecRuntime {
277                id: self.codec_id,
278                symbol: self.symbol.clone(),
279                decoder: Some(Arc::new(BinaryCodec)),
280                located_decoder: Some(Arc::new(BinaryCodec)),
281                tree_decoder: Some(Arc::new(BinaryCodec)),
282                encoder: Some(Arc::new(BinaryCodec)),
283                located_encoder: Some(Arc::new(BinaryCodec)),
284                tree_encoder: Some(Arc::new(BinaryCodec)),
285                expr_shape,
286                options_shape,
287                default_decode: CodecDefaultDecode::Datum,
288            }),
289        )?;
290        linker.function_value(
291            roundtrip_report_symbol(),
292            cx.factory().opaque(Arc::new(BinaryRoundtripReport))?,
293        )?;
294        Ok(())
295    }
296}