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