capsules_extra/hmac.rs
1// Licensed under the Apache License, Version 2.0 or the MIT License.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3// Copyright Tock Contributors 2022.
4
5//! HMAC (Hash-based Message Authentication Code).
6//!
7//! Usage
8//! -----
9//!
10//! ```rust,ignore
11//! let hmac = &earlgrey::hmac::HMAC;
12//!
13//! let mux_hmac = static_init!(MuxHmac<'static, lowrisc::hmac::Hmac>, MuxHmac::new(hmac));
14//! digest::Digest::set_client(&earlgrey::hmac::HMAC, mux_hmac);
15//!
16//! let virtual_hmac_user = static_init!(
17//! VirtualMuxHmac<'static, lowrisc::hmac::Hmac>,
18//! VirtualMuxHmac::new(mux_hmac)
19//! );
20//! let hmac = static_init!(
21//! capsules::hmac::HmacDriver<'static, VirtualMuxHmac<'static, lowrisc::hmac::Hmac>>,
22//! capsules::hmac::HmacDriver::new(
23//! virtual_hmac_user,
24//! board_kernel.create_grant(&memory_allocation_cap),
25//! )
26//! );
27//! digest::Digest::set_client(virtual_hmac_user, hmac);
28//! ```
29
30use capsules_core::driver;
31use kernel::errorcode::into_statuscode;
32/// Syscall driver number.
33pub const DRIVER_NUM: usize = driver::NUM::Hmac as usize;
34
35/// Ids for read-only allow buffers
36mod ro_allow {
37 pub const KEY: usize = 0;
38 pub const DATA: usize = 1;
39 pub const COMPARE: usize = 2;
40 /// The number of allow buffers the kernel stores for this grant
41 pub const COUNT: u8 = 3;
42}
43
44/// Ids for read-write allow buffers
45mod rw_allow {
46 pub const DEST: usize = 2;
47 /// The number of allow buffers the kernel stores for this grant
48 pub const COUNT: u8 = 3;
49}
50
51use core::cell::Cell;
52
53use kernel::grant::{AllowRoCount, AllowRwCount, Grant, UpcallCount};
54use kernel::hil::digest;
55use kernel::processbuffer::{ReadableProcessBuffer, WriteableProcessBuffer};
56use kernel::syscall::{CommandReturn, SyscallDriver};
57use kernel::utilities::cells::{OptionalCell, TakeCell};
58use kernel::utilities::leasable_buffer::SubSlice;
59use kernel::utilities::leasable_buffer::SubSliceMut;
60use kernel::{ErrorCode, ProcessId};
61
62enum ShaOperation {
63 Sha256,
64 Sha384,
65 Sha512,
66}
67
68// Temporary buffer to copy the keys from userspace into
69//
70// Needs to be able to accommodate the largest key sizes, e.g. 512
71const TMP_KEY_BUFFER_SIZE: usize = 512 / 8;
72
73pub struct HmacDriver<'a, H: digest::Digest<'a, DIGEST_LEN>, const DIGEST_LEN: usize> {
74 hmac: &'a H,
75
76 active: Cell<bool>,
77
78 apps: Grant<
79 App,
80 UpcallCount<1>,
81 AllowRoCount<{ ro_allow::COUNT }>,
82 AllowRwCount<{ rw_allow::COUNT }>,
83 >,
84 processid: OptionalCell<ProcessId>,
85
86 data_buffer: TakeCell<'static, [u8]>,
87 data_copied: Cell<usize>,
88 dest_buffer: TakeCell<'static, [u8; DIGEST_LEN]>,
89}
90
91impl<
92 'a,
93 H: digest::Digest<'a, DIGEST_LEN>
94 + digest::HmacSha256
95 + digest::HmacSha384
96 + digest::HmacSha512,
97 const DIGEST_LEN: usize,
98 > HmacDriver<'a, H, DIGEST_LEN>
99{
100 pub fn new(
101 hmac: &'a H,
102 data_buffer: &'static mut [u8],
103 dest_buffer: &'static mut [u8; DIGEST_LEN],
104 grant: Grant<
105 App,
106 UpcallCount<1>,
107 AllowRoCount<{ ro_allow::COUNT }>,
108 AllowRwCount<{ rw_allow::COUNT }>,
109 >,
110 ) -> HmacDriver<'a, H, DIGEST_LEN> {
111 HmacDriver {
112 hmac,
113 active: Cell::new(false),
114 apps: grant,
115 processid: OptionalCell::empty(),
116 data_buffer: TakeCell::new(data_buffer),
117 data_copied: Cell::new(0),
118 dest_buffer: TakeCell::new(dest_buffer),
119 }
120 }
121
122 fn run(&self) -> Result<(), ErrorCode> {
123 self.processid.map_or(Err(ErrorCode::RESERVE), |processid| {
124 self.apps
125 .enter(processid, |app, kernel_data| {
126 kernel_data
127 .get_readonly_processbuffer(ro_allow::KEY)
128 .and_then(|key| {
129 key.enter(|k| {
130 if let Some(op) = &app.sha_operation {
131 let mut tmp_key_buffer: [u8; TMP_KEY_BUFFER_SIZE] =
132 [0; TMP_KEY_BUFFER_SIZE];
133 let key_len = core::cmp::min(k.len(), TMP_KEY_BUFFER_SIZE);
134 k[..key_len].copy_to_slice(&mut tmp_key_buffer[..key_len]);
135
136 match op {
137 ShaOperation::Sha256 => self
138 .hmac
139 .set_mode_hmacsha256(&tmp_key_buffer[..key_len]),
140 ShaOperation::Sha384 => self
141 .hmac
142 .set_mode_hmacsha384(&tmp_key_buffer[..key_len]),
143 ShaOperation::Sha512 => self
144 .hmac
145 .set_mode_hmacsha512(&tmp_key_buffer[..key_len]),
146 }
147 } else {
148 Err(ErrorCode::INVAL)
149 }
150 })
151 })
152 .unwrap_or(Err(ErrorCode::RESERVE))?;
153
154 kernel_data
155 .get_readonly_processbuffer(ro_allow::DATA)
156 .and_then(|data| {
157 data.enter(|data| {
158 let mut static_buffer_len = 0;
159 self.data_buffer.map(|buf| {
160 // Determine the size of the static buffer we have
161 static_buffer_len = buf.len();
162
163 if static_buffer_len > data.len() {
164 static_buffer_len = data.len()
165 }
166
167 self.data_copied.set(static_buffer_len);
168
169 // Copy the data into the static buffer
170 data[..static_buffer_len]
171 .copy_to_slice(&mut buf[..static_buffer_len]);
172 });
173
174 // Add the data from the static buffer to the HMAC
175 let mut lease_buf = SubSliceMut::new(
176 self.data_buffer.take().ok_or(ErrorCode::RESERVE)?,
177 );
178 lease_buf.slice(0..static_buffer_len);
179 if let Err(e) = self.hmac.add_mut_data(lease_buf) {
180 self.data_buffer.replace(e.1.take());
181 return Err(e.0);
182 }
183 Ok(())
184 })
185 })
186 .unwrap_or(Err(ErrorCode::RESERVE))
187 })
188 .unwrap_or_else(|err| Err(err.into()))
189 })
190 }
191
192 fn calculate_digest(&self) -> Result<(), ErrorCode> {
193 self.data_copied.set(0);
194
195 if let Err(e) = self
196 .hmac
197 .run(self.dest_buffer.take().ok_or(ErrorCode::RESERVE)?)
198 {
199 // Error, clear the processid and data
200 self.hmac.clear_data();
201 self.processid.clear();
202 self.dest_buffer.replace(e.1);
203
204 return Err(e.0);
205 }
206
207 Ok(())
208 }
209
210 fn verify_digest(&self) -> Result<(), ErrorCode> {
211 self.data_copied.set(0);
212
213 if let Err(e) = self
214 .hmac
215 .verify(self.dest_buffer.take().ok_or(ErrorCode::RESERVE)?)
216 {
217 // Error, clear the processid and data
218 self.hmac.clear_data();
219 self.processid.clear();
220 self.dest_buffer.replace(e.1);
221
222 return Err(e.0);
223 }
224
225 Ok(())
226 }
227
228 fn check_queue(&self) {
229 for appiter in self.apps.iter() {
230 let started_command = appiter.enter(|app, _| {
231 // If an app is already running let it complete
232 if self.processid.is_some() {
233 return true;
234 }
235
236 // If this app has a pending command let's use it.
237 app.pending_run_app.take().is_some_and(|processid| {
238 // Mark this driver as being in use.
239 self.processid.set(processid);
240 // Actually make the buzz happen.
241 self.run() == Ok(())
242 })
243 });
244 if started_command {
245 break;
246 }
247 }
248 }
249}
250
251impl<
252 'a,
253 H: digest::Digest<'a, DIGEST_LEN>
254 + digest::HmacSha256
255 + digest::HmacSha384
256 + digest::HmacSha512,
257 const DIGEST_LEN: usize,
258 > digest::ClientData<DIGEST_LEN> for HmacDriver<'a, H, DIGEST_LEN>
259{
260 // Because data needs to be copied from a userspace buffer into a kernel (RAM) one,
261 // we always pass mut data; this callback should never be invoked.
262 fn add_data_done(&self, _result: Result<(), ErrorCode>, _data: SubSlice<'static, u8>) {}
263
264 fn add_mut_data_done(&self, _result: Result<(), ErrorCode>, data: SubSliceMut<'static, u8>) {
265 self.processid.map(move |id| {
266 self.apps
267 .enter(id, move |app, kernel_data| {
268 let mut data_len = 0;
269 let mut exit = false;
270 let mut static_buffer_len = 0;
271
272 self.data_buffer.replace(data.take());
273
274 self.data_buffer.map(|buf| {
275 let ret = kernel_data
276 .get_readonly_processbuffer(ro_allow::DATA)
277 .and_then(|data| {
278 data.enter(|data| {
279 // Determine the size of the static buffer we have
280 static_buffer_len = buf.len();
281 // Determine how much data we have already copied
282 let copied_data = self.data_copied.get();
283
284 data_len = data.len();
285
286 if data_len > copied_data {
287 let remaining_data = &data[copied_data..];
288 let remaining_len = data_len - copied_data;
289
290 if remaining_len < static_buffer_len {
291 remaining_data.copy_to_slice(&mut buf[..remaining_len]);
292 } else {
293 remaining_data[..static_buffer_len].copy_to_slice(buf);
294 }
295 }
296 Ok(())
297 })
298 })
299 .unwrap_or(Err(ErrorCode::RESERVE));
300
301 if ret == Err(ErrorCode::RESERVE) {
302 // No data buffer, clear the processid and data
303 self.hmac.clear_data();
304 self.processid.clear();
305 exit = true;
306 }
307 });
308
309 if exit {
310 return;
311 }
312
313 if static_buffer_len > 0 {
314 let copied_data = self.data_copied.get();
315
316 if data_len > copied_data {
317 // Update the amount of data copied
318 self.data_copied.set(copied_data + static_buffer_len);
319
320 let mut lease_buf = SubSliceMut::new(self.data_buffer.take().unwrap());
321
322 // Add the data from the static buffer to the HMAC
323 if data_len < (copied_data + static_buffer_len) {
324 lease_buf.slice(..(data_len - copied_data))
325 }
326
327 if self.hmac.add_mut_data(lease_buf).is_err() {
328 // Error, clear the processid and data
329 self.hmac.clear_data();
330 self.processid.clear();
331 return;
332 }
333
334 // Return as we don't want to run the digest yet
335 return;
336 }
337 }
338
339 // If we get here we are ready to run the digest, reset the copied data
340 if app.op.get().unwrap() == UserSpaceOp::Run {
341 if let Err(e) = self.calculate_digest() {
342 kernel_data
343 .schedule_upcall(0, (into_statuscode(e.into()), 0, 0))
344 .ok();
345 }
346 } else if app.op.get().unwrap() == UserSpaceOp::Verify {
347 let _ = kernel_data
348 .get_readonly_processbuffer(ro_allow::COMPARE)
349 .and_then(|compare| {
350 compare.enter(|compare| {
351 let mut static_buffer_len = 0;
352 self.dest_buffer.map(|buf| {
353 // Determine the size of the static buffer we have
354 static_buffer_len = buf.len();
355
356 if static_buffer_len > compare.len() {
357 static_buffer_len = compare.len()
358 }
359
360 self.data_copied.set(static_buffer_len);
361
362 // Copy the data into the static buffer
363 compare[..static_buffer_len]
364 .copy_to_slice(&mut buf[..static_buffer_len]);
365 });
366 })
367 });
368
369 if let Err(e) = self.verify_digest() {
370 kernel_data
371 .schedule_upcall(1, (into_statuscode(e.into()), 0, 0))
372 .ok();
373 }
374 } else {
375 kernel_data.schedule_upcall(0, (0, 0, 0)).ok();
376 }
377 })
378 .map_err(|err| {
379 if err == kernel::process::Error::NoSuchApp
380 || err == kernel::process::Error::InactiveApp
381 {
382 self.processid.clear();
383 }
384 })
385 });
386
387 self.check_queue();
388 }
389}
390
391impl<
392 'a,
393 H: digest::Digest<'a, DIGEST_LEN>
394 + digest::HmacSha256
395 + digest::HmacSha384
396 + digest::HmacSha512,
397 const DIGEST_LEN: usize,
398 > digest::ClientHash<DIGEST_LEN> for HmacDriver<'a, H, DIGEST_LEN>
399{
400 fn hash_done(&self, result: Result<(), ErrorCode>, digest: &'static mut [u8; DIGEST_LEN]) {
401 self.processid.map(|id| {
402 self.apps
403 .enter(id, |_, kernel_data| {
404 self.hmac.clear_data();
405
406 let pointer = digest[0] as *mut u8;
407
408 let _ = kernel_data
409 .get_readwrite_processbuffer(rw_allow::DEST)
410 .and_then(|dest| {
411 dest.mut_enter(|dest| {
412 let len = dest.len();
413
414 if len < DIGEST_LEN {
415 dest.copy_from_slice(&digest[0..len]);
416 } else {
417 dest[0..DIGEST_LEN].copy_from_slice(digest);
418 }
419 })
420 });
421
422 match result {
423 Ok(()) => kernel_data.schedule_upcall(0, (0, pointer as usize, 0)),
424 Err(e) => kernel_data
425 .schedule_upcall(0, (into_statuscode(e.into()), pointer as usize, 0)),
426 }
427 .ok();
428
429 // Clear the current processid as it has finished running
430 self.processid.clear();
431 })
432 .map_err(|err| {
433 if err == kernel::process::Error::NoSuchApp
434 || err == kernel::process::Error::InactiveApp
435 {
436 self.processid.clear();
437 }
438 })
439 });
440
441 self.check_queue();
442 self.dest_buffer.replace(digest);
443 }
444}
445
446impl<
447 'a,
448 H: digest::Digest<'a, DIGEST_LEN>
449 + digest::HmacSha256
450 + digest::HmacSha384
451 + digest::HmacSha512,
452 const DIGEST_LEN: usize,
453 > digest::ClientVerify<DIGEST_LEN> for HmacDriver<'a, H, DIGEST_LEN>
454{
455 fn verification_done(
456 &self,
457 result: Result<bool, ErrorCode>,
458 compare: &'static mut [u8; DIGEST_LEN],
459 ) {
460 self.processid.map(|id| {
461 self.apps
462 .enter(id, |_app, kernel_data| {
463 self.hmac.clear_data();
464
465 match result {
466 Ok(equal) => kernel_data.schedule_upcall(1, (0, equal as usize, 0)),
467 Err(e) => kernel_data.schedule_upcall(1, (into_statuscode(e.into()), 0, 0)),
468 }
469 .ok();
470
471 // Clear the current processid as it has finished running
472 self.processid.clear();
473 })
474 .map_err(|err| {
475 if err == kernel::process::Error::NoSuchApp
476 || err == kernel::process::Error::InactiveApp
477 {
478 self.processid.clear();
479 }
480 })
481 });
482
483 self.check_queue();
484 self.dest_buffer.replace(compare);
485 }
486}
487
488/// Specify memory regions to be used.
489///
490/// ### `allow_num`
491///
492/// - `0`: Allow a buffer for storing the key. The kernel will read from this
493/// when running This should not be changed after running `run` until the HMAC
494/// has completed
495/// - `1`: Allow a buffer for storing the buffer. The kernel will read from this
496/// when running This should not be changed after running `run` until the HMAC
497/// has completed
498/// - `2`: Allow a buffer for storing the digest. The kernel will fill this with
499/// the HMAC digest before calling the `hash_done` callback.
500impl<
501 'a,
502 H: digest::Digest<'a, DIGEST_LEN>
503 + digest::HmacSha256
504 + digest::HmacSha384
505 + digest::HmacSha512,
506 const DIGEST_LEN: usize,
507 > SyscallDriver for HmacDriver<'a, H, DIGEST_LEN>
508{
509 // Subscribe to HmacDriver events.
510 //
511 // ### `subscribe_num`
512 //
513 // - `0`: Subscribe to interrupts from HMAC events. The callback signature
514 // is `fn(result: u32)`
515
516 /// Setup and run the HMAC hardware
517 ///
518 /// We expect userspace to setup buffers for the key, data and digest.
519 /// These buffers must be allocated and specified to the kernel from the
520 /// above allow calls.
521 ///
522 /// We expect userspace not to change the value while running. If userspace
523 /// changes the value we have no guarantee of what is passed to the
524 /// hardware. This isn't a security issue, it will just prove the requesting
525 /// app with invalid data.
526 ///
527 /// The driver will take care of clearing data from the underlying implementation
528 /// by calling the `clear_data()` function when the `hash_complete()` callback
529 /// is called or if an error is encountered.
530 ///
531 /// ### `command_num`
532 ///
533 /// - `0`: set_algorithm
534 /// - `1`: run
535 /// - `2`: update
536 /// - `3`: finish
537 fn command(
538 &self,
539 command_num: usize,
540 data1: usize,
541 _data2: usize,
542 processid: ProcessId,
543 ) -> CommandReturn {
544 let match_or_empty_or_nonexistant = self.processid.map_or(true, |owning_app| {
545 // We have recorded that an app has ownership of the HMAC.
546
547 // If the HMAC is still active, then we need to wait for the operation
548 // to finish and the app, whether it exists or not (it may have crashed),
549 // still owns this capsule. If the HMAC is not active, then
550 // we need to verify that that application still exists, and remove
551 // it as owner if not.
552 if self.active.get() {
553 owning_app == processid
554 } else {
555 // Check the app still exists.
556 //
557 // If the `.enter()` succeeds, then the app is still valid, and
558 // we can check if the owning app matches the one that called
559 // the command. If the `.enter()` fails, then the owning app no
560 // longer exists and we return `true` to signify the
561 // "or_nonexistant" case.
562 self.apps
563 .enter(owning_app, |_, _| owning_app == processid)
564 .unwrap_or(true)
565 }
566 });
567
568 let app_match = self.processid.map_or(false, |owning_app| {
569 // We have recorded that an app has ownership of the HMAC.
570
571 // If the HMAC is still active, then we need to wait for the operation
572 // to finish and the app, whether it exists or not (it may have crashed),
573 // still owns this capsule. If the HMAC is not active, then
574 // we need to verify that that application still exists, and remove
575 // it as owner if not.
576 if self.active.get() {
577 owning_app == processid
578 } else {
579 // Check the app still exists.
580 //
581 // If the `.enter()` succeeds, then the app is still valid, and
582 // we can check if the owning app matches the one that called
583 // the command. If the `.enter()` fails, then the owning app no
584 // longer exists and we return `true` to signify the
585 // "or_nonexistant" case.
586 self.apps
587 .enter(owning_app, |_, _| owning_app == processid)
588 .unwrap_or(true)
589 }
590 });
591
592 // Try the commands where we want to start an operation *not* entered in
593 // an app grant first.
594 if match_or_empty_or_nonexistant
595 && (command_num == 1 || command_num == 2 || command_num == 4)
596 {
597 self.processid.set(processid);
598
599 let _ = self.apps.enter(processid, |app, _| {
600 if command_num == 1 {
601 // run
602 // Use key and data to compute hash
603 // This will trigger a callback once the digest is generated
604 app.op.set(Some(UserSpaceOp::Run));
605 } else if command_num == 2 {
606 // update
607 // Input key and data, don't compute final hash yet
608 // This will trigger a callback once the data has been added.
609 app.op.set(Some(UserSpaceOp::Update));
610 } else if command_num == 4 {
611 // verify
612 // Use key and data to compute hash and comapre it against
613 // the digest
614 app.op.set(Some(UserSpaceOp::Verify));
615 }
616 });
617
618 return if let Err(e) = self.run() {
619 self.hmac.clear_data();
620 self.processid.clear();
621 self.check_queue();
622 CommandReturn::failure(e)
623 } else {
624 CommandReturn::success()
625 };
626 }
627
628 self.apps
629 .enter(processid, |app, kernel_data| {
630 match command_num {
631 // set_algorithm
632 0 => {
633 match data1 {
634 // SHA256
635 0 => {
636 app.sha_operation = Some(ShaOperation::Sha256);
637 CommandReturn::success()
638 }
639 // SHA384
640 1 => {
641 app.sha_operation = Some(ShaOperation::Sha384);
642 CommandReturn::success()
643 }
644 // SHA512
645 2 => {
646 app.sha_operation = Some(ShaOperation::Sha512);
647 CommandReturn::success()
648 }
649 _ => CommandReturn::failure(ErrorCode::NOSUPPORT),
650 }
651 }
652
653 // run
654 1 => {
655 // There is an active app, so queue this request (if possible).
656 if app.pending_run_app.is_some() {
657 // No more room in the queue, nowhere to store this
658 // request.
659 CommandReturn::failure(ErrorCode::NOMEM)
660 } else {
661 // We can store this, so lets do it.
662 app.pending_run_app = Some(processid);
663 app.op.set(Some(UserSpaceOp::Run));
664 CommandReturn::success()
665 }
666 }
667
668 // update
669 2 => {
670 // There is an active app, so queue this request (if possible).
671 if app.pending_run_app.is_some() {
672 // No more room in the queue, nowhere to store this
673 // request.
674 CommandReturn::failure(ErrorCode::NOMEM)
675 } else {
676 // We can store this, so lets do it.
677 app.pending_run_app = Some(processid);
678 app.op.set(Some(UserSpaceOp::Update));
679 CommandReturn::success()
680 }
681 }
682
683 // finish
684 // Compute final hash yet, useful after a update command
685 3 => {
686 if app_match {
687 if let Err(e) = self.calculate_digest() {
688 kernel_data
689 .schedule_upcall(
690 0,
691 (kernel::errorcode::into_statuscode(e.into()), 0, 0),
692 )
693 .ok();
694 }
695 CommandReturn::success()
696 } else {
697 // We don't queue this request, the user has to call
698 // `update` first.
699 CommandReturn::failure(ErrorCode::OFF)
700 }
701 }
702
703 // verify
704 4 => {
705 // There is an active app, so queue this request (if possible).
706 if app.pending_run_app.is_some() {
707 // No more room in the queue, nowhere to store this
708 // request.
709 CommandReturn::failure(ErrorCode::NOMEM)
710 } else {
711 // We can store this, so lets do it.
712 app.pending_run_app = Some(processid);
713 app.op.set(Some(UserSpaceOp::Verify));
714 CommandReturn::success()
715 }
716 }
717
718 // verify_finish
719 // Use key and data to compute hash and compare it against
720 // the digest, useful after a update command
721 5 => {
722 if app_match {
723 let _ = kernel_data
724 .get_readonly_processbuffer(ro_allow::COMPARE)
725 .and_then(|compare| {
726 compare.enter(|compare| {
727 let mut static_buffer_len = 0;
728 self.dest_buffer.map(|buf| {
729 // Determine the size of the static buffer we have
730 static_buffer_len = buf.len();
731
732 if static_buffer_len > compare.len() {
733 static_buffer_len = compare.len()
734 }
735
736 self.data_copied.set(static_buffer_len);
737
738 // Copy the data into the static buffer
739 compare[..static_buffer_len]
740 .copy_to_slice(&mut buf[..static_buffer_len]);
741 });
742 })
743 });
744
745 if let Err(e) = self.verify_digest() {
746 kernel_data
747 .schedule_upcall(1, (into_statuscode(e.into()), 0, 0))
748 .ok();
749 }
750 CommandReturn::success()
751 } else {
752 // We don't queue this request, the user has to call
753 // `update` first.
754 CommandReturn::failure(ErrorCode::OFF)
755 }
756 }
757
758 // default
759 _ => CommandReturn::failure(ErrorCode::NOSUPPORT),
760 }
761 })
762 .unwrap_or_else(|err| err.into())
763 }
764
765 fn allocate_grant(&self, processid: ProcessId) -> Result<(), kernel::process::Error> {
766 self.apps.enter(processid, |_, _| {})
767 }
768}
769
770#[derive(Copy, Clone, PartialEq)]
771enum UserSpaceOp {
772 Run,
773 Update,
774 Verify,
775}
776
777#[derive(Default)]
778pub struct App {
779 pending_run_app: Option<ProcessId>,
780 sha_operation: Option<ShaOperation>,
781 op: Cell<Option<UserSpaceOp>>,
782}