Skip to main content

bern_protocol/
codec.rs

1//! Compact, schema-directed binary layout for in-process bridges.
2//!
3//! Both sides know the schema, so the layout carries no field names or type
4//! tags: bool = u8, int = i64 LE, float = f64 LE, text = u32 LE length + UTF-8,
5//! enum = u32 variant + payload fields, optional = u8 flag + value,
6//! record = fields in order, list = u32 count + items. It is not used on
7//! control channels, which keep the JSON encoding.
8
9use crate::{Type, TypeTable, Value};
10use std::fmt;
11
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub struct CodecError(pub String);
14
15impl fmt::Display for CodecError {
16    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
17        f.write_str(&self.0)
18    }
19}
20
21impl std::error::Error for CodecError {}
22
23#[derive(Default)]
24pub struct Writer {
25    pub bytes: Vec<u8>,
26}
27
28impl Writer {
29    pub fn new() -> Self {
30        Self::default()
31    }
32    pub fn u8(&mut self, value: u8) {
33        self.bytes.push(value);
34    }
35    pub fn u32(&mut self, value: u32) {
36        self.bytes.extend_from_slice(&value.to_le_bytes());
37    }
38    pub fn u64(&mut self, value: u64) {
39        self.bytes.extend_from_slice(&value.to_le_bytes());
40    }
41    pub fn i64(&mut self, value: i64) {
42        self.bytes.extend_from_slice(&value.to_le_bytes());
43    }
44    pub fn f64(&mut self, value: f64) {
45        self.bytes.extend_from_slice(&value.to_le_bytes());
46    }
47    pub fn text(&mut self, value: &str) {
48        self.u32(value.len() as u32);
49        self.bytes.extend_from_slice(value.as_bytes());
50    }
51}
52
53pub struct Reader<'a> {
54    bytes: &'a [u8],
55    position: usize,
56}
57
58impl<'a> Reader<'a> {
59    pub fn new(bytes: &'a [u8]) -> Self {
60        Self { bytes, position: 0 }
61    }
62
63    fn take(&mut self, count: usize) -> Result<&'a [u8], CodecError> {
64        let end = self
65            .position
66            .checked_add(count)
67            .filter(|end| *end <= self.bytes.len())
68            .ok_or_else(|| CodecError("unexpected end of buffer".into()))?;
69        let slice = &self.bytes[self.position..end];
70        self.position = end;
71        Ok(slice)
72    }
73
74    pub fn u8(&mut self) -> Result<u8, CodecError> {
75        Ok(self.take(1)?[0])
76    }
77    pub fn u32(&mut self) -> Result<u32, CodecError> {
78        Ok(u32::from_le_bytes(self.take(4)?.try_into().unwrap()))
79    }
80    pub fn u64(&mut self) -> Result<u64, CodecError> {
81        Ok(u64::from_le_bytes(self.take(8)?.try_into().unwrap()))
82    }
83    pub fn i64(&mut self) -> Result<i64, CodecError> {
84        Ok(i64::from_le_bytes(self.take(8)?.try_into().unwrap()))
85    }
86    pub fn f64(&mut self) -> Result<f64, CodecError> {
87        Ok(f64::from_le_bytes(self.take(8)?.try_into().unwrap()))
88    }
89    pub fn text(&mut self) -> Result<String, CodecError> {
90        let length = self.u32()? as usize;
91        let bytes = self.take(length)?;
92        String::from_utf8(bytes.to_vec()).map_err(|_| CodecError("text is not UTF-8".into()))
93    }
94    pub fn is_finished(&self) -> bool {
95        self.position == self.bytes.len()
96    }
97}
98
99/// Encode a value that has already been checked against `ty`.
100pub fn encode(value: &Value, ty: &Type, types: &TypeTable, writer: &mut Writer) {
101    match (value, ty) {
102        (Value::Unit, _) => {}
103        (Value::Bool(value), _) => writer.u8(u8::from(*value)),
104        (Value::Int(value), _) => writer.i64(*value),
105        (Value::Float(value), _) => writer.f64(*value),
106        (Value::Text(value), _) => writer.text(value),
107        (Value::Enum { variant, fields }, Type::Enum { name }) => {
108            writer.u32(*variant);
109            let def = types.enum_def(name).expect("checked enum");
110            for (field, def) in fields.iter().zip(&def.variants[*variant as usize].fields) {
111                encode(field, &def.ty, types, writer);
112            }
113        }
114        (Value::Optional(value), Type::Optional { inner }) => match value {
115            None => writer.u8(0),
116            Some(value) => {
117                writer.u8(1);
118                encode(value, inner, types, writer);
119            }
120        },
121        (Value::Record(fields), Type::Record { name }) => {
122            let def = types.record_def(name).expect("checked record");
123            for (field, def) in fields.iter().zip(&def.fields) {
124                encode(field, &def.ty, types, writer);
125            }
126        }
127        (Value::List(items), Type::List { inner }) => {
128            writer.u32(items.len() as u32);
129            for item in items {
130                encode(item, inner, types, writer);
131            }
132        }
133        _ => unreachable!("encode called with an unchecked value"),
134    }
135}
136
137/// Decode and validate a value of type `ty`.
138pub fn decode(ty: &Type, types: &TypeTable, reader: &mut Reader<'_>) -> Result<Value, CodecError> {
139    Ok(match ty {
140        Type::Unit => Value::Unit,
141        Type::Bool => match reader.u8()? {
142            0 => Value::Bool(false),
143            1 => Value::Bool(true),
144            other => return Err(CodecError(format!("invalid bool byte {other}"))),
145        },
146        Type::Int => Value::Int(reader.i64()?),
147        Type::Float => {
148            let value = reader.f64()?;
149            if !value.is_finite() {
150                return Err(CodecError("float values must be finite".into()));
151            }
152            Value::Float(value)
153        }
154        Type::Text => Value::Text(reader.text()?),
155        Type::Enum { name } => {
156            let def = types
157                .enum_def(name)
158                .ok_or_else(|| CodecError(format!("unknown enum `{name}`")))?;
159            let variant = reader.u32()?;
160            let variant_def = def
161                .variants
162                .get(variant as usize)
163                .ok_or_else(|| CodecError(format!("`{name}` has no variant #{variant}")))?;
164            let fields = variant_def
165                .fields
166                .iter()
167                .map(|field| decode(&field.ty, types, reader))
168                .collect::<Result<_, _>>()?;
169            Value::Enum { variant, fields }
170        }
171        Type::Optional { inner } => match reader.u8()? {
172            0 => Value::Optional(None),
173            1 => Value::some(decode(inner, types, reader)?),
174            other => return Err(CodecError(format!("invalid optional flag {other}"))),
175        },
176        Type::Record { name } => {
177            let def = types
178                .record_def(name)
179                .ok_or_else(|| CodecError(format!("unknown record `{name}`")))?;
180            Value::Record(
181                def.fields
182                    .iter()
183                    .map(|field| decode(&field.ty, types, reader))
184                    .collect::<Result<_, _>>()?,
185            )
186        }
187        Type::List { inner } => {
188            let count = reader.u32()? as usize;
189            let mut items = Vec::with_capacity(count.min(4096));
190            for _ in 0..count {
191                items.push(decode(inner, types, reader)?);
192            }
193            Value::List(items)
194        }
195    })
196}
197
198#[cfg(test)]
199mod tests {
200    use super::*;
201    use crate::{FieldDef, RecordDef};
202
203    #[test]
204    fn round_trip_preserves_values() {
205        let types = TypeTable {
206            enums: vec![],
207            records: vec![RecordDef {
208                name: "Row".into(),
209                fields: vec![
210                    FieldDef {
211                        name: "title".into(),
212                        ty: Type::Text,
213                        doc: String::new(),
214                        content: false,
215                    },
216                    FieldDef {
217                        name: "score".into(),
218                        ty: Type::optional(Type::Float),
219                        doc: String::new(),
220                        content: false,
221                    },
222                ],
223            }],
224        };
225        let ty = Type::list(Type::Record { name: "Row".into() });
226        let value = Value::List(vec![
227            Value::Record(vec![
228                Value::Text("你好".into()),
229                Value::some(Value::Float(1.5)),
230            ]),
231            Value::Record(vec![Value::Text(String::new()), Value::none()]),
232        ]);
233        value.check(&ty, &types).unwrap();
234        let mut writer = Writer::new();
235        encode(&value, &ty, &types, &mut writer);
236        let mut reader = Reader::new(&writer.bytes);
237        assert_eq!(decode(&ty, &types, &mut reader).unwrap(), value);
238        assert!(reader.is_finished());
239        let mut short = Reader::new(&writer.bytes[..writer.bytes.len() - 1]);
240        assert!(decode(&ty, &types, &mut short).is_err());
241    }
242}