Skip to main content

monero_epee/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![doc = include_str!("../README.md")]
3#![expect(single_use_lifetimes, clippy::elidable_lifetime_names)] // Benefits clarity
4#![no_std]
5
6use core::marker::PhantomData;
7
8mod io;
9mod stack;
10mod parser;
11
12pub(crate) use io::*;
13pub use io::{BytesLike, String};
14pub(crate) use stack::*;
15pub use parser::*;
16
17/// An error incurred when decoding.
18#[derive(Clone, Copy, Debug)]
19pub enum EpeeError {
20  /// An unexpected state was reached while decoding.
21  InternalError,
22  /// The blob did not have the expected header.
23  InvalidHeader,
24  /// The blob did not have the expected version.
25  ///
26  /// For `EpeeError::InvalidVersion(version)`, `version` is the version read from the blob.
27  InvalidVersion(Option<u8>),
28  /// The blob was short, as discovered when trying to read `{0}` bytes.
29  Short(usize),
30  /// Unrecognized type specified.
31  UnrecognizedType,
32  /// An object defined a key of `""`.
33  EmptyKey,
34  /// The depth limit was exceeded.
35  DepthLimitExceeded,
36  /// An operation expected one type yet the actual type was distinct.
37  TypeError,
38  /// An `Epee` object was reused.
39  EpeeReuse,
40}
41
42/// The EPEE header.
43// https://github.com/monero-project/monero/blob/8d4c625713e3419573dfcc7119c8848f47cabbaa
44//  /contrib/epee/include/storages/portable_storage_base.h#L37-L38
45pub const HEADER: [u8; 8] = *b"\x01\x11\x01\x01\x01\x01\x02\x01";
46/// The supported version of the EPEE protocol.
47// https://github.com/monero-project/monero/blob/8d4c625713e3419573dfcc7119c8848f47cabbaa
48//  /contrib/epee/include/storages/portable_storage_base.h#L39
49pub const VERSION: u8 = 1;
50
51/// A decoder for an EPEE-encoded object.
52pub struct Epee<'encoding, B: BytesLike<'encoding>> {
53  current_encoding_state: B,
54  stack: Stack,
55  error: Option<EpeeError>,
56  _encoding_lifetime: PhantomData<&'encoding ()>,
57}
58/// An item with an EPEE-encoded object.
59pub struct EpeeEntry<'encoding, 'parent, B: BytesLike<'encoding>> {
60  root: Option<&'parent mut Epee<'encoding, B>>,
61  kind: Type,
62  len: usize,
63}
64
65// When this entry is dropped, advance the decoder past it
66impl<'encoding, 'parent, B: BytesLike<'encoding>> Drop for EpeeEntry<'encoding, 'parent, B> {
67  #[inline(always)]
68  fn drop(&mut self) {
69    if let Some(root) = self.root.take() {
70      while root.error.is_none() && (self.len != 0) {
71        root.error = root.stack.step(&mut root.current_encoding_state).err();
72        self.len -= 1;
73      }
74    }
75  }
76}
77
78impl<'encoding, B: BytesLike<'encoding>> Epee<'encoding, B> {
79  /// Create a new view of an encoding.
80  pub fn new(mut encoding: B) -> Result<Self, EpeeError> {
81    // Check the header
82    {
83      let mut present_header = [0; HEADER.len()];
84      encoding.read_into_slice(&mut present_header)?;
85      if present_header != HEADER {
86        Err(EpeeError::InvalidHeader)?;
87      }
88    }
89
90    // Check the version
91    {
92      let version = encoding.read_byte().ok();
93      if version != Some(VERSION) {
94        Err(EpeeError::InvalidVersion(version))?;
95      }
96    }
97
98    Ok(Epee {
99      current_encoding_state: encoding,
100      stack: Stack::root_object(),
101      error: None,
102      _encoding_lifetime: PhantomData,
103    })
104  }
105
106  /// Obtain an `EpeeEntry` representing the encoded object.
107  ///
108  /// This takes a mutable reference as `Epee` is the owned object representing the decoder's
109  /// state. However, this is not eligible to be called again after consumption. Multiple calls to
110  /// this function will cause an error to be returned.
111  pub fn entry(&mut self) -> Result<EpeeEntry<'encoding, '_, B>, EpeeError> {
112    if (self.stack.depth() != 1) || self.error.is_some() {
113      Err(EpeeError::EpeeReuse)?;
114    }
115    Ok(EpeeEntry { root: Some(self), kind: Type::Object, len: 1 })
116  }
117}
118
119/// An iterator over fields.
120pub struct FieldIterator<'encoding, 'parent, B: BytesLike<'encoding>> {
121  root: &'parent mut Epee<'encoding, B>,
122  len: usize,
123}
124
125// When this object is dropped, advance the decoder past the unread items
126impl<'encoding, 'parent, B: BytesLike<'encoding>> Drop for FieldIterator<'encoding, 'parent, B> {
127  #[inline(always)]
128  fn drop(&mut self) {
129    while self.root.error.is_none() && (self.len != 0) {
130      self.root.error = self.root.stack.step(&mut self.root.current_encoding_state).err();
131      self.len -= 1;
132    }
133  }
134}
135
136impl<'encoding, 'parent, B: BytesLike<'encoding>> FieldIterator<'encoding, 'parent, B> {
137  /// The next entry (key, value) within the object.
138  ///
139  /// This is approximate to `Iterator::next` yet each item maintains a mutable reference to the
140  /// iterator. Accordingly, we cannot use `Iterator::next` which requires items not borrow from
141  /// the iterator.
142  ///
143  /// [polonius-the-crab](https://docs.rs/polonius-the-crab) details a frequent limitation of
144  /// Rust's borrow checker which users of this function may incur. It also details potential
145  /// solutions (primarily using inlined code instead of functions, callbacks) before presenting
146  /// itself as a complete solution. Please refer to it if you have difficulties calling this
147  /// method for context.
148  #[expect(clippy::type_complexity, clippy::should_implement_trait)]
149  pub fn next(
150    &mut self,
151  ) -> Option<Result<(String<'encoding, B>, EpeeEntry<'encoding, '_, B>), EpeeError>> {
152    if let Some(error) = self.root.error {
153      return Some(Err(error));
154    }
155
156    self.len = self.len.checked_sub(1)?;
157    let (key, kind, len) = match self.root.stack.single_step(&mut self.root.current_encoding_state)
158    {
159      Ok(Some(SingleStepResult::Entry { key, kind, len })) => (key, kind, len),
160      // A `FieldIterator` was constructed incorrectly or some other internal error
161      Ok(_) => return Some(Err(EpeeError::InternalError)),
162      Err(e) => return Some(Err(e)),
163    };
164
165    Some(Ok((key, EpeeEntry { root: Some(self.root), kind, len })))
166  }
167}
168
169/// An iterator over an array.
170pub struct ArrayIterator<'encoding, 'parent, B: BytesLike<'encoding>> {
171  root: &'parent mut Epee<'encoding, B>,
172  kind: Type,
173  len: usize,
174}
175
176// When this array is dropped, advance the decoder past the unread items
177impl<'encoding, 'parent, B: BytesLike<'encoding>> Drop for ArrayIterator<'encoding, 'parent, B> {
178  #[inline(always)]
179  fn drop(&mut self) {
180    while self.root.error.is_none() && (self.len != 0) {
181      self.root.error = self.root.stack.step(&mut self.root.current_encoding_state).err();
182      self.len -= 1;
183    }
184  }
185}
186
187impl<'encoding, 'parent, B: BytesLike<'encoding>> ArrayIterator<'encoding, 'parent, B> {
188  /// The next item within the array.
189  ///
190  /// This is approximate to `Iterator::next` yet each item maintains a mutable reference to the
191  /// iterator. Accordingly, we cannot use `Iterator::next` which requires items not borrow from
192  /// the iterator.
193  ///
194  /// [polonius-the-crab](https://docs.rs/polonius-the-crab) details a frequent limitation of
195  /// Rust's borrow checker which users of this function may incur. It also details potential
196  /// solutions (primarily using inlined code instead of functions, callbacks) before presenting
197  /// itself as a complete solution. Please refer to it if you have difficulties calling this
198  /// method for context.
199  #[expect(clippy::should_implement_trait)]
200  pub fn next(&mut self) -> Option<Result<EpeeEntry<'encoding, '_, B>, EpeeError>> {
201    if let Some(err) = self.root.error {
202      return Some(Err(err));
203    }
204
205    self.len = self.len.checked_sub(1)?;
206    Some(Ok(EpeeEntry { root: Some(self.root), kind: self.kind, len: 1 }))
207  }
208}
209
210impl<'encoding, 'parent, B: BytesLike<'encoding>> EpeeEntry<'encoding, 'parent, B> {
211  /// The type of object this entry represents.
212  #[inline(always)]
213  pub fn kind(&self) -> Type {
214    self.kind
215  }
216
217  /// The amount of items present within this entry.
218  #[expect(clippy::len_without_is_empty)]
219  #[inline(always)]
220  pub fn len(&self) -> usize {
221    self.len
222  }
223
224  /// Iterate over the fields within this object.
225  pub fn fields(mut self) -> Result<FieldIterator<'encoding, 'parent, B>, EpeeError> {
226    let root = self.root.take().ok_or(EpeeError::InternalError)?;
227
228    if let Some(err) = root.error {
229      Err(err)?;
230    }
231
232    if (self.kind != Type::Object) || (self.len != 1) {
233      Err(EpeeError::TypeError)?;
234    }
235
236    // Read past the `Type::Object` this was constructed with into `[Type::Entry; n]`
237    let len = match root.stack.single_step(&mut root.current_encoding_state)? {
238      Some(SingleStepResult::Object { fields }) => fields,
239      _ => Err(EpeeError::InternalError)?,
240    };
241    Ok(FieldIterator { root, len })
242  }
243
244  /// Get an iterator of all items within this container.
245  ///
246  /// If you want to index a specific item, you may use `.iterate()?.nth(i)?`. An `index` method
247  /// isn't provided as each index operation is of O(n) complexity and single indexes SHOULD NOT be
248  /// used. Only exposing `iterate` attempts to make this clear to the user.
249  pub fn iterate(mut self) -> Result<ArrayIterator<'encoding, 'parent, B>, EpeeError> {
250    let root = self.root.take().ok_or(EpeeError::InternalError)?;
251
252    if let Some(err) = root.error {
253      Err(err)?;
254    }
255
256    Ok(ArrayIterator { root, kind: self.kind, len: self.len })
257  }
258
259  #[expect(clippy::wrong_self_convention)]
260  #[inline(always)]
261  fn to_primitive(mut self, kind: Type, slice: &mut [u8]) -> Result<&mut [u8], EpeeError> {
262    if (self.kind != kind) || (self.len != 1) {
263      Err(EpeeError::TypeError)?;
264    }
265
266    let root = self.root.take().ok_or(EpeeError::InternalError)?;
267    if let Some(error) = root.error {
268      Err(error)?;
269    }
270    root.stack.pop();
271    root.current_encoding_state.read_into_slice(slice)?;
272    Ok(slice)
273  }
274
275  /// Get the current item as an `i64`.
276  #[inline(always)]
277  pub fn to_i64(self) -> Result<i64, EpeeError> {
278    let mut buf = [0; core::mem::size_of::<i64>()];
279    Ok(i64::from_le_bytes(self.to_primitive(Type::Int64, &mut buf)?.try_into().unwrap()))
280  }
281
282  /// Get the current item as an `i32`.
283  #[inline(always)]
284  pub fn to_i32(self) -> Result<i32, EpeeError> {
285    let mut buf = [0; core::mem::size_of::<i32>()];
286    Ok(i32::from_le_bytes(self.to_primitive(Type::Int32, &mut buf)?.try_into().unwrap()))
287  }
288
289  /// Get the current item as an `i16`.
290  #[inline(always)]
291  pub fn to_i16(self) -> Result<i16, EpeeError> {
292    let mut buf = [0; core::mem::size_of::<i16>()];
293    Ok(i16::from_le_bytes(self.to_primitive(Type::Int16, &mut buf)?.try_into().unwrap()))
294  }
295
296  /// Get the current item as an `i8`.
297  #[inline(always)]
298  pub fn to_i8(self) -> Result<i8, EpeeError> {
299    let mut buf = [0; core::mem::size_of::<i8>()];
300    Ok(i8::from_le_bytes(self.to_primitive(Type::Int8, &mut buf)?.try_into().unwrap()))
301  }
302
303  /// Get the current item as a `u64`.
304  #[inline(always)]
305  pub fn to_u64(self) -> Result<u64, EpeeError> {
306    let mut buf = [0; core::mem::size_of::<u64>()];
307    Ok(u64::from_le_bytes(self.to_primitive(Type::Uint64, &mut buf)?.try_into().unwrap()))
308  }
309
310  /// Get the current item as a `u32`.
311  #[inline(always)]
312  pub fn to_u32(self) -> Result<u32, EpeeError> {
313    let mut buf = [0; core::mem::size_of::<u32>()];
314    Ok(u32::from_le_bytes(self.to_primitive(Type::Uint32, &mut buf)?.try_into().unwrap()))
315  }
316
317  /// Get the current item as a `u16`.
318  #[inline(always)]
319  pub fn to_u16(self) -> Result<u16, EpeeError> {
320    let mut buf = [0; core::mem::size_of::<u16>()];
321    Ok(u16::from_le_bytes(self.to_primitive(Type::Uint16, &mut buf)?.try_into().unwrap()))
322  }
323
324  /// Get the current item as a `u8`.
325  #[inline(always)]
326  pub fn to_u8(self) -> Result<u8, EpeeError> {
327    let mut buf = [0; core::mem::size_of::<u8>()];
328    Ok(self.to_primitive(Type::Uint8, &mut buf)?[0])
329  }
330
331  /// Get the current item as an `f64`.
332  #[inline(always)]
333  pub fn to_f64(self) -> Result<f64, EpeeError> {
334    let mut buf = [0; core::mem::size_of::<f64>()];
335    Ok(f64::from_le_bytes(self.to_primitive(Type::Double, &mut buf)?.try_into().unwrap()))
336  }
337
338  /// Get the current item as a 'string' (represented as a `B`).
339  #[inline(always)]
340  pub fn to_str(mut self) -> Result<String<'encoding, B>, EpeeError> {
341    if (self.kind != Type::String) || (self.len != 1) {
342      Err(EpeeError::TypeError)?;
343    }
344
345    let root = self.root.take().ok_or(EpeeError::InternalError)?;
346    if let Some(error) = root.error {
347      Err(error)?;
348    }
349    root.stack.pop();
350    read_str(&mut root.current_encoding_state)
351  }
352
353  /// Get the current item as a 'string' (represented as a `B`) of a specific length.
354  ///
355  /// This will error if the result is not actually the expected length.
356  #[inline(always)]
357  pub fn to_fixed_len_str(self, len: usize) -> Result<String<'encoding, B>, EpeeError> {
358    let str = self.to_str()?;
359    if str.len() != len {
360      Err(EpeeError::TypeError)?;
361    }
362    Ok(str)
363  }
364
365  /// Get the current item as a `bool`.
366  #[inline(always)]
367  pub fn to_bool(self) -> Result<bool, EpeeError> {
368    let mut buf = [0; core::mem::size_of::<bool>()];
369    Ok(self.to_primitive(Type::Bool, &mut buf)?[0] != 0)
370  }
371}