1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153
// Copyright 2020 ChainSafe Systems
// SPDX-License-Identifier: Apache-2.0
//!
//! This module implements the logic for traversing a layout in a way that corresponds to how the binary data is arranged out.
//! Since BinProtRule is a recursive data type the correct traversal is a depth first traversal of the tree defined by the data type
//! with a few important differences.
//!
//! The layout tree includes the concept of Sum(ocaml)/Enum(rust) types. These are nodes in the tree where only one branch should be taken
//! depending on which variant of the enum we are deserializing. When reading an enum from binary the first byte specifies which variant to deserialize.
//! It is the responsibility of the driving code to handle when Sum/Option rules are encountered and push the correct next rule to the stack.
//!
//! The other interesting case is deserializing variable length vector types. When deserializing a vector the length can be read from the binary and then
//! the element rule pushed back to the stack using the `push_n` method. It will then repeat the given node `n` times to allow it to be deserialized.
//!
//! The traversal of a layout should be done in parallel with reading a bin_prot encoded binary such that the type tree informs the deserializer how to read the
//! data and the data informs the traversal how it should handle enum types.
//! Combined this allows parsing of types defined by the layout into loosely typed representations.
//!
use crate::value::layout::{BinProtRule, RuleRef};
/// Implements a depth first search of the type tree
/// defined by a BinProtRule
pub struct BinProtRuleIterator {
pub(crate) stack: Vec<BinProtRule>, // regular stack to implement the DFS
current_module_path: Option<String>, // holds on to most recent path encountered in traverse
}
impl Iterator for BinProtRuleIterator {
type Item = BinProtRule;
fn next(&mut self) -> Option<Self::Item> {
let top = self.stack.pop();
let r = top.clone();
match top {
Some(rule) => {
match rule {
BinProtRule::List(_rule) => {
// the code driving the iterator should take the list rule, push it
// then call repeat the required number of times
}
BinProtRule::Record(mut rules) => {
self.stack
.extend(rules.drain(0..).map(|field| field.field_rule).rev());
}
BinProtRule::Tuple(mut rules) => {
self.stack.extend(rules.drain(0..).rev());
}
BinProtRule::Sum(_) | BinProtRule::Polyvar(_) => {
// don't add to the stack. Add to the branch field instead
// this must be resolved by calling `branch` before the iterator can continue
}
BinProtRule::Option(_r) => {
// Option is a special case of a Sum where the None variant contain nothing
}
BinProtRule::Reference(rule_ref) => match rule_ref {
RuleRef::Unresolved(_payload) => {
unimplemented!();
}
RuleRef::Resolved(payload) => {
self.stack.push(*payload.ref_rule);
self.current_module_path = Some(payload.source_module_path);
return self.next();
}
},
BinProtRule::String
| BinProtRule::Int
| BinProtRule::Int64
| BinProtRule::Nat0
| BinProtRule::Bool
| BinProtRule::Unit
| BinProtRule::Char
| BinProtRule::Int32
| BinProtRule::NativeInt
| BinProtRule::Float => {} // These are leaves so nothing required
BinProtRule::Custom(rules) => {
if let Some(path) = &self.current_module_path {
return Some(BinProtRule::CustomForPath(path.to_string(), rules));
}
}
_ => unimplemented!(),
};
r
}
None => None, // end of traversal
}
}
}
impl BinProtRuleIterator {
// Drop a custom rule onto the stack
pub fn push(&mut self, rules: Vec<BinProtRule>) {
self.stack.extend(rules);
}
// Drop a custom rule onto the stack n times
pub fn push_n(&mut self, rule: BinProtRule, n: usize) {
self.stack.extend(std::iter::repeat(rule).take(n));
}
}
impl IntoIterator for BinProtRule {
type Item = BinProtRule;
type IntoIter = BinProtRuleIterator;
fn into_iter(self) -> <Self as IntoIterator>::IntoIter {
BinProtRuleIterator {
stack: vec![self],
current_module_path: None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::value::layout::Layout;
const TEST_LAYOUT: &str = include_str!("./test_layouts/mina_state_hash_tuple.json");
#[test]
fn test_layout() {
let layout: Layout = serde_json::from_str(TEST_LAYOUT).unwrap();
let mut iter = layout.bin_prot_rule.into_iter();
while let Some(v) = iter.next() {
println!("{:?}\n", v);
}
}
const TEST_LAYOUT_SUM: &str = include_str!("./test_layouts/sum_type_layout.json");
#[test]
fn test_layout_sum() {
let layout: Layout = serde_json::from_str(TEST_LAYOUT_SUM).unwrap();
let mut iter = layout.bin_prot_rule.into_iter();
// Test by taking the 0th branch at each branch node. Test is considered as pass
// if no error
loop {
match iter.next() {
Some(v) => {
if let BinProtRule::Sum(summands) = v {
// if its a sum type take the first variant in each case
iter.push(summands[0].ctor_args.clone())
}
}
None => {
println!("END!!!!");
break;
}
}
}
}
}