Skip to main content

candid/
binary_parser.rs

1use crate::types::internal::{Field, Function, Label, Type, TypeInner};
2use crate::types::{FuncMode, TypeEnv};
3use anyhow::{anyhow, Context, Result};
4use binread::io::{Read, Seek};
5use binread::{BinRead, BinResult, Error as BError, ReadOptions};
6use std::convert::TryInto;
7
8const MAX_TYPE_TABLE_LEN: u64 = 10_000; // Max type entries
9
10fn read_leb<R: Read + Seek>(reader: &mut R, ro: &ReadOptions, (): ()) -> BinResult<u64> {
11    let pos = reader.stream_position()?;
12    leb128::read::unsigned(reader).map_err(|_| BError::Custom {
13        pos,
14        err: Box::new(ro.variable_name.unwrap_or("Invalid leb128")),
15    })
16}
17fn read_sleb<R: Read + Seek>(reader: &mut R, ro: &ReadOptions, (): ()) -> BinResult<i64> {
18    let pos = reader.stream_position()?;
19    leb128::read::signed(reader).map_err(|_| BError::Custom {
20        pos,
21        err: Box::new(ro.variable_name.unwrap_or("Invalid sleb128")),
22    })
23}
24
25#[derive(BinRead, Debug)]
26#[br(magic = b"DIDL")]
27#[br(import(max_type_len: Option<usize>))]
28pub struct Header {
29    #[br(args(max_type_len))]
30    table: Table,
31    #[br(parse_with = read_leb)]
32    len: u64,
33    #[br(count = len)]
34    args: Vec<IndexType>,
35}
36
37#[derive(BinRead, Debug)]
38#[br(import(max_type_len: Option<usize>))]
39struct Table {
40    #[br(parse_with = read_leb)]
41    #[br(assert(len <= max_type_len.unwrap_or(MAX_TYPE_TABLE_LEN as usize) as u64, "type table size exceeded"))]
42    len: u64,
43    #[br(count = len)]
44    table: Vec<ConsType>,
45}
46#[derive(BinRead, Debug)]
47enum ConsType {
48    #[br(magic = 0x6eu8)]
49    Opt(Box<IndexType>),
50    #[br(magic = 0x6du8)]
51    Vec(Box<IndexType>),
52    #[br(magic = 0x6cu8)]
53    Record(Fields),
54    #[br(magic = 0x6bu8)]
55    Variant(Fields),
56    #[br(magic = 0x6au8)]
57    Func(FuncType),
58    #[br(magic = 0x69u8)]
59    Service(ServType),
60    Future(FutureType),
61}
62#[derive(BinRead, Debug)]
63struct IndexType {
64    #[br(parse_with = read_sleb, assert(index >= -17 || index == -24, "unknown opcode {}", index))]
65    index: i64,
66}
67#[derive(BinRead, Debug)]
68struct Fields {
69    #[br(parse_with = read_leb, try_map = |x:u64| x.try_into().map_err(|_| "field length out of 32-bit range"))]
70    len: u32,
71    #[br(count = len)]
72    inner: Vec<FieldType>,
73}
74#[derive(BinRead, Debug)]
75struct FieldType {
76    #[br(parse_with = read_leb, try_map = |x:u64| x.try_into().map_err(|_| "field id out of 32-bit range"))]
77    id: u32,
78    index: IndexType,
79}
80#[derive(BinRead, Debug)]
81struct FuncType {
82    #[br(parse_with = read_leb)]
83    arg_len: u64,
84    #[br(count = arg_len)]
85    args: Vec<IndexType>,
86    #[br(parse_with = read_leb)]
87    ret_len: u64,
88    #[br(count = ret_len)]
89    rets: Vec<IndexType>,
90    #[br(assert(ann_len <= 1u8, "function annotation length should be at most 1"))]
91    ann_len: u8,
92    #[br(count = ann_len)]
93    ann: Vec<Mode>,
94}
95#[derive(BinRead, Debug)]
96struct ServType {
97    #[br(parse_with = read_leb)]
98    len: u64,
99    #[br(count = len)]
100    meths: Vec<Meths>,
101}
102#[derive(BinRead, Debug)]
103struct FutureType {
104    #[br(parse_with = read_sleb, assert(opcode < -24, "{} is not a valid future type", opcode))]
105    opcode: i64,
106    #[br(parse_with = read_leb)]
107    len: u64,
108    #[br(count = len)]
109    blob: Vec<u8>,
110}
111#[derive(BinRead, Debug)]
112struct Meths {
113    #[br(parse_with = read_leb)]
114    len: u64,
115    #[br(count = len, try_map = |x:Vec<u8>| String::from_utf8(x).map_err(|_| "invalid utf8"))]
116    name: String,
117    ty: IndexType,
118}
119#[derive(BinRead, Debug)]
120struct Mode {
121    #[br(try_map = |x:u8| match x { 1u8 => Ok(FuncMode::Query), | 2u8 => Ok(FuncMode::Oneway), | 3u8 => Ok(FuncMode::CompositeQuery), | _ => Err("Unknown annotation") })]
122    inner: FuncMode,
123}
124
125#[derive(BinRead)]
126pub struct BoolValue(
127    #[br(try_map = |x:u8| match x { 0u8 => Ok(false), | 1u8 => Ok(true), | _ => Err("Expect 00 or 01") } )]
128    pub bool,
129);
130#[derive(BinRead)]
131pub struct Len(
132    #[br(parse_with = read_leb, try_map = |x:u64| x.try_into().map_err(|_| "length out of usize range"))]
133    pub usize,
134);
135#[derive(BinRead)]
136pub struct PrincipalBytes {
137    #[br(assert(flag == 1u8, "Opaque reference not supported"))]
138    pub flag: u8,
139    #[br(parse_with = read_leb, assert(len <= 29, "Principal is longer than 29 bytes"))]
140    pub len: u64,
141    #[br(count = len)]
142    pub inner: Vec<u8>,
143}
144
145fn index_to_var(ind: i64) -> String {
146    format!("table{ind}")
147}
148impl IndexType {
149    fn to_type(&self, len: u64) -> Result<Type> {
150        Ok(match self.index {
151            v if v >= 0 => {
152                if v >= len as i64 {
153                    return Err(anyhow!("type index {} out of range", v));
154                }
155                TypeInner::Var(index_to_var(v))
156            }
157            -1 => TypeInner::Null,
158            -2 => TypeInner::Bool,
159            -3 => TypeInner::Nat,
160            -4 => TypeInner::Int,
161            -5 => TypeInner::Nat8,
162            -6 => TypeInner::Nat16,
163            -7 => TypeInner::Nat32,
164            -8 => TypeInner::Nat64,
165            -9 => TypeInner::Int8,
166            -10 => TypeInner::Int16,
167            -11 => TypeInner::Int32,
168            -12 => TypeInner::Int64,
169            -13 => TypeInner::Float32,
170            -14 => TypeInner::Float64,
171            -15 => TypeInner::Text,
172            -16 => TypeInner::Reserved,
173            -17 => TypeInner::Empty,
174            -24 => TypeInner::Principal,
175            _ => unreachable!(),
176        }
177        .into())
178    }
179}
180impl ConsType {
181    fn to_type(&self, len: u64) -> Result<Type> {
182        Ok(match &self {
183            ConsType::Opt(ref ind) => TypeInner::Opt(ind.to_type(len)?),
184            ConsType::Vec(ref ind) => TypeInner::Vec(ind.to_type(len)?),
185            ConsType::Record(fs) | ConsType::Variant(fs) => {
186                let mut res = Vec::new();
187                let mut prev = None;
188                for f in &fs.inner {
189                    if let Some(prev) = prev {
190                        if prev >= f.id {
191                            return Err(anyhow!("field id {} collision or not sorted", f.id));
192                        }
193                    }
194                    prev = Some(f.id);
195                    let field = Field {
196                        id: Label::Id(f.id).into(),
197                        ty: f.index.to_type(len)?,
198                    };
199                    res.push(field);
200                }
201                if matches!(&self, ConsType::Record(_)) {
202                    TypeInner::Record(res)
203                } else {
204                    TypeInner::Variant(res)
205                }
206            }
207            ConsType::Func(f) => {
208                let mut args = Vec::new();
209                let mut rets = Vec::new();
210                for arg in &f.args {
211                    args.push(arg.to_type(len)?);
212                }
213                for ret in &f.rets {
214                    rets.push(ret.to_type(len)?);
215                }
216                TypeInner::Func(Function {
217                    modes: f.ann.iter().map(|x| x.inner.clone()).collect(),
218                    args,
219                    rets,
220                })
221            }
222            ConsType::Service(serv) => {
223                let mut res = Vec::new();
224                let mut prev = None;
225                for m in &serv.meths {
226                    if let Some(prev) = prev {
227                        if prev >= &m.name {
228                            return Err(anyhow!("method name {} duplicate or not sorted", m.name));
229                        }
230                    }
231                    prev = Some(&m.name);
232                    res.push((m.name.clone(), m.ty.to_type(len)?));
233                }
234                TypeInner::Service(res)
235            }
236            ConsType::Future(_) => TypeInner::Future,
237        }
238        .into())
239    }
240}
241impl Table {
242    fn to_env(&self, len: u64) -> Result<TypeEnv> {
243        use std::collections::BTreeMap;
244        let mut env = BTreeMap::new();
245        for (i, t) in self.table.iter().enumerate() {
246            let ty = t
247                .to_type(len)
248                .with_context(|| format!("Invalid table entry {i}: {t:?}"))?;
249            env.insert(index_to_var(i as i64), ty);
250        }
251        // validate method has func type
252        for t in env.values() {
253            if let TypeInner::Service(ms) = t.as_ref() {
254                for (name, ty) in ms {
255                    if let TypeInner::Var(id) = ty.as_ref() {
256                        if matches!(env.get(id).map(|t| t.as_ref()), Some(TypeInner::Func(_))) {
257                            continue;
258                        }
259                    }
260                    return Err(anyhow!("Method {name} has a non-function type {ty}"));
261                }
262            }
263        }
264        Ok(TypeEnv(env))
265    }
266}
267impl Header {
268    pub fn to_types(&self) -> Result<(TypeEnv, Vec<Type>)> {
269        let len = self.table.len;
270        let mut env = self.table.to_env(len)?;
271        env.replace_empty()?;
272        let mut args = Vec::new();
273        for (i, t) in self.args.iter().enumerate() {
274            args.push(
275                t.to_type(len)
276                    .with_context(|| format!("Invalid argument entry {i}: {t:?}"))?,
277            );
278        }
279        Ok((env, args))
280    }
281}