ic_kit_runtime/
lib.rs

1use futures::executor::block_on;
2use ic_kit_sys::ic0;
3use ic_kit_sys::ic0::runtime;
4use ic_kit_sys::ic0::Ic0CallHandler;
5use std::fmt::{Display, Formatter};
6use std::panic::set_hook;
7use std::thread::JoinHandle;
8use tokio::select;
9use tokio::sync::mpsc::{self, Receiver, Sender};
10
11pub struct Runtime {
12    /// The request we're currently processing.
13    processing: Option<Request>,
14    /// Inter-canister calls to perform if the current methods does not trap.
15    perform_calls: Vec<Call>,
16    /// The current call that is being constructed. When `call_perform` gets called we will push
17    /// this value to self.calls.
18    call_factory: Option<IncompleteCall>,
19    /// The thread in which the canister is being executed at.
20    execution_thread_handle: JoinHandle<()>,
21    /// The communication channel to send tasks to the execution thread.
22    task_tx: Sender<Box<dyn Fn() + Send>>,
23    /// Emits when the task we just sent has returned.
24    task_returned_rx: Receiver<()>,
25    /// To send the response to the calls.
26    reply_tx: Sender<runtime::Response>,
27    /// The channel that we use to get the requests from the execution thread.
28    request_rx: Receiver<runtime::Request>,
29}
30
31pub enum Request {
32    Init {
33        arg_data: Vec<u8>,
34    },
35    Update {
36        arg_data: Vec<u8>,
37    },
38    Query {
39        arg_data: Vec<u8>,
40    },
41    CanisterInspect {
42        arg_data: Vec<u8>,
43        method_name: String,
44    },
45    ReplyCallback {
46        arg_data: Vec<u8>,
47    },
48    RejectCallback {},
49    CleanupCallback {},
50    Heartbeat,
51    PostUpgrade,
52    PreUpgrade,
53}
54
55pub struct Call {}
56
57pub struct IncompleteCall {}
58
59impl Runtime {
60    pub fn new() -> Self {
61        let (request_tx, request_rx) = mpsc::channel(8);
62        let (reply_tx, reply_rx) = mpsc::channel(8);
63        let (task_tx, mut task_rx) = mpsc::channel::<Box<dyn Fn() + Send>>(8);
64        let (mut task_returned_tx, task_returned_rx) = mpsc::channel(8);
65
66        let thread = std::thread::spawn(move || {
67            // Register the ic-kit-sys handler for current thread, this will make ic-kit-sys to
68            // forward all of the system calls done in the current thread to the provided channel
69            // and use the rx channel for waiting on responses.
70            let handle = runtime::RuntimeHandle::new(reply_rx, request_tx);
71            ic0::register_handler(handle);
72
73            // set the custom panic hook, this will give us:
74            // 1. No message such as "thread panic during test" in the terminal.
75            // 2. Allow us to notify the main thread that we panicked.
76            // 3. Also allows us to signal the task runner that the task has returned, so it can
77            //    stop waiting for requests made by us.
78            let task_panicked_tx = task_returned_tx.clone();
79            set_hook(Box::new(move |_| {
80                block_on(async {
81                    let trap_message = "Canister panicked";
82                    unsafe {
83                        ic0::trap(trap_message.as_ptr() as isize, trap_message.len() as isize)
84                    };
85                    task_panicked_tx.send(()).await.expect("ic-kit-runtime: Execution thread could not send task-completion signal to the main thread after panic.");
86                });
87            }));
88
89            block_on(async {
90                while let Some(task) = task_rx.recv().await {
91                    task();
92                    task_returned_tx.send(()).await.expect("ic-kit-runtime: Execution thread could not send task-completion signal to the main thread.")
93                }
94            });
95        });
96
97        Self {
98            processing: None,
99            perform_calls: vec![],
100            call_factory: None,
101            execution_thread_handle: thread,
102            task_tx,
103            task_returned_rx,
104            reply_tx,
105            request_rx,
106        }
107    }
108
109    /// Send a request to the execution thread and waits until it's finished.
110    pub async fn send(&mut self, request: Request) {
111        self.task_tx
112            .send(Box::new(|| {
113                println!("Some function related to the request.")
114            }))
115            .await
116            .unwrap_or_else(|_| {
117                panic!("ic-kit-runtime: Could not send the task to the execution thread.")
118            });
119
120        loop {
121            select! {
122                Some(()) = self.task_returned_rx.recv() => {
123                    // Okay the task returned successfully, we can give up.
124                    return;
125                },
126                Some(req) = self.request_rx.recv() => {
127                    let res = req.proxy(self);
128                    self.reply_tx
129                        .send(res)
130                        .await
131                        .expect("ic-kit-runtime: Could not send the system API call's response to the execution thread.");
132                }
133            }
134        }
135    }
136
137    pub fn explicit_trap(&mut self, message: String) -> ! {
138        panic!("Canister Trapped: {}", message)
139    }
140}
141
142impl Ic0CallHandler for Runtime {
143    fn msg_arg_data_size(&mut self) -> isize {
144        let req = self
145            .processing
146            .as_ref()
147            .expect("Unexpected: No request is being processed.");
148
149        match req {
150            Request::Init { arg_data, .. } => arg_data.len() as isize,
151            Request::Update { arg_data, .. } => arg_data.len() as isize,
152            Request::Query { arg_data, .. } => arg_data.len() as isize,
153            Request::CanisterInspect { arg_data, .. } => arg_data.len() as isize,
154            Request::ReplyCallback { arg_data, .. } => arg_data.len() as isize,
155            r => self.explicit_trap(format!(
156                "ic0::msg_arg_data_size was invoked from canister_'{}'",
157                r
158            )),
159        }
160    }
161
162    fn msg_arg_data_copy(&mut self, dst: isize, offset: isize, size: isize) {
163        let req = self
164            .processing
165            .as_ref()
166            .expect("Unexpected: No request is being processed.");
167
168        let arg_data = match req {
169            Request::Init { arg_data, .. } => arg_data,
170            Request::Update { arg_data, .. } => arg_data,
171            Request::Query { arg_data, .. } => arg_data,
172            Request::CanisterInspect { arg_data, .. } => arg_data,
173            Request::ReplyCallback { arg_data, .. } => arg_data,
174            r => self.explicit_trap(format!(
175                "ic0::msg_arg_data_copy was invoked from canister_'{}'",
176                r
177            )),
178        };
179
180        copy_to_canister(dst, offset, size, arg_data)
181            .map_err(|e| self.explicit_trap(e))
182            .expect("TODO: panic message");
183    }
184
185    fn msg_caller_size(&mut self) -> isize {
186        todo!()
187    }
188
189    fn msg_caller_copy(&mut self, dst: isize, offset: isize, size: isize) {
190        todo!()
191    }
192
193    fn msg_reject_code(&mut self) -> i32 {
194        todo!()
195    }
196
197    fn msg_reject_msg_size(&mut self) -> isize {
198        todo!()
199    }
200
201    fn msg_reject_msg_copy(&mut self, dst: isize, offset: isize, size: isize) {
202        todo!()
203    }
204
205    fn msg_reply_data_append(&mut self, src: isize, size: isize) {
206        todo!()
207    }
208
209    fn msg_reply(&mut self) {
210        todo!()
211    }
212
213    fn msg_reject(&mut self, src: isize, size: isize) {
214        todo!()
215    }
216
217    fn msg_cycles_available(&mut self) -> i64 {
218        todo!()
219    }
220
221    fn msg_cycles_available128(&mut self, dst: isize) {
222        todo!()
223    }
224
225    fn msg_cycles_refunded(&mut self) -> i64 {
226        todo!()
227    }
228
229    fn msg_cycles_refunded128(&mut self, dst: isize) {
230        todo!()
231    }
232
233    fn msg_cycles_accept(&mut self, max_amount: i64) -> i64 {
234        todo!()
235    }
236
237    fn msg_cycles_accept128(&mut self, max_amount_high: i64, max_amount_low: i64, dst: isize) {
238        todo!()
239    }
240
241    fn canister_self_size(&mut self) -> isize {
242        todo!()
243    }
244
245    fn canister_self_copy(&mut self, dst: isize, offset: isize, size: isize) {
246        todo!()
247    }
248
249    fn canister_cycle_balance(&mut self) -> i64 {
250        todo!()
251    }
252
253    fn canister_cycle_balance128(&mut self, dst: isize) {
254        todo!()
255    }
256
257    fn canister_status(&mut self) -> i32 {
258        todo!()
259    }
260
261    fn msg_method_name_size(&mut self) -> isize {
262        todo!()
263    }
264
265    fn msg_method_name_copy(&mut self, dst: isize, offset: isize, size: isize) {
266        todo!()
267    }
268
269    fn accept_message(&mut self) {
270        todo!()
271    }
272
273    fn call_new(
274        &mut self,
275        callee_src: isize,
276        callee_size: isize,
277        name_src: isize,
278        name_size: isize,
279        reply_fun: isize,
280        reply_env: isize,
281        reject_fun: isize,
282        reject_env: isize,
283    ) {
284        todo!()
285    }
286
287    fn call_on_cleanup(&mut self, fun: isize, env: isize) {
288        todo!()
289    }
290
291    fn call_data_append(&mut self, src: isize, size: isize) {
292        todo!()
293    }
294
295    fn call_cycles_add(&mut self, amount: i64) {
296        todo!()
297    }
298
299    fn call_cycles_add128(&mut self, amount_high: i64, amount_low: i64) {
300        todo!()
301    }
302
303    fn call_perform(&mut self) -> i32 {
304        todo!()
305    }
306
307    fn stable_size(&mut self) -> i32 {
308        todo!()
309    }
310
311    fn stable_grow(&mut self, new_pages: i32) -> i32 {
312        todo!()
313    }
314
315    fn stable_write(&mut self, offset: i32, src: isize, size: isize) {
316        todo!()
317    }
318
319    fn stable_read(&mut self, dst: isize, offset: i32, size: isize) {
320        todo!()
321    }
322
323    fn stable64_size(&mut self) -> i64 {
324        todo!()
325    }
326
327    fn stable64_grow(&mut self, new_pages: i64) -> i64 {
328        todo!()
329    }
330
331    fn stable64_write(&mut self, offset: i64, src: i64, size: i64) {
332        todo!()
333    }
334
335    fn stable64_read(&mut self, dst: i64, offset: i64, size: i64) {
336        todo!()
337    }
338
339    fn certified_data_set(&mut self, src: isize, size: isize) {
340        todo!()
341    }
342
343    fn data_certificate_present(&mut self) -> i32 {
344        todo!()
345    }
346
347    fn data_certificate_size(&mut self) -> isize {
348        todo!()
349    }
350
351    fn data_certificate_copy(&mut self, dst: isize, offset: isize, size: isize) {
352        todo!()
353    }
354
355    fn time(&mut self) -> i64 {
356        todo!()
357    }
358
359    fn performance_counter(&mut self, counter_type: i32) -> i64 {
360        todo!()
361    }
362
363    fn debug_print(&mut self, src: isize, size: isize) {
364        todo!()
365    }
366
367    fn trap(&mut self, src: isize, size: isize) {
368        todo!()
369    }
370}
371
372impl Display for Request {
373    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
374        match self {
375            Request::Init { .. } => f.write_str("init"),
376            Request::Update { .. } => f.write_str("update"),
377            Request::Query { .. } => f.write_str("query"),
378            Request::CanisterInspect { .. } => f.write_str("inspect"),
379            Request::ReplyCallback { .. } => f.write_str("reply_callback"),
380            Request::RejectCallback { .. } => f.write_str("reject_callback"),
381            Request::CleanupCallback { .. } => f.write_str("cleanup_callback"),
382            Request::Heartbeat => f.write_str("heartbeat"),
383            Request::PostUpgrade => f.write_str("post_upgrade"),
384            Request::PreUpgrade => f.write_str("init"),
385        }
386    }
387}
388
389fn copy_to_canister(dst: isize, offset: isize, size: isize, data: &[u8]) -> Result<(), String> {
390    let dst = dst as usize;
391    let offset = offset as usize;
392    let size = size as usize;
393
394    if offset + size > data.len() {
395        return Err("Out of bound read.".into());
396    }
397
398    let slice = unsafe { std::slice::from_raw_parts_mut(dst as *mut u8, size) };
399    slice.copy_from_slice(&data[offset..offset + size]);
400    Ok(())
401}