Skip to main content

oxide_sdk/
proto.rs

1//! Lightweight protobuf wire-format encoder/decoder.
2//!
3//! Produces bytes fully compatible with the Protocol Buffers binary wire
4//! format (no .proto file or code-generation required).  This makes
5//! protobuf the *native* serialisation layer for Oxide guest applications.
6//!
7//! ## Encoding
8//!
9//! ```rust,ignore
10//! use oxide_sdk::proto::ProtoEncoder;
11//!
12//! let data = ProtoEncoder::new()
13//!     .string(1, "alice")
14//!     .uint64(2, 42)
15//!     .bool(3, true)
16//!     .bytes(4, &[0xCA, 0xFE])
17//!     .finish();
18//! ```
19//!
20//! ## Decoding
21//!
22//! ```rust,ignore
23//! use oxide_sdk::proto::ProtoDecoder;
24//!
25//! let mut decoder = ProtoDecoder::new(&data);
26//! while let Some(field) = decoder.next() {
27//!     match field.number {
28//!         1 => log(&format!("name = {}", field.as_str())),
29//!         2 => log(&format!("age  = {}", field.as_u64())),
30//!         _ => {}
31//!     }
32//! }
33//! ```
34
35// ── Wire types ───────────────────────────────────────────────────────────────
36
37const WIRE_VARINT: u32 = 0;
38const WIRE_64BIT: u32 = 1;
39const WIRE_LEN: u32 = 2;
40const WIRE_32BIT: u32 = 5;
41
42// ── Varint helpers ───────────────────────────────────────────────────────────
43
44fn encode_varint(buf: &mut Vec<u8>, mut v: u64) {
45    loop {
46        if v < 0x80 {
47            buf.push(v as u8);
48            return;
49        }
50        buf.push((v as u8 & 0x7F) | 0x80);
51        v >>= 7;
52    }
53}
54
55fn decode_varint(buf: &[u8], pos: &mut usize) -> Option<u64> {
56    let mut result: u64 = 0;
57    let mut shift = 0u32;
58    loop {
59        if *pos >= buf.len() {
60            return None;
61        }
62        let byte = buf[*pos];
63        *pos += 1;
64        result |= ((byte & 0x7F) as u64) << shift;
65        if byte < 0x80 {
66            return Some(result);
67        }
68        shift += 7;
69        if shift >= 64 {
70            return None;
71        }
72    }
73}
74
75fn zigzag_encode(v: i64) -> u64 {
76    ((v << 1) ^ (v >> 63)) as u64
77}
78
79fn zigzag_decode(v: u64) -> i64 {
80    ((v >> 1) as i64) ^ (-((v & 1) as i64))
81}
82
83// ── Encoder ──────────────────────────────────────────────────────────────────
84
85/// Builds a protobuf-compatible binary message field by field.
86pub struct ProtoEncoder {
87    buf: Vec<u8>,
88}
89
90impl ProtoEncoder {
91    pub fn new() -> Self {
92        Self { buf: Vec::new() }
93    }
94
95    pub fn with_capacity(cap: usize) -> Self {
96        Self {
97            buf: Vec::with_capacity(cap),
98        }
99    }
100
101    fn tag(self, field: u32, wire: u32) -> Self {
102        let mut s = self;
103        encode_varint(&mut s.buf, ((field as u64) << 3) | (wire as u64));
104        s
105    }
106
107    // ── Varint types ────────────────────────────────────────────────
108
109    pub fn uint64(self, field: u32, value: u64) -> Self {
110        let mut s = self.tag(field, WIRE_VARINT);
111        encode_varint(&mut s.buf, value);
112        s
113    }
114
115    pub fn uint32(self, field: u32, value: u32) -> Self {
116        self.uint64(field, value as u64)
117    }
118
119    pub fn int64(self, field: u32, value: i64) -> Self {
120        self.uint64(field, value as u64)
121    }
122
123    pub fn int32(self, field: u32, value: i32) -> Self {
124        self.uint64(field, value as u64)
125    }
126
127    pub fn sint64(self, field: u32, value: i64) -> Self {
128        self.uint64(field, zigzag_encode(value))
129    }
130
131    pub fn sint32(self, field: u32, value: i32) -> Self {
132        self.sint64(field, value as i64)
133    }
134
135    pub fn bool(self, field: u32, value: bool) -> Self {
136        self.uint64(field, value as u64)
137    }
138
139    // ── Length-delimited types ───────────────────────────────────────
140
141    pub fn bytes(self, field: u32, value: &[u8]) -> Self {
142        let mut s = self.tag(field, WIRE_LEN);
143        encode_varint(&mut s.buf, value.len() as u64);
144        s.buf.extend_from_slice(value);
145        s
146    }
147
148    pub fn string(self, field: u32, value: &str) -> Self {
149        self.bytes(field, value.as_bytes())
150    }
151
152    /// Embed a sub-message (another `ProtoEncoder`'s output).
153    pub fn message(self, field: u32, msg: &ProtoEncoder) -> Self {
154        self.bytes(field, &msg.buf)
155    }
156
157    // ── Fixed-width types ───────────────────────────────────────────
158
159    pub fn fixed64(self, field: u32, value: u64) -> Self {
160        let mut s = self.tag(field, WIRE_64BIT);
161        s.buf.extend_from_slice(&value.to_le_bytes());
162        s
163    }
164
165    pub fn sfixed64(self, field: u32, value: i64) -> Self {
166        self.fixed64(field, value as u64)
167    }
168
169    pub fn double(self, field: u32, value: f64) -> Self {
170        self.fixed64(field, value.to_bits())
171    }
172
173    pub fn fixed32(self, field: u32, value: u32) -> Self {
174        let mut s = self.tag(field, WIRE_32BIT);
175        s.buf.extend_from_slice(&value.to_le_bytes());
176        s
177    }
178
179    pub fn sfixed32(self, field: u32, value: i32) -> Self {
180        self.fixed32(field, value as u32)
181    }
182
183    pub fn float(self, field: u32, value: f32) -> Self {
184        self.fixed32(field, value.to_bits())
185    }
186
187    // ── Finalise ────────────────────────────────────────────────────
188
189    pub fn as_bytes(&self) -> &[u8] {
190        &self.buf
191    }
192
193    pub fn finish(self) -> Vec<u8> {
194        self.buf
195    }
196
197    pub fn len(&self) -> usize {
198        self.buf.len()
199    }
200
201    pub fn is_empty(&self) -> bool {
202        self.buf.is_empty()
203    }
204}
205
206impl Default for ProtoEncoder {
207    fn default() -> Self {
208        Self::new()
209    }
210}
211
212// ── Decoder ──────────────────────────────────────────────────────────────────
213
214/// Iterates over protobuf-encoded fields one at a time.
215pub struct ProtoDecoder<'a> {
216    buf: &'a [u8],
217    pos: usize,
218}
219
220/// A single decoded protobuf field.
221pub struct ProtoField<'a> {
222    pub number: u32,
223    pub wire_type: u32,
224    data: FieldData<'a>,
225}
226
227enum FieldData<'a> {
228    Varint(u64),
229    Fixed64([u8; 8]),
230    Bytes(&'a [u8]),
231    Fixed32([u8; 4]),
232}
233
234impl<'a> ProtoField<'a> {
235    pub fn as_u64(&self) -> u64 {
236        match &self.data {
237            FieldData::Varint(v) => *v,
238            FieldData::Fixed64(b) => u64::from_le_bytes(*b),
239            FieldData::Fixed32(b) => u32::from_le_bytes(*b) as u64,
240            FieldData::Bytes(b) => {
241                let mut arr = [0u8; 8];
242                let n = b.len().min(8);
243                arr[..n].copy_from_slice(&b[..n]);
244                u64::from_le_bytes(arr)
245            }
246        }
247    }
248
249    pub fn as_i64(&self) -> i64 {
250        self.as_u64() as i64
251    }
252
253    pub fn as_u32(&self) -> u32 {
254        self.as_u64() as u32
255    }
256
257    pub fn as_i32(&self) -> i32 {
258        self.as_u64() as i32
259    }
260
261    pub fn as_sint64(&self) -> i64 {
262        zigzag_decode(self.as_u64())
263    }
264
265    pub fn as_sint32(&self) -> i32 {
266        self.as_sint64() as i32
267    }
268
269    pub fn as_bool(&self) -> bool {
270        self.as_u64() != 0
271    }
272
273    pub fn as_f64(&self) -> f64 {
274        match &self.data {
275            FieldData::Fixed64(b) => f64::from_bits(u64::from_le_bytes(*b)),
276            _ => self.as_u64() as f64,
277        }
278    }
279
280    pub fn as_f32(&self) -> f32 {
281        match &self.data {
282            FieldData::Fixed32(b) => f32::from_bits(u32::from_le_bytes(*b)),
283            _ => self.as_u64() as f32,
284        }
285    }
286
287    pub fn as_bytes(&self) -> &'a [u8] {
288        match &self.data {
289            FieldData::Bytes(b) => b,
290            _ => &[],
291        }
292    }
293
294    pub fn as_str(&self) -> &'a str {
295        core::str::from_utf8(self.as_bytes()).unwrap_or("")
296    }
297
298    /// Decode this field's bytes as a nested message.
299    pub fn as_message(&self) -> ProtoDecoder<'a> {
300        ProtoDecoder::new(self.as_bytes())
301    }
302}
303
304impl<'a> ProtoDecoder<'a> {
305    pub fn new(buf: &'a [u8]) -> Self {
306        Self { buf, pos: 0 }
307    }
308
309    pub fn next(&mut self) -> Option<ProtoField<'a>> {
310        if self.pos >= self.buf.len() {
311            return None;
312        }
313        let tag = decode_varint(self.buf, &mut self.pos)?;
314        let wire_type = (tag & 0x07) as u32;
315        let number = (tag >> 3) as u32;
316
317        let data = match wire_type {
318            WIRE_VARINT => {
319                let v = decode_varint(self.buf, &mut self.pos)?;
320                FieldData::Varint(v)
321            }
322            WIRE_64BIT => {
323                if self.pos + 8 > self.buf.len() {
324                    return None;
325                }
326                let mut arr = [0u8; 8];
327                arr.copy_from_slice(&self.buf[self.pos..self.pos + 8]);
328                self.pos += 8;
329                FieldData::Fixed64(arr)
330            }
331            WIRE_LEN => {
332                let len = decode_varint(self.buf, &mut self.pos)? as usize;
333                if self.pos + len > self.buf.len() {
334                    return None;
335                }
336                let slice = &self.buf[self.pos..self.pos + len];
337                self.pos += len;
338                FieldData::Bytes(slice)
339            }
340            WIRE_32BIT => {
341                if self.pos + 4 > self.buf.len() {
342                    return None;
343                }
344                let mut arr = [0u8; 4];
345                arr.copy_from_slice(&self.buf[self.pos..self.pos + 4]);
346                self.pos += 4;
347                FieldData::Fixed32(arr)
348            }
349            _ => return None, // unknown wire type — stop
350        };
351
352        Some(ProtoField {
353            number,
354            wire_type,
355            data,
356        })
357    }
358
359    /// Collect all fields into a `Vec` for random-access lookup.
360    pub fn collect_fields(&mut self) -> Vec<ProtoField<'a>> {
361        let mut fields = Vec::new();
362        while let Some(f) = self.next() {
363            fields.push(f);
364        }
365        fields
366    }
367}