Skip to main content

monero_epee/
parser.rs

1use core::marker::PhantomData;
2
3use crate::{EpeeError, Stack, io::*};
4
5/// The EPEE-defined type of the field being read.
6#[derive(Clone, Copy, PartialEq, Eq, Debug)]
7pub enum Type {
8  /// An `i64`.
9  Int64 = 1,
10  /// An `i32`.
11  Int32 = 2,
12  /// An `i16`.
13  Int16 = 3,
14  /// An `i8`.
15  Int8 = 4,
16  /// A `u64`.
17  Uint64 = 5,
18  /// A `u32`.
19  Uint32 = 6,
20  /// A `u16`.
21  Uint16 = 7,
22  /// A `u8`.
23  Uint8 = 8,
24  /// A `f64`.
25  Double = 9,
26  /// A length-prefixed collection of bytes.
27  String = 10,
28  /// A `bool`.
29  Bool = 11,
30  /// An object.
31  Object = 12,
32  /*
33    Unused and unsupported. See
34    https://github.com/monero-project/monero/pull/10138 for more info.
35  */
36  // Array = 13,
37}
38
39/// A bitflag for if the field is actually an array.
40#[derive(Clone, Copy, Debug)]
41#[repr(u8)]
42pub enum Array {
43  /// A unit type.
44  Unit = 0,
45  /// An array.
46  Array = 1 << 7,
47}
48
49/*
50  An internal marker used to distinguish if we're reading an EPEE-defined field OR if we're reading
51  an entry within an section (object). This lets us collapse the definition of a section to an
52  array of entries, simplifying decoding.
53*/
54#[derive(Clone, Copy, PartialEq, Eq)]
55pub(crate) enum TypeOrEntry {
56  // An epee-defined type
57  Type(Type),
58  // An entry (name, type, value)
59  Entry,
60}
61
62impl Type {
63  /// Read a type specification, including its length.
64  pub fn read<'encoding>(reader: &mut impl BytesLike<'encoding>) -> Result<(Self, u64), EpeeError> {
65    let kind = reader.read_byte()?;
66
67    // Check if the array bit is set
68    #[expect(clippy::as_conversions)]
69    let array = kind & (Array::Array as u8);
70    // Clear the array bit
71    #[expect(clippy::as_conversions)]
72    let kind = kind & (!(Array::Array as u8));
73
74    let kind = match kind {
75      1 => Type::Int64,
76      2 => Type::Int32,
77      3 => Type::Int16,
78      4 => Type::Int8,
79      5 => Type::Uint64,
80      6 => Type::Uint32,
81      7 => Type::Uint16,
82      8 => Type::Uint8,
83      9 => Type::Double,
84      10 => Type::String,
85      11 => Type::Bool,
86      12 => Type::Object,
87      _ => Err(EpeeError::UnrecognizedType)?,
88    };
89
90    // Flatten non-array values to an array of length one
91    /*
92      TODO: Will `epee` proper return an error if an array of length one is specified for a unit
93      type? This wouldn't break our definition of compatibility yet should be revisited.
94    */
95    let len = if array != 0 { read_varint(reader)? } else { 1 };
96
97    Ok((kind, len))
98  }
99}
100
101/// Read a entry's key.
102// https://github.com/monero-project/monero/blob/8d4c625713e3419573dfcc7119c8848f47cabbaa
103//   /contrib/epee/include/storages/portable_storage_from_bin.h#143-L152
104fn read_key<'encoding, B: BytesLike<'encoding>>(
105  reader: &mut B,
106) -> Result<String<'encoding, B>, EpeeError> {
107  let len = usize::from(reader.read_byte()?);
108  if len == 0 {
109    Err(EpeeError::EmptyKey)?;
110  }
111  let (len, bytes) = reader.read_bytes(len)?;
112  Ok(String { len, bytes, _encoding: PhantomData })
113}
114
115/// The result from a single step of the decoder.
116pub(crate) enum SingleStepResult<'encoding, B: BytesLike<'encoding>> {
117  Object { fields: usize },
118  Entry { key: String<'encoding, B>, kind: Type, len: usize },
119  Unit,
120}
121
122impl Stack {
123  /// Execute a single step of the decoding algorithm.
124  ///
125  /// Returns `Some((key, kind, len))` if an entry was read, or `None` otherwise. This also returns
126  /// `None` if the stack is empty.
127  pub(crate) fn single_step<'encoding, B: BytesLike<'encoding>>(
128    &mut self,
129    encoding: &mut B,
130  ) -> Result<Option<SingleStepResult<'encoding, B>>, EpeeError> {
131    let Some(kind) = self.pop() else {
132      return Ok(None);
133    };
134    match kind {
135      TypeOrEntry::Type(Type::Int64) => {
136        encoding.advance::<{ core::mem::size_of::<i64>() }>()?;
137      }
138      TypeOrEntry::Type(Type::Int32) => {
139        encoding.advance::<{ core::mem::size_of::<i32>() }>()?;
140      }
141      TypeOrEntry::Type(Type::Int16) => {
142        encoding.advance::<{ core::mem::size_of::<i16>() }>()?;
143      }
144      TypeOrEntry::Type(Type::Int8) => {
145        encoding.advance::<{ core::mem::size_of::<i8>() }>()?;
146      }
147      TypeOrEntry::Type(Type::Uint64) => {
148        encoding.advance::<{ core::mem::size_of::<u64>() }>()?;
149      }
150      TypeOrEntry::Type(Type::Uint32) => {
151        encoding.advance::<{ core::mem::size_of::<u32>() }>()?;
152      }
153      TypeOrEntry::Type(Type::Uint16) => {
154        encoding.advance::<{ core::mem::size_of::<u16>() }>()?;
155      }
156      TypeOrEntry::Type(Type::Uint8) => {
157        encoding.advance::<{ core::mem::size_of::<u8>() }>()?;
158      }
159      TypeOrEntry::Type(Type::Double) => {
160        encoding.advance::<{ core::mem::size_of::<f64>() }>()?;
161      }
162      TypeOrEntry::Type(Type::String) => {
163        read_str(encoding)?;
164      }
165      TypeOrEntry::Type(Type::Bool) => {
166        encoding.advance::<{ core::mem::size_of::<bool>() }>()?;
167      }
168      TypeOrEntry::Type(Type::Object) => {
169        let fields = read_varint(encoding)?;
170        // Since the amount of fields exceeds our virtual address space, claim the encoding is
171        // short
172        let fields = usize::try_from(fields).map_err(|_| EpeeError::Short(usize::MAX))?;
173        self.push(TypeOrEntry::Entry, fields)?;
174        return Ok(Some(SingleStepResult::Object { fields }));
175      }
176      TypeOrEntry::Entry => {
177        let key = read_key(encoding)?;
178        let (kind, len) = Type::read(encoding)?;
179        let len = usize::try_from(len).map_err(|_| EpeeError::Short(usize::MAX))?;
180        self.push(TypeOrEntry::Type(kind), len)?;
181        return Ok(Some(SingleStepResult::Entry { key, kind, len }));
182      }
183    }
184    Ok(Some(SingleStepResult::Unit))
185  }
186
187  /// Step through the entirety of the next item.
188  ///
189  /// Returns `None` if the stack is empty.
190  pub(crate) fn step<'encoding, B: BytesLike<'encoding>>(
191    &mut self,
192    encoding: &mut B,
193  ) -> Result<Option<()>, EpeeError> {
194    let Some((_kind, len)) = self.peek() else { return Ok(None) };
195
196    let current_stack_depth = self.depth();
197    // Read until the next item within this array
198    let stop_at_stack_depth = if len.get() > 1 {
199      current_stack_depth
200    } else {
201      // Read until we've popped this item entirely
202      // We could peek at an item on the stack, therefore it has an item
203      current_stack_depth - 1
204    };
205
206    while {
207      self.single_step(encoding)?;
208      self.depth() != stop_at_stack_depth
209    } {}
210
211    Ok(Some(()))
212  }
213}