Skip to main content

monero_epee/
io.rs

1//! IO primitives around bytes.
2
3use core::marker::PhantomData;
4
5use crate::EpeeError;
6
7/// An item which is like a `&[u8]`.
8pub trait BytesLike<'encoding>: Sized {
9  /// The type representing the length of a _read_ `BytesLike`, if a `BytesLike` does not
10  /// inherently know its length.
11  ///
12  /// This should be `usize` or `()`.
13  type ExternallyTrackedLength: Sized + Copy;
14
15  /// The length of these bytes.
16  fn len(&self, len: Self::ExternallyTrackedLength) -> usize;
17
18  /// Read a fixed amount of bytes from the container.
19  ///
20  /// This MUST return `Ok((len, slice))` where `slice` is the expected length or `Err(_)`.
21  fn read_bytes(
22    &mut self,
23    bytes: usize,
24  ) -> Result<(Self::ExternallyTrackedLength, Self), EpeeError>;
25
26  /// Read a fixed amount of bytes from the container into a slice.
27  /*
28    We _could_ provide this method around `read_bytes` but it'd be a very inefficient
29    default implementation. It's best to require callers provide the implementation.
30  */
31  fn read_into_slice(&mut self, slice: &mut [u8]) -> Result<(), EpeeError>;
32
33  /// Read a byte from the container.
34  #[inline(always)]
35  fn read_byte(&mut self) -> Result<u8, EpeeError> {
36    let mut buf = [0; 1];
37    self.read_into_slice(&mut buf)?;
38    Ok(buf[0])
39  }
40
41  /// Advance the container by a certain amount of bytes.
42  #[inline(always)]
43  fn advance<const N: usize>(&mut self) -> Result<(), EpeeError> {
44    self.read_bytes(N).map(|_| ())
45  }
46}
47
48impl<'encoding> BytesLike<'encoding> for &'encoding [u8] {
49  type ExternallyTrackedLength = ();
50
51  #[inline(always)]
52  fn len(&self, (): ()) -> usize {
53    <[u8]>::len(self)
54  }
55
56  #[inline(always)]
57  fn read_bytes(
58    &mut self,
59    bytes: usize,
60  ) -> Result<(Self::ExternallyTrackedLength, Self), EpeeError> {
61    if self.len() < bytes {
62      Err(EpeeError::Short(bytes))?;
63    }
64    let res = &self[.. bytes];
65    *self = &self[bytes ..];
66    Ok(((), res))
67  }
68
69  #[inline(always)]
70  fn read_into_slice(&mut self, slice: &mut [u8]) -> Result<(), EpeeError> {
71    /*
72      Ideally, we could return a immutable slice here as to avoid allocating the destination (even
73      if on the stack) and copying into it. That's only possible as this is itself a slice however,
74      so we can return a subslice. Allowing distinct containers does effectively require this
75      pattern. Thankfully, we only call this method for a max of just eight bytes.
76    */
77    slice.copy_from_slice(self.read_bytes(slice.len())?.1);
78    Ok(())
79  }
80}
81
82/// Read a VarInt per EPEE's definition.
83///
84/// This does not require the VarInt is canonically encoded. It _may_ be malleated to have a larger
85/// than necessary encoding.
86// https://github.com/monero-project/monero/blob/8d4c625713e3419573dfcc7119c8848f47cabbaa
87//   /contrib/epee/include/storages/portable_storage_from_bin.h#L237-L255
88pub(crate) fn read_varint<'encoding>(
89  reader: &mut impl BytesLike<'encoding>,
90) -> Result<u64, EpeeError> {
91  let vi_start = reader.read_byte()?;
92
93  // https://github.com/monero-project/monero/blob/8d4c625713e3419573dfcc7119c8848f47cabbaa
94  //  /contrib/epee/include/storages/portable_storage_base.h#L41
95  let len = match vi_start & 0b11 {
96    0 => 1,
97    1 => 2,
98    2 => 4,
99    3 => 8,
100    _ => unreachable!(),
101  };
102
103  let mut vi = u64::from(vi_start);
104  for i in 1 .. len {
105    vi |= u64::from(reader.read_byte()?) << (i * 8);
106  }
107  vi >>= 2;
108
109  Ok(vi)
110}
111
112/// A collection of bytes with an associated length.
113///
114/// This avoids defining `BytesLike::len` which lets us relax the requirement `BytesLike` knows its
115/// length before it has reached its end.
116pub struct String<'encoding, B: BytesLike<'encoding>> {
117  pub(crate) len: B::ExternallyTrackedLength,
118  pub(crate) bytes: B,
119  pub(crate) _encoding: PhantomData<&'encoding ()>,
120}
121
122impl<'encoding, B: BytesLike<'encoding>> String<'encoding, B> {
123  /// If this string is empty.
124  #[inline(always)]
125  pub fn is_empty(&self) -> bool {
126    self.bytes.len(self.len) == 0
127  }
128
129  /// The length of this string.
130  #[inline(always)]
131  pub fn len(&self) -> usize {
132    self.bytes.len(self.len)
133  }
134
135  /// Consume this into its underlying bytes.
136  #[inline(always)]
137  pub fn consume(self) -> B {
138    self.bytes
139  }
140}
141
142/// Read a string per EPEE's definition.
143#[inline(always)]
144pub(crate) fn read_str<'encoding, B: BytesLike<'encoding>>(
145  reader: &mut B,
146) -> Result<String<'encoding, B>, EpeeError> {
147  /*
148    Since this VarInt exceeds `usize::MAX`, it references more bytes than our system can represent
149    within a single slice. Accordingly, our slice _must_ be short. As we potentially can't
150    represent how short, we simply use `usize::MAX` here.
151  */
152  let len = usize::try_from(read_varint(reader)?).map_err(|_| EpeeError::Short(usize::MAX))?;
153  let (len, bytes) = reader.read_bytes(len)?;
154  Ok(String { len, bytes, _encoding: PhantomData })
155}