Skip to main content

host_api/
codec.rs

1//! Minimal SCALE codec for the Polkadot app host-api wire format.
2//!
3//! Implements only the primitives used by the protocol:
4//! compact integers, strings, bytes, enums (u8 tag), options, results, vectors.
5
6// ---------------------------------------------------------------------------
7// Decode
8// ---------------------------------------------------------------------------
9
10pub struct Reader<'a> {
11    pub data: &'a [u8],
12    pub pos: usize,
13}
14
15impl<'a> Reader<'a> {
16    pub fn new(data: &'a [u8]) -> Self {
17        Self { data, pos: 0 }
18    }
19
20    pub fn remaining(&self) -> &'a [u8] {
21        &self.data[self.pos..]
22    }
23
24    fn need(&self, n: usize) -> Result<(), DecodeErr> {
25        if self.pos + n > self.data.len() {
26            Err(DecodeErr::Eof)
27        } else {
28            Ok(())
29        }
30    }
31
32    pub fn read_u8(&mut self) -> Result<u8, DecodeErr> {
33        self.need(1)?;
34        let v = self.data[self.pos];
35        self.pos += 1;
36        Ok(v)
37    }
38
39    pub fn read_u32_le(&mut self) -> Result<u32, DecodeErr> {
40        self.need(4)?;
41        let v = u32::from_le_bytes(self.data[self.pos..self.pos + 4].try_into().unwrap());
42        self.pos += 4;
43        Ok(v)
44    }
45
46    pub fn read_raw(&mut self, n: usize) -> Result<&'a [u8], DecodeErr> {
47        self.need(n)?;
48        let v = &self.data[self.pos..self.pos + n];
49        self.pos += n;
50        Ok(v)
51    }
52
53    /// SCALE compact integer (unsigned, up to u32 range).
54    pub fn read_compact_u32(&mut self) -> Result<u32, DecodeErr> {
55        let first = self.read_u8()? as u32;
56        match first & 0b11 {
57            0b00 => Ok(first >> 2),
58            0b01 => {
59                let second = self.read_u8()? as u32;
60                Ok(((first | (second << 8)) >> 2) & 0x3FFF)
61            }
62            0b10 => {
63                self.need(3)?;
64                let b1 = self.data[self.pos] as u32;
65                let b2 = self.data[self.pos + 1] as u32;
66                let b3 = self.data[self.pos + 2] as u32;
67                self.pos += 3;
68                let val = first | (b1 << 8) | (b2 << 16) | (b3 << 24);
69                Ok(val >> 2)
70            }
71            0b11 => {
72                let byte_count = (first >> 2) + 4;
73                if byte_count > 4 {
74                    return Err(DecodeErr::CompactTooLarge);
75                }
76                let mut val = 0u32;
77                for i in 0..byte_count as usize {
78                    val |= (self.read_u8()? as u32) << (i * 8);
79                }
80                Ok(val)
81            }
82            _ => unreachable!(),
83        }
84    }
85
86    /// SCALE string: compact length + UTF-8 bytes.
87    pub fn read_string(&mut self) -> Result<String, DecodeErr> {
88        let len = self.read_compact_u32()? as usize;
89        let bytes = self.read_raw(len)?;
90        String::from_utf8(bytes.to_vec()).map_err(|_| DecodeErr::InvalidUtf8)
91    }
92
93    /// Dynamic-length bytes: compact length + raw bytes.
94    pub fn read_var_bytes(&mut self) -> Result<Vec<u8>, DecodeErr> {
95        let len = self.read_compact_u32()? as usize;
96        Ok(self.read_raw(len)?.to_vec())
97    }
98
99    /// Fixed-length bytes.
100    pub fn read_fixed_bytes(&mut self, n: usize) -> Result<Vec<u8>, DecodeErr> {
101        Ok(self.read_raw(n)?.to_vec())
102    }
103
104    /// SCALE Option: 0x00 = None, 0x01 = Some(T).
105    pub fn read_option<T>(
106        &mut self,
107        f: impl FnOnce(&mut Self) -> Result<T, DecodeErr>,
108    ) -> Result<Option<T>, DecodeErr> {
109        match self.read_u8()? {
110            0 => Ok(None),
111            1 => f(self).map(Some),
112            _ => Err(DecodeErr::InvalidOption),
113        }
114    }
115
116    /// Skip all remaining bytes (for void / don't-care payloads).
117    pub fn skip_rest(&mut self) {
118        self.pos = self.data.len();
119    }
120}
121
122#[derive(Debug)]
123pub enum DecodeErr {
124    Eof,
125    CompactTooLarge,
126    InvalidUtf8,
127    InvalidOption,
128    InvalidTag(u8),
129    BadMessage(&'static str),
130    UnknownProtocol,
131}
132
133impl std::fmt::Display for DecodeErr {
134    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
135        match self {
136            Self::Eof => write!(f, "unexpected end of input"),
137            Self::CompactTooLarge => write!(f, "compact integer exceeds u32"),
138            Self::InvalidUtf8 => write!(f, "invalid UTF-8 in string"),
139            Self::InvalidOption => write!(f, "invalid option discriminant"),
140            Self::InvalidTag(t) => write!(f, "invalid tag: {t}"),
141            Self::BadMessage(msg) => write!(f, "{msg}"),
142            Self::UnknownProtocol => {
143                write!(f, "message does not start with protocol discriminator 0x01")
144            }
145        }
146    }
147}
148
149impl std::error::Error for DecodeErr {}
150
151// ---------------------------------------------------------------------------
152// Encode
153// ---------------------------------------------------------------------------
154
155/// SCALE compact integer encode (u32 range).
156pub fn encode_compact_u32(buf: &mut Vec<u8>, val: u32) {
157    if val < 0x40 {
158        buf.push((val as u8) << 2);
159    } else if val < 0x4000 {
160        let v = (val << 2) | 0b01;
161        buf.push(v as u8);
162        buf.push((v >> 8) as u8);
163    } else if val < 0x4000_0000 {
164        let v = (val << 2) | 0b10;
165        buf.push(v as u8);
166        buf.push((v >> 8) as u8);
167        buf.push((v >> 16) as u8);
168        buf.push((v >> 24) as u8);
169    } else {
170        buf.push(0b11); // mode 3, 0 extra bytes indicator = 4 bytes total
171        buf.push(val as u8);
172        buf.push((val >> 8) as u8);
173        buf.push((val >> 16) as u8);
174        buf.push((val >> 24) as u8);
175    }
176}
177
178/// SCALE string: compact length + UTF-8 bytes.
179pub fn encode_string(buf: &mut Vec<u8>, s: &str) {
180    encode_compact_u32(buf, s.len() as u32);
181    buf.extend_from_slice(s.as_bytes());
182}
183
184/// Enum tag (u8).
185pub fn encode_tag(buf: &mut Vec<u8>, tag: u8) {
186    buf.push(tag);
187}
188
189/// Result::Ok(void) = [0x00].
190pub fn encode_result_ok_void(buf: &mut Vec<u8>) {
191    buf.push(0x00);
192}
193
194/// Result::Ok with inner value.
195pub fn encode_result_ok(buf: &mut Vec<u8>) {
196    buf.push(0x00);
197}
198
199/// Result::Err with inner error.
200pub fn encode_result_err(buf: &mut Vec<u8>) {
201    buf.push(0x01);
202}
203
204/// Dynamic-length bytes: compact length + raw bytes.
205pub fn encode_var_bytes(buf: &mut Vec<u8>, data: &[u8]) {
206    encode_compact_u32(buf, data.len() as u32);
207    buf.extend_from_slice(data);
208}
209
210/// Option::None = [0x00].
211pub fn encode_option_none(buf: &mut Vec<u8>) {
212    buf.push(0x00);
213}
214
215/// Option::Some prefix = [0x01], then caller writes the value.
216pub fn encode_option_some(buf: &mut Vec<u8>) {
217    buf.push(0x01);
218}
219
220/// Vector: compact count + items (caller encodes each item).
221pub fn encode_vector_len(buf: &mut Vec<u8>, count: u32) {
222    encode_compact_u32(buf, count);
223}
224
225#[cfg(test)]
226mod tests {
227    use super::*;
228
229    #[test]
230    fn compact_round_trip() {
231        for val in [0u32, 1, 63, 64, 16383, 16384, 1_073_741_823, u32::MAX] {
232            let mut buf = Vec::new();
233            encode_compact_u32(&mut buf, val);
234            let mut r = Reader::new(&buf);
235            let decoded = r.read_compact_u32().unwrap();
236            assert_eq!(val, decoded, "compact round-trip failed for {val}");
237            assert_eq!(r.pos, buf.len());
238        }
239    }
240
241    #[test]
242    fn string_round_trip() {
243        for s in [
244            "",
245            "hello",
246            "dot://mytestapp.dot",
247            "a".repeat(1000).as_str(),
248        ] {
249            let mut buf = Vec::new();
250            encode_string(&mut buf, s);
251            let mut r = Reader::new(&buf);
252            let decoded = r.read_string().unwrap();
253            assert_eq!(s, decoded);
254        }
255    }
256
257    #[test]
258    fn var_bytes_round_trip() {
259        let data = vec![0xde, 0xad, 0xbe, 0xef];
260        let mut buf = Vec::new();
261        encode_var_bytes(&mut buf, &data);
262        let mut r = Reader::new(&buf);
263        let decoded = r.read_var_bytes().unwrap();
264        assert_eq!(data, decoded);
265    }
266
267    #[test]
268    fn var_bytes_empty() {
269        let mut buf = Vec::new();
270        encode_var_bytes(&mut buf, &[]);
271        assert_eq!(buf, vec![0x00]); // compact(0)
272        let mut r = Reader::new(&buf);
273        let decoded = r.read_var_bytes().unwrap();
274        assert!(decoded.is_empty());
275    }
276
277    #[test]
278    fn reader_eof_on_empty() {
279        let mut r = Reader::new(&[]);
280        assert!(r.read_u8().is_err());
281        assert!(r.read_string().is_err());
282        assert!(r.read_compact_u32().is_err());
283    }
284
285    #[test]
286    fn reader_truncated_string() {
287        // Compact length says 10 bytes but only 3 available
288        let mut buf = Vec::new();
289        encode_compact_u32(&mut buf, 10);
290        buf.extend_from_slice(b"abc");
291        let mut r = Reader::new(&buf);
292        assert!(r.read_string().is_err());
293    }
294}