cannon_io/oracle/
oracle_reader.rs

1use crate::syscalls::{self, SyscallError};
2use alloc::vec::Vec;
3
4pub use super::PreimageKey;
5
6#[derive(Debug)]
7pub enum OracleError {
8    NoKeySet,
9    EndOfData,
10    SyscallError(syscalls::SyscallError),
11}
12
13impl From<SyscallError> for OracleError {
14    fn from(e: SyscallError) -> Self {
15        OracleError::SyscallError(e)
16    }
17}
18
19pub struct OracleReader {
20    key: Option<PreimageKey>,
21    length: u64,
22    cursor: u64,
23}
24
25// the only way to access an oracle reader is through this singleton.
26// This is to ensure there cannot be more than one at a time which would have
27// unpredictable results
28static mut ORACLE_READER: Option<OracleReader> = Some(OracleReader {
29    key: None,
30    length: 0,
31    cursor: 0,
32});
33
34/// Get the global oracle reader
35///
36/// # Panics
37/// This will panic if called more than once. This is to ensure there is only one oracle reader at once
38/// as it encapsulates host global state.
39pub fn oracle_reader() -> OracleReader {
40    unsafe {
41        let reader = core::ptr::replace(&mut ORACLE_READER, None);
42        reader.expect("oracle_reader` has already been called. Can only call once per program")
43    }
44}
45
46impl OracleReader {
47    /// Set the preimage key for the global oracle reader. This will overwrite any existing key
48    ///
49    /// Internally this sends the 32 bytes of the key to the host by writing into the WritePreimage file descriptor.
50    /// This may require several writes as the host may only accept a few bytes at a time. Once 32 bytes have been written
51    /// successfully the key is considered set. If it fails to write 32 bytes it will return an error.
52    /// Once it has written the key it will read the first 8 bytes of the ReadPreimage file descriptor which is the length
53    /// encoded as a big endian u64. This is stored in the oracle reader along with the read cursor position.
54    ///
55    /// # Examples
56    /// ```
57    /// use cannon_io::prelude::*;
58    ///
59    /// let mut oracle = oracle_reader();
60    /// oracle.set_key(PreimageKey::new_local(&[0xff;31]));
61    /// ```
62    pub fn set_key(&mut self, key: PreimageKey) -> Result<(), OracleError> {
63        self.key = Some(key);
64        let key_bytes: [u8; 32] = key.into();
65        let mut written = 0;
66        // need to loop and write the bytes a chunk at a time until all are written
67        loop {
68            match syscalls::write_preimage(&key_bytes[written..]) {
69                Ok(0) => break,
70                Ok(n) => {
71                    written += n as usize;
72                    continue;
73                }
74                Err(e) => return Err(e.into()),
75            }
76        }
77
78        // first read the length prefix, cache and reset the cursor
79        let mut length_buffer = [0_u8; 8];
80        self.read_exact(&mut length_buffer)?;
81        self.length = u64::from_be_bytes(length_buffer);
82        self.cursor = 0;
83        Ok(())
84    }
85
86    /// Return the current key stored in the global oracle reader
87    pub fn key(&self) -> Option<PreimageKey> {
88        self.key
89    }
90
91    /// length of the current pre-image
92    pub fn length(&self) -> u64 {
93        self.length
94    }
95
96    /// Current position of the read cursor within the current pre-image
97    pub fn cursor(&self) -> u64 {
98        self.cursor
99    }
100
101    /// Get the data corresponding to the currently set key from the host. Return the data in a new heap allocated `Vec<u8>`
102    ///
103    /// Internally this reads self.length bytes from the ReadPreimage file descriptor into a new heap allocated `Vec<u8>` and returns it.
104    /// This is a high level way to interact with the preimage oracle but may not be the best way if heap allocations are not desirable.
105    ///
106    /// # Examples
107    /// ```
108    /// use cannon_io::prelude::*;
109    ///
110    /// let mut oracle = oracle_reader();
111    /// let key = PreimageKey::new_local(&[0xff;31]);
112    /// let data = oracle.get(key).unwrap();
113    /// ```
114    pub fn get(&mut self, key: PreimageKey) -> Result<Vec<u8>, OracleError> {
115        self.set_key(key)?;
116        let mut data_buffer = Vec::with_capacity(self.length as usize);
117        data_buffer.resize(self.length as usize, 0);
118        self.read_exact(&mut data_buffer)?;
119        Ok(data_buffer)
120    }
121
122    /// Get the data corresponding to the currently set key from the host. Write the data into the provided buffer
123    ///
124    /// # Panics
125    /// This will panic if the size of the buffer is not equal to the size of the preimage as reported by the host
126    ///
127    /// # Examples
128    /// ```
129    /// use cannon_io::prelude::*;
130    ///
131    /// let mut oracle = oracle_reader();
132    /// let key = PreimageKey::new_local(&[0xff;31]);
133    /// let mut buffer = [0_u8; 100];
134    /// oracle.get_exact(key, &mut buffer).unwrap();
135    /// ```
136    pub fn get_exact(&mut self, key: PreimageKey, buf: &mut [u8]) -> Result<(), OracleError> {
137        self.set_key(key)?;
138        assert!(self.length as usize == buf.len(), "Buffer not correct size for preimage data. Preimage size: {} bytes, buffer size: {} bytes", self.length, buf.len());
139        self.read_exact(buf)?;
140        Ok(())
141    }
142}
143
144// Since the Rust Error trait cannot be used in no_std, we define our own
145// trait that is very similar and should feel familiar
146pub trait Read {
147    type Error;
148
149    fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error>;
150
151    fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize, Self::Error>;
152
153    fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), Self::Error>;
154}
155
156impl Read for OracleReader {
157    type Error = OracleError;
158
159    /// Read up to buf.len() bytes from the ReadPreimage file descriptor into buf
160    ///
161    /// This returns the number of bytes read. If the end of the data is reached it will return 0.
162    /// Subsequent calls can be used to write the rest of the data as the host may only accept arbitrary small
163    /// chunks at a time.
164    fn read(&mut self, buf: &mut [u8]) -> Result<usize, Self::Error> {
165        self.key.ok_or(Self::Error::NoKeySet)?;
166        let read = syscalls::read_preimage(buf)?;
167        self.cursor += read as u64;
168        Ok(read as usize)
169    }
170
171    /// Read all the data from the ReadPreimage file descriptor into buf
172    ///
173    /// This will read all the data from the ReadPreimage file descriptor into buf. This is implemented by calling
174    /// read() in a loop until it returns 0. This will read at most 32 bytes per call to read.
175    /// New space will be allocated into the return buffer if it fills up.
176    fn read_to_end(&mut self, buf: &mut Vec<u8>) -> Result<usize, Self::Error> {
177        let mut chunk = [0; 32];
178        loop {
179            let read = self.read(&mut chunk)?;
180            if read == 0 {
181                break;
182            }
183            buf.extend_from_slice(&chunk[..read]);
184        }
185        Ok(buf.len())
186    }
187
188    /// Read exactly buf.len() bytes from the ReadPreimage file descriptor into buf
189    ///
190    /// This will read exactly buf.len() bytes from the ReadPreimage file descriptor into buf. This is implemented by calling
191    /// read() in a loop until it returns 0. This will read at most 32 bytes per call to read.
192    /// If the end of the data is reached before buf.len() bytes have been read it will return an error.
193    /// After this function returns there may be more bytes in the stream to be read.
194    fn read_exact(&mut self, buf: &mut [u8]) -> Result<(), Self::Error> {
195        let mut chunk = [0; 32];
196        let mut read = 0;
197        while read < buf.len() {
198            let chunk_read = self.read(&mut chunk)?;
199            if chunk_read == 0 {
200                return Err(OracleError::EndOfData);
201            }
202            buf[read..read + chunk_read].copy_from_slice(&chunk[..chunk_read]);
203            read += chunk_read;
204        }
205        Ok(())
206    }
207}