diff --git a/Cargo.toml b/Cargo.toml index 76791d69..39a2f9eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -19,6 +19,7 @@ libtock_leds = { path = "apis/leds" } libtock_low_level_debug = { path = "apis/low_level_debug" } libtock_platform = { path = "platform" } libtock_proximity = { path = "apis/proximity" } +libtock_rng = { path = "apis/rng" } libtock_runtime = { path = "runtime" } libtock_temperature = { path = "apis/temperature" } @@ -42,6 +43,7 @@ members = [ "apis/leds", "apis/low_level_debug", "apis/proximity", + "apis/rng", "apis/temperature", "panic_handlers/debug_panic", "panic_handlers/small_panic", diff --git a/apis/rng/Cargo.toml b/apis/rng/Cargo.toml new file mode 100644 index 00000000..37dc65bb --- /dev/null +++ b/apis/rng/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "libtock_rng" +version = "0.1.0" +authors = ["Tock Project Developers "] +license = "MIT/Apache-2.0" +edition = "2021" +repository = "https://www.github.com/tock/libtock-rs" +description = "libtock rng driver" + +[dependencies] +libtock_platform = { path = "../../platform" } + +[dev-dependencies] +libtock_unittest = { path = "../../unittest" } diff --git a/apis/rng/src/lib.rs b/apis/rng/src/lib.rs new file mode 100644 index 00000000..d94a02c7 --- /dev/null +++ b/apis/rng/src/lib.rs @@ -0,0 +1,88 @@ +#![no_std] + +use core::cell::Cell; + +use libtock_platform::{share, AllowRw, DefaultConfig, ErrorCode, Subscribe, Syscalls}; +pub struct Rng(S); + +impl Rng { + /// Returns Ok() if the driver was present.This does not necessarily mean + /// that the driver is working. + pub fn exists() -> Result<(), ErrorCode> { + S::command(DRIVER_NUM, EXISTS, 0, 0).to_result() + } + + /// Register a listener to be called when the random generation is finished + pub fn register_listener<'share>( + listener: &'share Cell>, + subscribe: share::Handle>, + ) -> Result<(), ErrorCode> { + S::subscribe::<_, _, DefaultConfig, DRIVER_NUM, 0>(subscribe, listener) + } + + /// Sets the buffer in which the random numbers will be written + pub fn set_buffer<'share>( + buffer: &'share mut [u8], + allow_rw: share::Handle>, + ) -> Result<(), ErrorCode> { + S::allow_rw::(allow_rw, buffer) + } + + /// Initiates an async random generation for n bytes + /// A buffer and a callback should have been set before + pub fn get_random(n: u32) -> Result<(), ErrorCode> { + S::command(DRIVER_NUM, ASK_FOR_RANDOM_BYTES, n, 0).to_result() + } + + /// Initiates a synchronous random number generation + /// `n` random bytes will be written in `buf` + /// n must be smaller or equal to buf.len() + /// returns the number of bytes successfully written or error + pub fn get_random_sync(buf: &mut [u8], n: u32) -> Result { + if n > (buf.len() as u32) { + return Err(ErrorCode::Size); + } + + let listener: Cell> = Cell::new(None); + share::scope::< + ( + AllowRw<_, DRIVER_NUM, RW_ALLOW>, + Subscribe<_, DRIVER_NUM, 0>, + ), + _, + _, + >(|handle| { + let (allow_rw, subscribe) = handle.split(); + + Self::set_buffer(buf, allow_rw)?; + Self::register_listener(&listener, subscribe)?; + Self::get_random(n)?; + while listener.get() == None { + S::yield_wait(); + } + + Ok(()) + })?; + + match listener.get() { + Some((_, bytes_received)) => Ok(bytes_received), + None => Err(ErrorCode::Fail), + } + } +} + +#[cfg(test)] +mod tests; + +// ----------------------------------------------------------------------------- +// Driver number and command IDs +// ----------------------------------------------------------------------------- + +const DRIVER_NUM: u32 = 0x40001; + +// Command IDs + +const EXISTS: u32 = 0; +const ASK_FOR_RANDOM_BYTES: u32 = 1; + +const RW_ALLOW: u32 = 0; diff --git a/apis/rng/src/tests.rs b/apis/rng/src/tests.rs new file mode 100644 index 00000000..57fef66b --- /dev/null +++ b/apis/rng/src/tests.rs @@ -0,0 +1,80 @@ +use super::*; +use core::cell::Cell; +use libtock_platform::{share, AllowRw, ErrorCode, Subscribe, YieldNoWaitReturn}; +use libtock_unittest::fake; + +type Rng = super::Rng; + +#[test] +fn no_driver() { + let _kernel = fake::Kernel::new(); + assert_eq!(Rng::exists(), Err(ErrorCode::NoDevice)); +} + +#[test] +fn driver_check() { + let kernel = fake::Kernel::new(); + let driver = fake::Rng::new(); + kernel.add_driver(&driver); + + assert_eq!(Rng::exists(), Ok(())); +} + +#[test] +fn get_random() { + let kernel = fake::Kernel::new(); + let driver = fake::Rng::new(); + kernel.add_driver(&driver); + + let mut buf: [u8; 10] = [0; 10]; + let listener: Cell> = Cell::new(None); + + share::scope::< + ( + AllowRw<_, DRIVER_NUM, RW_ALLOW>, + Subscribe<_, DRIVER_NUM, 0>, + ), + _, + _, + >( + |handle: share::Handle<( + AllowRw, + Subscribe, + )>| { + let (allow_rw, subscribe) = handle.split(); + + assert!(Rng::set_buffer(&mut buf, allow_rw).is_ok()); + assert!(Rng::register_listener(&listener, subscribe).is_ok()); + + assert!(Rng::get_random(5).is_ok()); + + driver.add_bytes(&[1, 2, 3]); + + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); + + driver.add_bytes(&[4, 5, 6]); + + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); + + assert_eq!(listener.get(), Some((0, 5))); + }, + ); + assert!(buf[..5].eq(&[1, 2, 3, 4, 5])); +} +#[test] +fn get_sync() { + let kernel = fake::Kernel::new(); + let driver = fake::Rng::new(); + kernel.add_driver(&driver); + + driver.add_bytes_sync(&[10, 20, 30, 40, 50]); + + let mut buf: [u8; 10] = [0; 10]; + + assert_eq!(Rng::get_random_sync(&mut buf, 5), Ok(5)); + assert!(buf[..5].eq(&[10, 20, 30, 40, 50])); + + driver.add_bytes_sync(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]); + + assert_eq!(Rng::get_random_sync(&mut buf, 11), Err(ErrorCode::Size)); +} diff --git a/examples/rng.rs b/examples/rng.rs new file mode 100644 index 00000000..99944b16 --- /dev/null +++ b/examples/rng.rs @@ -0,0 +1,48 @@ +//! A simple libtock-rs example. Checks for random number generator driver +//! asks for some random numbers and prints them. + +#![no_main] +#![no_std] + +use core::fmt::Write; +use libtock::console::Console; +use libtock::rng::Rng; +use libtock::runtime::{set_main, stack_size}; +set_main! {main} +stack_size! {0x200} + +fn main() { + if Rng::exists().is_err() { + writeln!( + Console::writer(), + "Random number generator driver unavailable" + ) + .unwrap(); + return; + } + + writeln!( + Console::writer(), + "Random number generator driver available" + ) + .unwrap(); + + let mut buf: [u8; 10] = [0; 10]; + + match Rng::get_random_sync(&mut buf[..], 10) { + Ok(bytes_received) => { + writeln!( + Console::writer(), + "Received {} random bytes. Buf:", + bytes_received, + ) + .unwrap(); + for byte in buf.iter() { + writeln!(Console::writer(), "{} ", byte).unwrap(); + } + } + Err(_) => { + writeln!(Console::writer(), "FAIL").unwrap(); + } + } +} diff --git a/src/lib.rs b/src/lib.rs index fda15c86..5b6ed435 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -42,6 +42,10 @@ pub mod proximity { use libtock_proximity as proximity; pub type Proximity = proximity::Proximity; } +pub mod rng { + use libtock_rng as rng; + pub type Rng = rng::Rng; +} pub mod temperature { use libtock_temperature as temperature; pub type Temperature = temperature::Temperature; diff --git a/unittest/src/fake/mod.rs b/unittest/src/fake/mod.rs index 61e0141a..2e196614 100644 --- a/unittest/src/fake/mod.rs +++ b/unittest/src/fake/mod.rs @@ -17,6 +17,7 @@ mod kernel; mod leds; mod low_level_debug; mod proximity; +mod rng; mod syscall_driver; mod syscalls; mod temperature; @@ -29,6 +30,7 @@ pub use kernel::Kernel; pub use leds::Leds; pub use low_level_debug::{LowLevelDebug, Message}; pub use proximity::Proximity; +pub use rng::Rng; pub use syscall_driver::SyscallDriver; pub use syscalls::Syscalls; pub use temperature::Temperature; diff --git a/unittest/src/fake/rng/mod.rs b/unittest/src/fake/rng/mod.rs new file mode 100644 index 00000000..6f1057c9 --- /dev/null +++ b/unittest/src/fake/rng/mod.rs @@ -0,0 +1,119 @@ +use std::cell::{Cell, RefCell}; + +use crate::{DriverInfo, DriverShareRef, RwAllowBuffer}; +use core::cmp; +use libtock_platform::ErrorCode; + +pub struct Rng { + buffer: RefCell, + remaining: Cell, + idx: Cell, + getting_randomness: Cell, + share_ref: DriverShareRef, + random_numbers: Cell>>, +} + +impl Rng { + pub fn new() -> std::rc::Rc { + std::rc::Rc::new(Rng { + buffer: Default::default(), + remaining: Cell::new(0), + idx: Cell::new(0), + getting_randomness: Cell::new(false), + share_ref: Default::default(), + random_numbers: Cell::new(None), + }) + } + + pub fn add_bytes(&self, buf: &[u8]) { + if !self.getting_randomness.get() { + return; + } + + // this would happen only if the buffer was changed + if self.idx.get() > self.buffer.borrow().len() { + self.idx.set(0); + self.remaining.set(0); + } else { + if self.idx.get() + self.remaining.get() > self.buffer.borrow().len() { + self.remaining + .set(self.buffer.borrow().len() - self.idx.get()) + } + + let bytes_to_add = cmp::min(self.remaining.get(), buf.len()); + self.buffer.borrow_mut()[self.idx.get()..self.idx.get() + bytes_to_add] + .copy_from_slice(&buf[..bytes_to_add]); + self.remaining.set(self.remaining.get() - bytes_to_add); + self.idx.set(self.idx.get() + bytes_to_add); + } + + if self.remaining.get() == 0 { + self.share_ref + .schedule_upcall(0, (0, self.idx.get() as u32, 0)) + .expect("Unable to schedule upcall"); + self.getting_randomness.set(false); + self.random_numbers.set(None); + } + } + + pub fn add_bytes_sync(&self, buf: &[u8]) { + self.random_numbers.set(Some(Vec::from(buf))) + } +} + +impl crate::fake::SyscallDriver for Rng { + fn info(&self) -> DriverInfo { + DriverInfo::new(DRIVER_NUM).upcall_count(1) + } + + fn register(&self, share_ref: DriverShareRef) { + self.share_ref.replace(share_ref); + } + + fn allow_readwrite( + &self, + buffer_num: u32, + buffer: RwAllowBuffer, + ) -> Result { + if buffer_num == RW_ALLOW { + Ok(self.buffer.replace(buffer)) + } else { + Err((buffer, ErrorCode::Invalid)) + } + } + + fn command(&self, command_id: u32, data: u32, _: u32) -> libtock_platform::CommandReturn { + match command_id { + EXISTS => crate::command_return::success(), + + ASK_FOR_RANDOM_BYTES => { + self.remaining.set(data as usize); + self.idx.set(0); + self.getting_randomness.set(true); + + if let Some(numbers) = self.random_numbers.take() { + self.add_bytes(&numbers); + } + + crate::command_return::success() + } + _ => crate::command_return::failure(ErrorCode::NoSupport), + } + } +} + +#[cfg(test)] +mod tests; + +// ----------------------------------------------------------------------------- +// Driver number and command IDs +// ----------------------------------------------------------------------------- + +const DRIVER_NUM: u32 = 0x40001; + +// Command IDs + +const EXISTS: u32 = 0; +const ASK_FOR_RANDOM_BYTES: u32 = 1; + +const RW_ALLOW: u32 = 0; diff --git a/unittest/src/fake/rng/tests.rs b/unittest/src/fake/rng/tests.rs new file mode 100644 index 00000000..8e11f94b --- /dev/null +++ b/unittest/src/fake/rng/tests.rs @@ -0,0 +1,81 @@ +use crate::fake::{self, SyscallDriver}; +use fake::rng::*; +use libtock_platform::{ + share::{self}, + AllowRw, DefaultConfig, Subscribe, YieldNoWaitReturn, +}; + +//Test the command implementation +#[test] +fn command() { + let rng = Rng::new(); + assert!(rng.command(EXISTS, 0, 0).is_success()); + assert!(rng.command(ASK_FOR_RANDOM_BYTES, 7, 0).is_success()); + assert!(rng.command(ASK_FOR_RANDOM_BYTES, 5, 0).is_success()); + + assert!(rng + .allow_readwrite(RW_ALLOW, RwAllowBuffer::default()) + .is_ok()); + assert!(rng.allow_readwrite(1, RwAllowBuffer::default()).is_err()); +} + +// Integration test that verifies Rng works with fake::Kernel and +// libtock_platform::Syscalls. +#[test] +fn kernel_integration() { + use libtock_platform::Syscalls; + let kernel = fake::Kernel::new(); + let rng = Rng::new(); + kernel.add_driver(&rng); + assert!(fake::Syscalls::command(DRIVER_NUM, EXISTS, 1, 2).is_success()); + let mut buf: [u8; 10] = [0; 10]; + let mut buf_sync: [u8; 3] = [0; 3]; + let listener: Cell> = Cell::new(None); + share::scope::< + ( + AllowRw<_, DRIVER_NUM, RW_ALLOW>, + Subscribe<_, DRIVER_NUM, 0>, + ), + _, + _, + >(|handle| { + let (allow_rw, subscribe) = handle.split(); + assert!( + fake::Syscalls::allow_rw::(allow_rw, &mut buf) + .is_ok() + ); + assert!( + fake::Syscalls::subscribe::<_, _, DefaultConfig, DRIVER_NUM, 0>(subscribe, &listener) + .is_ok() + ); + + assert!(fake::Syscalls::command(DRIVER_NUM, ASK_FOR_RANDOM_BYTES, 5, 0).is_success()); + + rng.add_bytes(&[1, 2, 3]); + + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::NoUpcall); + + rng.add_bytes(&[4, 5, 6]); + + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); + + assert_eq!(listener.get(), Some((0, 5))); + + assert!( + fake::Syscalls::allow_rw::( + allow_rw, + &mut buf_sync + ) + .is_ok() + ); + rng.add_bytes_sync(&[6, 7, 8, 9]); + assert!(fake::Syscalls::command(DRIVER_NUM, ASK_FOR_RANDOM_BYTES, 5, 0).is_success()); + assert_eq!(fake::Syscalls::yield_no_wait(), YieldNoWaitReturn::Upcall); + + //size of the buffer is smaller than number of random bytes requested + assert_eq!(listener.get(), Some((0, 3))); + }); + + assert!(buf[..5].eq(&[1, 2, 3, 4, 5])); + assert!(buf_sync.eq(&[6, 7, 8])); +}