Skip to main content

sim_codec_binary_base64/
codec.rs

1//! The `BinaryBase64Codec` runtime object and its `Lib` registration.
2//!
3//! Implements the codec traits by delegating frame encode/decode to
4//! `sim-codec-binary` and adding the base64 text wrapping on either side.
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, CodecId, DefaultFactory, Dependency, Error, Export, Expr, Lib, LibManifest,
14    LibTarget, Linker, LocatedExpr, LocatedExprTree, Result, Symbol, Version, WriteCx,
15};
16
17use crate::base64::{decode_base64_with_limits, encode_base64};
18use crate::cookbook::{BinaryBase64RoundtripReport, roundtrip_report_symbol};
19
20/// Codec runtime object that carries `sim-codec-binary` frames as base64 text.
21///
22/// This domain codec is a thin text wrapper: it implements every codec role --
23/// [`Decoder`]/[`Encoder`], located [`LocatedDecoder`]/[`LocatedEncoder`], and
24/// tree [`TreeDecoder`]/[`TreeEncoder`] -- by delegating frame encode/decode to
25/// `sim-codec-binary` and base64-encoding the bytes on the way out and
26/// base64-decoding them on the way in. The base64 text and the underlying bytes
27/// are untrusted data; malformed input fails closed and is never executed.
28pub struct BinaryBase64Codec;
29
30impl Decoder for BinaryBase64Codec {
31    fn decode(&self, cx: &mut ReadCx<'_>, input: Input) -> Result<Expr> {
32        decode_tree(cx, input).map(|tree| tree.located().expr)
33    }
34}
35
36impl Encoder for BinaryBase64Codec {
37    fn encode(&self, _cx: &mut WriteCx<'_>, expr: &Expr) -> Result<Output> {
38        let frame = sim_codec_binary::encode_frame(expr)?;
39        Ok(Output::Text(encode_base64(&frame.0)))
40    }
41}
42
43impl LocatedDecoder for BinaryBase64Codec {
44    fn decode_located(
45        &self,
46        cx: &mut ReadCx<'_>,
47        input: Input,
48        _source_id: String,
49    ) -> Result<LocatedExpr> {
50        decode_tree(cx, input).map(|tree| tree.located())
51    }
52}
53
54impl LocatedEncoder for BinaryBase64Codec {
55    fn encode_located(&self, cx: &mut WriteCx<'_>, expr: &LocatedExpr) -> Result<Output> {
56        let frame = sim_codec_binary::encode_located_frame(expr, cx.options.lossless_origin)?;
57        Ok(Output::Text(encode_base64(&frame.0)))
58    }
59}
60
61impl TreeDecoder for BinaryBase64Codec {
62    fn decode_tree(
63        &self,
64        cx: &mut ReadCx<'_>,
65        input: Input,
66        _source_id: String,
67    ) -> Result<LocatedExprTree> {
68        decode_tree(cx, input)
69    }
70}
71
72impl TreeEncoder for BinaryBase64Codec {
73    fn encode_tree(&self, cx: &mut WriteCx<'_>, expr: &LocatedExprTree) -> Result<Output> {
74        validate_expr_tree(cx.codec, expr)?;
75        let frame = sim_codec_binary::encode_located_tree_frame(expr, cx.options.lossless_origin)?;
76        Ok(Output::Text(encode_base64(&frame.0)))
77    }
78}
79
80fn decode_tree(cx: &mut ReadCx<'_>, input: Input) -> Result<LocatedExprTree> {
81    let text = input_text(cx.codec, input)?;
82    let bytes = decode_base64_with_limits(cx.codec, &text, cx.limits)?;
83    sim_codec_binary::decode_located_tree_frame_with_limits(
84        cx.codec,
85        &bytes,
86        sim_codec_binary::DecodeLimits::from(cx.limits),
87    )
88    .map(|(_, tree)| tree)
89}
90
91fn input_text(codec: CodecId, input: Input) -> Result<String> {
92    match input {
93        Input::Text(text) => Ok(text),
94        Input::Bytes(bytes) => String::from_utf8(bytes).map_err(|err| Error::CodecError {
95            codec,
96            message: format!("binary-base64 input is not valid UTF-8: {err}"),
97        }),
98    }
99}
100
101/// [`Lib`] that registers the binary-base64 codec with the runtime.
102///
103/// Its manifest exports the `codec/binary-base64` codec, and loading wires a
104/// [`BinaryBase64Codec`] into the linker as the decode and encode surface for
105/// all codec roles.
106pub struct BinaryBase64CodecLib {
107    symbol: Symbol,
108    codec_id: CodecId,
109}
110
111impl BinaryBase64CodecLib {
112    /// Creates the codec lib bound to the runtime-assigned `id` for
113    /// `codec/binary-base64`.
114    pub fn new(id: CodecId) -> Self {
115        Self {
116            symbol: Symbol::qualified("codec", "binary-base64"),
117            codec_id: id,
118        }
119    }
120}
121
122impl Lib for BinaryBase64CodecLib {
123    fn manifest(&self) -> LibManifest {
124        LibManifest {
125            id: self.symbol.clone(),
126            version: Version(env!("CARGO_PKG_VERSION").to_owned()),
127            abi: AbiVersion { major: 0, minor: 1 },
128            target: LibTarget::HostRegistered,
129            requires: Vec::<Dependency>::new(),
130            capabilities: Vec::new(),
131            exports: vec![
132                Export::Codec {
133                    symbol: self.symbol.clone(),
134                    codec_id: Some(self.codec_id),
135                },
136                Export::Function {
137                    symbol: roundtrip_report_symbol(),
138                    function_id: None,
139                },
140            ],
141        }
142    }
143
144    fn load(&self, cx: &mut sim_kernel::LoadCx, linker: &mut Linker) -> Result<()> {
145        let _factory = DefaultFactory;
146        let expr_shape =
147            sim_codec::resolve_expr_shape(linker, &Symbol::qualified("codec", "BinaryBase64Text"))?;
148        let options_shape = sim_codec::resolve_options_shape(linker)?;
149
150        linker.codec_value(
151            self.symbol.clone(),
152            codec_value(CodecRuntime {
153                id: self.codec_id,
154                symbol: self.symbol.clone(),
155                decoder: Some(Arc::new(BinaryBase64Codec)),
156                located_decoder: Some(Arc::new(BinaryBase64Codec)),
157                tree_decoder: Some(Arc::new(BinaryBase64Codec)),
158                encoder: Some(Arc::new(BinaryBase64Codec)),
159                located_encoder: Some(Arc::new(BinaryBase64Codec)),
160                tree_encoder: Some(Arc::new(BinaryBase64Codec)),
161                expr_shape,
162                options_shape,
163                default_decode: CodecDefaultDecode::Datum,
164            }),
165        )?;
166        linker.function_value(
167            roundtrip_report_symbol(),
168            cx.factory().opaque(Arc::new(BinaryBase64RoundtripReport))?,
169        )?;
170        Ok(())
171    }
172}