Tock V2 port - rebased and updated (#620)
* Changes from #580 * fixes USB cancel panic * style fixes * Update src/env/tock/storage.rs Co-authored-by: Zach Halvorsen <zhalvorsen@google.com> --------- Co-authored-by: Zach Halvorsen <zhalvorsen@google.com>
This commit is contained in:
26
src/env/tock/buffer_upgrade_storage.rs
vendored
26
src/env/tock/buffer_upgrade_storage.rs
vendored
@@ -14,20 +14,35 @@
|
||||
|
||||
use super::storage_helper::ModRange;
|
||||
use alloc::boxed::Box;
|
||||
use core::marker::PhantomData;
|
||||
use libtock_platform as platform;
|
||||
use libtock_platform::Syscalls;
|
||||
use persistent_store::{StorageError, StorageResult};
|
||||
use platform::DefaultConfig;
|
||||
|
||||
const PARTITION_LENGTH: usize = 0x41000;
|
||||
const METADATA_LENGTH: usize = 0x1000;
|
||||
|
||||
pub struct BufferUpgradeStorage {
|
||||
pub struct BufferUpgradeStorage<
|
||||
S: Syscalls,
|
||||
C: platform::subscribe::Config + platform::allow_ro::Config = DefaultConfig,
|
||||
> {
|
||||
/// Content of the partition storage.
|
||||
partition: Box<[u8]>,
|
||||
s: PhantomData<S>,
|
||||
c: PhantomData<C>,
|
||||
}
|
||||
|
||||
impl BufferUpgradeStorage {
|
||||
pub fn new() -> StorageResult<BufferUpgradeStorage> {
|
||||
impl<S, C> BufferUpgradeStorage<S, C>
|
||||
where
|
||||
S: Syscalls,
|
||||
C: platform::subscribe::Config + platform::allow_ro::Config,
|
||||
{
|
||||
pub fn new() -> StorageResult<Self> {
|
||||
Ok(BufferUpgradeStorage {
|
||||
partition: vec![0xff; PARTITION_LENGTH].into_boxed_slice(),
|
||||
s: PhantomData,
|
||||
c: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -72,10 +87,11 @@ impl BufferUpgradeStorage {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use libtock_unittest::fake::Syscalls;
|
||||
|
||||
#[test]
|
||||
fn read_write_bundle() {
|
||||
let mut storage = BufferUpgradeStorage::new().unwrap();
|
||||
let mut storage = BufferUpgradeStorage::<Syscalls>::new().unwrap();
|
||||
assert_eq!(storage.read_partition(0, 2).unwrap(), &[0xFF, 0xFF]);
|
||||
assert!(storage.write_bundle(1, vec![0x88, 0x88]).is_ok());
|
||||
assert_eq!(storage.read_partition(0, 2).unwrap(), &[0xFF, 0x88]);
|
||||
@@ -108,7 +124,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn partition_slice() {
|
||||
let storage = BufferUpgradeStorage::new().unwrap();
|
||||
let storage = BufferUpgradeStorage::<Syscalls>::new().unwrap();
|
||||
assert_eq!(storage.bundle_identifier(), 0x60000);
|
||||
}
|
||||
}
|
||||
|
||||
32
src/env/tock/clock.rs
vendored
32
src/env/tock/clock.rs
vendored
@@ -12,19 +12,21 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use core::marker::PhantomData;
|
||||
use libtock_drivers::timer::{get_clock_frequency, get_ticks};
|
||||
use libtock_platform::Syscalls;
|
||||
use opensk::api::clock::Clock;
|
||||
|
||||
/// 56-bits timestamp (valid for 70k+ years)
|
||||
#[derive(Clone, Copy, Debug, Default, PartialOrd, Ord, PartialEq, Eq)]
|
||||
struct Timestamp {
|
||||
epoch: usize, // 32-bits
|
||||
tick: usize, // 24-bits (32kHz)
|
||||
epoch: u32,
|
||||
tick: u32, // 24-bits (32kHz)
|
||||
}
|
||||
|
||||
impl Timestamp {
|
||||
/// Adds (potentially more than 24 bit of) ticks to this timestamp.
|
||||
pub fn add_ticks(&mut self, ticks: usize) {
|
||||
pub fn add_ticks(&mut self, ticks: u32) {
|
||||
// Saturating should never happen, but it fails gracefully.
|
||||
let sum = self.tick.saturating_add(ticks);
|
||||
self.epoch += sum >> 24;
|
||||
@@ -42,17 +44,26 @@ pub struct TockTimer {
|
||||
/// To guarantee correctness, you have to call any of its functions at least once per full tick
|
||||
/// counter wrap. In our case, 24 bit ticks with a 32 kHz frequency wrap after 512 seconds. If you
|
||||
/// can't guarantee to regularly create or check timers, call tickle at least every 8 minutes.
|
||||
#[derive(Default)]
|
||||
pub struct TockClock {
|
||||
pub struct TockClock<S: Syscalls> {
|
||||
now: Timestamp,
|
||||
s: PhantomData<S>,
|
||||
}
|
||||
|
||||
impl TockClock {
|
||||
impl<S: Syscalls> Default for TockClock<S> {
|
||||
fn default() -> Self {
|
||||
TockClock {
|
||||
now: Timestamp::default(),
|
||||
s: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: Syscalls> TockClock<S> {
|
||||
/// Elapses timers before the clock wraps.
|
||||
///
|
||||
/// Call this regularly to timeout reliably despite wrapping clock ticks.
|
||||
pub fn tickle(&mut self) {
|
||||
let cur_tick = get_ticks().ok().unwrap();
|
||||
let cur_tick = get_ticks::<S>().ok().unwrap();
|
||||
if cur_tick < self.now.tick {
|
||||
self.now.epoch += 1;
|
||||
}
|
||||
@@ -60,12 +71,13 @@ impl TockClock {
|
||||
}
|
||||
}
|
||||
|
||||
impl Clock for TockClock {
|
||||
impl<S: Syscalls> Clock for TockClock<S> {
|
||||
type Timer = TockTimer;
|
||||
|
||||
fn make_timer(&mut self, milliseconds: usize) -> Self::Timer {
|
||||
let milliseconds = milliseconds as u32;
|
||||
self.tickle();
|
||||
let clock_frequency = get_clock_frequency().ok().unwrap();
|
||||
let clock_frequency = get_clock_frequency::<S>().ok().unwrap();
|
||||
let delta_tick = match milliseconds.checked_mul(clock_frequency) {
|
||||
Some(x) => x / 1000,
|
||||
// All CTAP timeouts are multiples of 100 so far. Worst case we timeout too early.
|
||||
@@ -83,7 +95,7 @@ impl Clock for TockClock {
|
||||
|
||||
#[cfg(feature = "debug_ctap")]
|
||||
fn timestamp_us(&mut self) -> usize {
|
||||
let clock_frequency = get_clock_frequency().ok().unwrap();
|
||||
let clock_frequency = get_clock_frequency::<S>().ok().unwrap();
|
||||
let total_ticks = 0x100_0000u64 * self.now.epoch as u64 + self.now.tick as u64;
|
||||
(total_ticks.wrapping_mul(1_000_000u64) / clock_frequency as u64) as usize
|
||||
}
|
||||
|
||||
69
src/env/tock/commands.rs
vendored
69
src/env/tock/commands.rs
vendored
@@ -17,6 +17,7 @@ use alloc::vec;
|
||||
use alloc::vec::Vec;
|
||||
use arrayref::array_ref;
|
||||
use core::convert::TryFrom;
|
||||
use libtock_platform::Syscalls;
|
||||
use opensk::api::attestation_store::{self, Attestation, AttestationStore};
|
||||
use opensk::api::crypto::sha256::Sha256;
|
||||
use opensk::api::crypto::EC_FIELD_SIZE;
|
||||
@@ -31,15 +32,18 @@ use opensk::ctap::secret::Secret;
|
||||
use opensk::ctap::status_code::Ctap2StatusCode;
|
||||
use opensk::ctap::{cbor_read, cbor_write, Channel};
|
||||
use opensk::env::{Env, Sha};
|
||||
use sk_cbor as cbor;
|
||||
use sk_cbor::{cbor_map_options, destructure_cbor_map};
|
||||
use {libtock_platform as platform, sk_cbor as cbor};
|
||||
|
||||
const VENDOR_COMMAND_CONFIGURE: u8 = 0x40;
|
||||
const VENDOR_COMMAND_UPGRADE: u8 = 0x42;
|
||||
const VENDOR_COMMAND_UPGRADE_INFO: u8 = 0x43;
|
||||
|
||||
pub fn process_vendor_command(
|
||||
env: &mut TockEnv,
|
||||
pub fn process_vendor_command<
|
||||
S: Syscalls,
|
||||
C: platform::subscribe::Config + platform::allow_ro::Config,
|
||||
>(
|
||||
env: &mut TockEnv<S, C>,
|
||||
bytes: &[u8],
|
||||
channel: Channel,
|
||||
) -> Option<Vec<u8>> {
|
||||
@@ -50,8 +54,8 @@ pub fn process_vendor_command(
|
||||
process_cbor(env, bytes, channel).unwrap_or_else(|e| Some(vec![e as u8]))
|
||||
}
|
||||
|
||||
fn process_cbor(
|
||||
env: &mut TockEnv,
|
||||
fn process_cbor<S: Syscalls, C: platform::subscribe::Config + platform::allow_ro::Config>(
|
||||
env: &mut TockEnv<S, C>,
|
||||
bytes: &[u8],
|
||||
channel: Channel,
|
||||
) -> Result<Option<Vec<u8>>, Ctap2StatusCode> {
|
||||
@@ -85,8 +89,11 @@ fn encode_cbor(value: cbor::Value) -> Vec<u8> {
|
||||
}
|
||||
}
|
||||
|
||||
fn process_vendor_configure(
|
||||
env: &mut TockEnv,
|
||||
fn process_vendor_configure<
|
||||
S: Syscalls,
|
||||
C: platform::subscribe::Config + platform::allow_ro::Config,
|
||||
>(
|
||||
env: &mut TockEnv<S, C>,
|
||||
params: VendorConfigureParameters,
|
||||
// Unused in std only
|
||||
_channel: Channel,
|
||||
@@ -141,12 +148,15 @@ fn process_vendor_configure(
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
fn process_vendor_upgrade(
|
||||
env: &mut TockEnv,
|
||||
fn process_vendor_upgrade<
|
||||
S: Syscalls,
|
||||
C: platform::subscribe::Config + platform::allow_ro::Config,
|
||||
>(
|
||||
env: &mut TockEnv<S, C>,
|
||||
params: VendorUpgradeParameters,
|
||||
) -> Result<(), Ctap2StatusCode> {
|
||||
let VendorUpgradeParameters { offset, data, hash } = params;
|
||||
let calculated_hash = Sha::<TockEnv>::digest(&data);
|
||||
let calculated_hash = Sha::<TockEnv<S>>::digest(&data);
|
||||
if hash != calculated_hash {
|
||||
return Err(Ctap2StatusCode::CTAP2_ERR_INTEGRITY_FAILURE);
|
||||
}
|
||||
@@ -156,8 +166,11 @@ fn process_vendor_upgrade(
|
||||
.map_err(|_| Ctap2StatusCode::CTAP1_ERR_INVALID_PARAMETER)
|
||||
}
|
||||
|
||||
fn process_vendor_upgrade_info(
|
||||
env: &mut TockEnv,
|
||||
fn process_vendor_upgrade_info<
|
||||
S: Syscalls,
|
||||
C: platform::subscribe::Config + platform::allow_ro::Config,
|
||||
>(
|
||||
env: &mut TockEnv<S, C>,
|
||||
) -> Result<VendorUpgradeInfoResponse, Ctap2StatusCode> {
|
||||
let upgrade_locations = env
|
||||
.upgrade_storage()
|
||||
@@ -288,6 +301,7 @@ impl From<VendorUpgradeInfoResponse> for cbor::Value {
|
||||
mod test {
|
||||
use super::*;
|
||||
use cbor::cbor_map;
|
||||
use libtock_unittest::fake::Syscalls;
|
||||
|
||||
const DUMMY_CHANNEL: Channel = Channel::MainHid([0x12, 0x34, 0x56, 0x78]);
|
||||
#[cfg(feature = "vendor_hid")]
|
||||
@@ -295,14 +309,14 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_process_cbor_unrelated_input() {
|
||||
let mut env = TockEnv::default();
|
||||
let mut env = TockEnv::<Syscalls>::default();
|
||||
let cbor_bytes = vec![0x01];
|
||||
assert_eq!(process_cbor(&mut env, &cbor_bytes, DUMMY_CHANNEL), Ok(None));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_process_cbor_invalid_input() {
|
||||
let mut env = TockEnv::default();
|
||||
let mut env = TockEnv::<Syscalls>::default();
|
||||
let cbor_bytes = vec![VENDOR_COMMAND_CONFIGURE];
|
||||
assert_eq!(
|
||||
process_cbor(&mut env, &cbor_bytes, DUMMY_CHANNEL),
|
||||
@@ -312,7 +326,7 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_process_cbor_valid_input() {
|
||||
let mut env = TockEnv::default();
|
||||
let mut env = TockEnv::<Syscalls>::default();
|
||||
let cbor_bytes = vec![VENDOR_COMMAND_UPGRADE_INFO];
|
||||
assert!(process_cbor(&mut env, &cbor_bytes, DUMMY_CHANNEL)
|
||||
.unwrap()
|
||||
@@ -322,7 +336,7 @@ mod test {
|
||||
#[test]
|
||||
#[cfg(feature = "vendor_hid")]
|
||||
fn test_process_command_valid_vendor_hid() {
|
||||
let mut env = TockEnv::default();
|
||||
let mut env = TockEnv::<Syscalls>::default();
|
||||
let cbor_bytes = vec![VENDOR_COMMAND_UPGRADE_INFO];
|
||||
assert!(process_cbor(&mut env, &cbor_bytes, VENDOR_CHANNEL)
|
||||
.unwrap()
|
||||
@@ -453,7 +467,7 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_deserialize_vendor_upgrade_info() {
|
||||
let mut env = TockEnv::default();
|
||||
let mut env = TockEnv::<Syscalls>::default();
|
||||
let cbor_bytes = [VENDOR_COMMAND_UPGRADE_INFO];
|
||||
assert!(process_cbor(&mut env, &cbor_bytes, DUMMY_CHANNEL)
|
||||
.unwrap()
|
||||
@@ -462,7 +476,7 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_vendor_configure() {
|
||||
let mut env = TockEnv::default();
|
||||
let mut env = TockEnv::<Syscalls>::default();
|
||||
|
||||
// Nothing should be configured at the beginning
|
||||
let response = process_vendor_configure(
|
||||
@@ -538,7 +552,7 @@ mod test {
|
||||
}))
|
||||
);
|
||||
|
||||
// Now try to lock the device
|
||||
// Now try to lock the device, but that is currently not supported.
|
||||
let response = process_vendor_configure(
|
||||
&mut env,
|
||||
VendorConfigureParameters {
|
||||
@@ -549,10 +563,7 @@ mod test {
|
||||
);
|
||||
assert_eq!(
|
||||
response,
|
||||
Ok(VendorConfigureResponse {
|
||||
cert_programmed: true,
|
||||
pkey_programmed: true,
|
||||
})
|
||||
Err(Ctap2StatusCode::CTAP2_ERR_VENDOR_INTERNAL_ERROR)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -561,13 +572,13 @@ mod test {
|
||||
// The test partition storage has size 0x40000.
|
||||
// The test metadata storage has size 0x1000.
|
||||
// The test identifier matches partition B.
|
||||
let mut env = TockEnv::default();
|
||||
let mut env = TockEnv::<Syscalls>::default();
|
||||
|
||||
const METADATA_LEN: usize = 0x1000;
|
||||
let metadata = vec![0xFF; METADATA_LEN];
|
||||
let metadata_hash = Sha::<TockEnv>::digest(&metadata);
|
||||
let metadata_hash = Sha::<TockEnv<Syscalls>>::digest(&metadata);
|
||||
let data = vec![0xFF; 0x1000];
|
||||
let hash = Sha::<TockEnv>::digest(&data);
|
||||
let hash = Sha::<TockEnv<Syscalls>>::digest(&data);
|
||||
|
||||
// Write to partition.
|
||||
let response = process_vendor_upgrade(
|
||||
@@ -638,11 +649,11 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_vendor_upgrade_no_second_partition() {
|
||||
let mut env = TockEnv::default();
|
||||
let mut env = TockEnv::<Syscalls>::default();
|
||||
env.disable_upgrade_storage();
|
||||
|
||||
let data = vec![0xFF; 0x1000];
|
||||
let hash = Sha::<TockEnv>::digest(&data);
|
||||
let hash = Sha::<TockEnv<Syscalls>>::digest(&data);
|
||||
let response = process_vendor_upgrade(
|
||||
&mut env,
|
||||
VendorUpgradeParameters {
|
||||
@@ -656,7 +667,7 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_vendor_upgrade_info() {
|
||||
let mut env = TockEnv::default();
|
||||
let mut env = TockEnv::<Syscalls>::default();
|
||||
let bundle_identifier = env.upgrade_storage().unwrap().bundle_identifier();
|
||||
|
||||
let upgrade_info_reponse = process_vendor_upgrade_info(&mut env);
|
||||
|
||||
323
src/env/tock/mod.rs
vendored
323
src/env/tock/mod.rs
vendored
@@ -16,16 +16,18 @@ use alloc::vec::Vec;
|
||||
use clock::TockClock;
|
||||
use core::cell::Cell;
|
||||
use core::convert::TryFrom;
|
||||
#[cfg(not(feature = "std"))]
|
||||
use core::marker::PhantomData;
|
||||
#[cfg(all(target_has_atomic = "8", not(feature = "std")))]
|
||||
use core::sync::atomic::{AtomicBool, Ordering};
|
||||
use libtock_core::result::{CommandError, EALREADY};
|
||||
use libtock_drivers::buttons::{self, ButtonState};
|
||||
use libtock_drivers::console::Console;
|
||||
#[cfg(not(feature = "std"))]
|
||||
use libtock_drivers::crp;
|
||||
use libtock_buttons::{ButtonListener, ButtonState, Buttons};
|
||||
use libtock_console::{Console, ConsoleWriter};
|
||||
use libtock_drivers::result::{FlexUnwrap, TockError};
|
||||
use libtock_drivers::timer::Duration;
|
||||
use libtock_drivers::{led, rng, timer, usb_ctap_hid};
|
||||
use libtock_drivers::usb_ctap_hid::UsbCtapHid;
|
||||
use libtock_drivers::{rng, timer, usb_ctap_hid};
|
||||
use libtock_leds::Leds;
|
||||
use libtock_platform as platform;
|
||||
use libtock_platform::{ErrorCode, Syscalls};
|
||||
use opensk::api::attestation_store::AttestationStore;
|
||||
use opensk::api::connection::{
|
||||
HidConnection, SendOrRecvError, SendOrRecvResult, SendOrRecvStatus, UsbEndpoint,
|
||||
@@ -38,28 +40,31 @@ use opensk::api::{attestation_store, key_store};
|
||||
use opensk::ctap::Channel;
|
||||
use opensk::env::Env;
|
||||
#[cfg(feature = "std")]
|
||||
use persistent_store::{BufferOptions, BufferStorage};
|
||||
use persistent_store::BufferOptions;
|
||||
use persistent_store::{StorageResult, Store};
|
||||
use platform::{share, DefaultConfig, Subscribe};
|
||||
use rand_core::{impls, CryptoRng, Error, RngCore};
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
mod buffer_upgrade_storage;
|
||||
mod clock;
|
||||
mod commands;
|
||||
#[cfg(feature = "std")]
|
||||
mod phantom_buffer_storage;
|
||||
#[cfg(not(feature = "std"))]
|
||||
mod storage;
|
||||
mod storage_helper;
|
||||
mod upgrade_helper;
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
pub type Storage = storage::TockStorage;
|
||||
pub type Storage<S, C> = storage::TockStorage<S, C>;
|
||||
#[cfg(feature = "std")]
|
||||
pub type Storage = BufferStorage;
|
||||
pub type Storage<S, C> = phantom_buffer_storage::PhantomBufferStorage<S, C>;
|
||||
|
||||
#[cfg(not(feature = "std"))]
|
||||
type UpgradeStorage = storage::TockUpgradeStorage;
|
||||
type UpgradeStorage<S, C> = storage::TockUpgradeStorage<S, C>;
|
||||
#[cfg(feature = "std")]
|
||||
type UpgradeStorage = buffer_upgrade_storage::BufferUpgradeStorage;
|
||||
type UpgradeStorage<S, C> = buffer_upgrade_storage::BufferUpgradeStorage<S, C>;
|
||||
|
||||
pub const AAGUID: &[u8; AAGUID_LENGTH] =
|
||||
include_bytes!(concat!(env!("OUT_DIR"), "/opensk_aaguid.bin"));
|
||||
@@ -70,11 +75,21 @@ const TOCK_CUSTOMIZATION: CustomizationImpl = CustomizationImpl {
|
||||
};
|
||||
|
||||
/// RNG backed by the TockOS rng driver.
|
||||
pub struct TockRng {}
|
||||
pub struct TockRng<S: Syscalls> {
|
||||
_syscalls: PhantomData<S>,
|
||||
}
|
||||
|
||||
impl CryptoRng for TockRng {}
|
||||
impl<S: Syscalls> Default for TockRng<S> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
_syscalls: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RngCore for TockRng {
|
||||
impl<S: Syscalls> CryptoRng for TockRng<S> {}
|
||||
|
||||
impl<S: Syscalls> RngCore for TockRng<S> {
|
||||
fn next_u32(&mut self) -> u32 {
|
||||
impls::next_u32_via_fill(self)
|
||||
}
|
||||
@@ -84,7 +99,7 @@ impl RngCore for TockRng {
|
||||
}
|
||||
|
||||
fn fill_bytes(&mut self, dest: &mut [u8]) {
|
||||
rng::fill_buffer(dest);
|
||||
rng::Rng::<S>::fill_buffer(dest);
|
||||
}
|
||||
|
||||
fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), Error> {
|
||||
@@ -93,74 +108,89 @@ impl RngCore for TockRng {
|
||||
}
|
||||
}
|
||||
|
||||
impl Rng for TockRng {}
|
||||
impl<S: Syscalls> Rng for TockRng<S> {}
|
||||
|
||||
pub struct TockHidConnection {
|
||||
pub struct TockHidConnection<S: Syscalls> {
|
||||
endpoint: UsbEndpoint,
|
||||
s: PhantomData<S>,
|
||||
}
|
||||
|
||||
impl HidConnection for TockHidConnection {
|
||||
impl<S: Syscalls> HidConnection for TockHidConnection<S> {
|
||||
fn send_and_maybe_recv(&mut self, buf: &mut [u8; 64], timeout_ms: usize) -> SendOrRecvResult {
|
||||
match usb_ctap_hid::send_or_recv_with_timeout(
|
||||
match UsbCtapHid::<S>::send_or_recv_with_timeout(
|
||||
buf,
|
||||
Duration::from_ms(timeout_ms as isize),
|
||||
self.endpoint as usize,
|
||||
self.endpoint as u32,
|
||||
) {
|
||||
Ok(usb_ctap_hid::SendOrRecvStatus::Timeout) => Ok(SendOrRecvStatus::Timeout),
|
||||
Ok(usb_ctap_hid::SendOrRecvStatus::Sent) => Ok(SendOrRecvStatus::Sent),
|
||||
Ok(usb_ctap_hid::SendOrRecvStatus::Received(recv_endpoint)) => {
|
||||
UsbEndpoint::try_from(recv_endpoint).map(SendOrRecvStatus::Received)
|
||||
UsbEndpoint::try_from(recv_endpoint as usize).map(SendOrRecvStatus::Received)
|
||||
}
|
||||
_ => Err(SendOrRecvError),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TockEnv {
|
||||
rng: TockRng,
|
||||
store: Store<Storage>,
|
||||
upgrade_storage: Option<UpgradeStorage>,
|
||||
main_connection: TockHidConnection,
|
||||
pub struct TockEnv<
|
||||
S: Syscalls,
|
||||
C: platform::subscribe::Config + platform::allow_ro::Config = DefaultConfig,
|
||||
> {
|
||||
rng: TockRng<S>,
|
||||
store: Store<Storage<S, C>>,
|
||||
upgrade_storage: Option<UpgradeStorage<S, C>>,
|
||||
main_connection: TockHidConnection<S>,
|
||||
#[cfg(feature = "vendor_hid")]
|
||||
vendor_connection: TockHidConnection,
|
||||
vendor_connection: TockHidConnection<S>,
|
||||
blink_pattern: usize,
|
||||
clock: TockClock,
|
||||
clock: TockClock<S>,
|
||||
c: PhantomData<C>,
|
||||
}
|
||||
|
||||
impl Default for TockEnv {
|
||||
impl<S: Syscalls, C: platform::subscribe::Config + platform::allow_ro::Config> Default
|
||||
for TockEnv<S, C>
|
||||
{
|
||||
/// Returns the unique instance of the Tock environment.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// - If called a second time.
|
||||
fn default() -> Self {
|
||||
let rng = TockRng::default();
|
||||
// We rely on `take_storage` to ensure that this function is called only once.
|
||||
let storage = take_storage().unwrap();
|
||||
let storage = take_storage::<S, C>().unwrap();
|
||||
let store = Store::new(storage).ok().unwrap();
|
||||
let upgrade_storage = UpgradeStorage::new().ok();
|
||||
TockEnv {
|
||||
rng: TockRng {},
|
||||
rng,
|
||||
store,
|
||||
upgrade_storage,
|
||||
main_connection: TockHidConnection {
|
||||
endpoint: UsbEndpoint::MainHid,
|
||||
s: PhantomData,
|
||||
},
|
||||
#[cfg(feature = "vendor_hid")]
|
||||
vendor_connection: TockHidConnection {
|
||||
endpoint: UsbEndpoint::VendorHid,
|
||||
s: PhantomData,
|
||||
},
|
||||
blink_pattern: 0,
|
||||
clock: TockClock::default(),
|
||||
c: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TockEnv {
|
||||
impl<S, C> TockEnv<S, C>
|
||||
where
|
||||
S: Syscalls,
|
||||
C: platform::subscribe::Config + platform::allow_ro::Config,
|
||||
{
|
||||
/// Returns the upgrade storage instance.
|
||||
///
|
||||
/// Upgrade storage is optional, so implementations may return `None`. However, implementations
|
||||
/// should either always return `None` or always return `Some`.
|
||||
pub fn upgrade_storage(&mut self) -> Option<&mut UpgradeStorage> {
|
||||
pub fn upgrade_storage(&mut self) -> Option<&mut UpgradeStorage<S, C>> {
|
||||
self.upgrade_storage.as_mut()
|
||||
}
|
||||
|
||||
@@ -169,39 +199,13 @@ impl TockEnv {
|
||||
}
|
||||
|
||||
pub fn lock_firmware_protection(&mut self) -> bool {
|
||||
#[cfg(not(feature = "std"))]
|
||||
{
|
||||
matches!(
|
||||
crp::set_protection(crp::ProtectionLevel::FullyLocked),
|
||||
Ok(())
|
||||
| Err(TockError::Command(CommandError {
|
||||
return_code: EALREADY,
|
||||
..
|
||||
}))
|
||||
)
|
||||
}
|
||||
#[cfg(feature = "std")]
|
||||
{
|
||||
true
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the unique storage instance.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// - If called a second time.
|
||||
#[cfg(not(feature = "std"))]
|
||||
pub fn take_storage() -> StorageResult<Storage> {
|
||||
// Make sure the storage was not already taken.
|
||||
static TAKEN: AtomicBool = AtomicBool::new(false);
|
||||
assert!(!TAKEN.fetch_or(true, Ordering::SeqCst));
|
||||
Storage::new()
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
pub fn take_storage() -> StorageResult<Storage> {
|
||||
pub fn take_storage<S: Syscalls, C: platform::subscribe::Config + platform::allow_ro::Config>(
|
||||
) -> StorageResult<Storage<S, C>> {
|
||||
// Use the Nordic configuration.
|
||||
const PAGE_SIZE: usize = 0x1000;
|
||||
const NUM_PAGES: usize = 20;
|
||||
@@ -213,10 +217,54 @@ pub fn take_storage() -> StorageResult<Storage> {
|
||||
max_page_erases: 10000,
|
||||
strict_mode: true,
|
||||
};
|
||||
Ok(BufferStorage::new(store, options))
|
||||
Ok(phantom_buffer_storage::PhantomBufferStorage::new(
|
||||
store, options,
|
||||
))
|
||||
}
|
||||
|
||||
impl UserPresence for TockEnv {
|
||||
/// Returns the unique storage instance.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// - If called a second time.
|
||||
#[cfg(not(feature = "std"))]
|
||||
pub fn take_storage<S: Syscalls, C: platform::subscribe::Config + platform::allow_ro::Config>(
|
||||
) -> StorageResult<Storage<S, C>> {
|
||||
// Make sure the storage was not already taken.
|
||||
#[cfg(target_has_atomic = "8")]
|
||||
{
|
||||
static TAKEN: AtomicBool = AtomicBool::new(false);
|
||||
assert!(!TAKEN.fetch_or(true, Ordering::SeqCst));
|
||||
}
|
||||
#[cfg(not(target_has_atomic = "8"))]
|
||||
{
|
||||
static mut TAKEN: bool = false;
|
||||
// Safety
|
||||
//
|
||||
// We can not use an AtomicBool on platforms that do not support atomics,
|
||||
// such as the whole `riscv32i[mc]` family like OpenTitan.
|
||||
// Thus, we need to use a mutable static variable which are unsafe
|
||||
// cause they could cause a data race when two threads access it
|
||||
// at the same time.
|
||||
//
|
||||
// However, as we are running an application on TockOS and because
|
||||
// of its [architecture](https://www.tockos.org/documentation/design)
|
||||
// we are running in a single-threaded event loop which means the
|
||||
// aforementioned data race is impossible. Thus, in this case, the
|
||||
// usage of a static mut is safe.
|
||||
unsafe {
|
||||
assert!(!TAKEN);
|
||||
TAKEN = true;
|
||||
}
|
||||
}
|
||||
Storage::new()
|
||||
}
|
||||
|
||||
impl<S, C> UserPresence for TockEnv<S, C>
|
||||
where
|
||||
S: Syscalls,
|
||||
C: platform::subscribe::Config + platform::allow_ro::Config,
|
||||
{
|
||||
fn check_init(&mut self) {
|
||||
self.blink_pattern = 0;
|
||||
}
|
||||
@@ -225,52 +273,77 @@ impl UserPresence for TockEnv {
|
||||
if timeout_ms == 0 {
|
||||
return Err(UserPresenceError::Timeout);
|
||||
}
|
||||
blink_leds(self.blink_pattern);
|
||||
blink_leds::<S>(self.blink_pattern);
|
||||
self.blink_pattern += 1;
|
||||
|
||||
// enable interrupts for all buttons
|
||||
let num_buttons = Buttons::<S>::count().map_err(|_| UserPresenceError::Fail)?;
|
||||
(0..num_buttons)
|
||||
.try_for_each(|n| Buttons::<S>::enable_interrupts(n))
|
||||
.map_err(|_| UserPresenceError::Fail)?;
|
||||
|
||||
let button_touched = Cell::new(false);
|
||||
let mut buttons_callback = buttons::with_callback(|_button_num, state| {
|
||||
let button_listener = ButtonListener(|_button_num, state| {
|
||||
match state {
|
||||
ButtonState::Pressed => button_touched.set(true),
|
||||
ButtonState::Released => (),
|
||||
};
|
||||
});
|
||||
let mut buttons = buttons_callback.init().flex_unwrap();
|
||||
for mut button in &mut buttons {
|
||||
button.enable().flex_unwrap();
|
||||
}
|
||||
|
||||
// Setup a keep-alive callback.
|
||||
// Setup a keep-alive callback but don't enable it yet
|
||||
let keepalive_expired = Cell::new(false);
|
||||
let mut keepalive_callback = timer::with_callback(|_, _| {
|
||||
keepalive_expired.set(true);
|
||||
});
|
||||
let mut keepalive = keepalive_callback.init().flex_unwrap();
|
||||
let keepalive_alarm = keepalive
|
||||
.set_alarm(Duration::from_ms(timeout_ms as isize))
|
||||
.flex_unwrap();
|
||||
let mut keepalive_callback =
|
||||
timer::with_callback::<S, C, _>(|_| keepalive_expired.set(true));
|
||||
share::scope::<
|
||||
(
|
||||
Subscribe<_, { libtock_buttons::DRIVER_NUM }, 0>,
|
||||
Subscribe<
|
||||
S,
|
||||
{ libtock_drivers::timer::DRIVER_NUM },
|
||||
{ libtock_drivers::timer::subscribe::CALLBACK },
|
||||
>,
|
||||
),
|
||||
_,
|
||||
_,
|
||||
>(|handle| {
|
||||
let (sub_button, sub_timer) = handle.split();
|
||||
Buttons::<S>::register_listener(&button_listener, sub_button)
|
||||
.map_err(|_| UserPresenceError::Fail)?;
|
||||
|
||||
// Wait for a button touch or an alarm.
|
||||
libtock_drivers::util::yieldk_for(|| button_touched.get() || keepalive_expired.get());
|
||||
let mut keepalive = keepalive_callback.init().flex_unwrap();
|
||||
keepalive_callback
|
||||
.enable(sub_timer)
|
||||
.map_err(|_| UserPresenceError::Fail)?;
|
||||
keepalive
|
||||
.set_alarm(timer::Duration::from_ms(timeout_ms as isize))
|
||||
.flex_unwrap();
|
||||
|
||||
// Cleanup alarm callback.
|
||||
match keepalive.stop_alarm(keepalive_alarm) {
|
||||
Ok(()) => (),
|
||||
Err(TockError::Command(CommandError {
|
||||
return_code: EALREADY,
|
||||
..
|
||||
})) => assert!(keepalive_expired.get()),
|
||||
Err(_e) => {
|
||||
#[cfg(feature = "debug_ctap")]
|
||||
panic!("Unexpected error when stopping alarm: {:?}", _e);
|
||||
#[cfg(not(feature = "debug_ctap"))]
|
||||
panic!("Unexpected error when stopping alarm: <error is only visible with the debug_ctap feature>");
|
||||
// Wait for a button touch or an alarm.
|
||||
libtock_drivers::util::Util::<S>::yieldk_for(|| {
|
||||
button_touched.get() || keepalive_expired.get()
|
||||
});
|
||||
|
||||
Buttons::<S>::unregister_listener();
|
||||
|
||||
// disable event interrupts for all buttons
|
||||
(0..num_buttons)
|
||||
.try_for_each(|n| Buttons::<S>::disable_interrupts(n))
|
||||
.map_err(|_| UserPresenceError::Fail)?;
|
||||
|
||||
// Cleanup alarm callback.
|
||||
match keepalive.stop_alarm() {
|
||||
Ok(()) => (),
|
||||
Err(TockError::Command(ErrorCode::Already)) => assert!(keepalive_expired.get()),
|
||||
Err(_e) => {
|
||||
#[cfg(feature = "debug_ctap")]
|
||||
panic!("Unexpected error when stopping alarm: {:?}", _e);
|
||||
#[cfg(not(feature = "debug_ctap"))]
|
||||
panic!("Unexpected error when stopping alarm: <error is only visible with the debug_ctap feature>");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for mut button in &mut buttons {
|
||||
button.disable().flex_unwrap();
|
||||
}
|
||||
Ok::<(), UserPresenceError>(())
|
||||
})?;
|
||||
|
||||
if button_touched.get() {
|
||||
Ok(())
|
||||
@@ -282,13 +355,22 @@ impl UserPresence for TockEnv {
|
||||
}
|
||||
|
||||
fn check_complete(&mut self) {
|
||||
switch_off_leds();
|
||||
switch_off_leds::<S>();
|
||||
}
|
||||
}
|
||||
|
||||
impl key_store::Helper for TockEnv {}
|
||||
impl<S, C> key_store::Helper for TockEnv<S, C>
|
||||
where
|
||||
S: Syscalls,
|
||||
C: platform::allow_ro::Config + platform::subscribe::Config,
|
||||
{
|
||||
}
|
||||
|
||||
impl AttestationStore for TockEnv {
|
||||
impl<S, C> AttestationStore for TockEnv<S, C>
|
||||
where
|
||||
S: Syscalls,
|
||||
C: platform::subscribe::Config + platform::allow_ro::Config,
|
||||
{
|
||||
fn get(
|
||||
&mut self,
|
||||
id: &attestation_store::Id,
|
||||
@@ -311,16 +393,18 @@ impl AttestationStore for TockEnv {
|
||||
}
|
||||
}
|
||||
|
||||
impl Env for TockEnv {
|
||||
type Rng = TockRng;
|
||||
impl<S: Syscalls, C: platform::subscribe::Config + platform::allow_ro::Config> Env
|
||||
for TockEnv<S, C>
|
||||
{
|
||||
type Rng = TockRng<S>;
|
||||
type UserPresence = Self;
|
||||
type Storage = Storage;
|
||||
type Storage = Storage<S, C>;
|
||||
type KeyStore = Self;
|
||||
type AttestationStore = Self;
|
||||
type Clock = TockClock;
|
||||
type Write = Console;
|
||||
type Clock = TockClock<S>;
|
||||
type Write = ConsoleWriter<S>;
|
||||
type Customization = CustomizationImpl;
|
||||
type HidConnection = TockHidConnection;
|
||||
type HidConnection = TockHidConnection<S>;
|
||||
type Crypto = SoftwareCrypto;
|
||||
|
||||
fn rng(&mut self) -> &mut Self::Rng {
|
||||
@@ -348,7 +432,7 @@ impl Env for TockEnv {
|
||||
}
|
||||
|
||||
fn write(&mut self) -> Self::Write {
|
||||
Console::new()
|
||||
Console::<S>::writer()
|
||||
}
|
||||
|
||||
fn customization(&self) -> &Self::Customization {
|
||||
@@ -375,17 +459,17 @@ impl Env for TockEnv {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn blink_leds(pattern_seed: usize) {
|
||||
for l in 0..led::count().flex_unwrap() {
|
||||
if (pattern_seed ^ l).count_ones() & 1 != 0 {
|
||||
led::get(l).flex_unwrap().on().flex_unwrap();
|
||||
pub fn blink_leds<S: Syscalls>(pattern_seed: usize) {
|
||||
for l in 0..Leds::<S>::count().unwrap() {
|
||||
if (pattern_seed ^ l as usize).count_ones() & 1 != 0 {
|
||||
Leds::<S>::on(l).unwrap();
|
||||
} else {
|
||||
led::get(l).flex_unwrap().off().flex_unwrap();
|
||||
Leds::<S>::off(l).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn wink_leds(pattern_seed: usize) {
|
||||
pub fn wink_leds<S: Syscalls>(pattern_seed: usize) {
|
||||
// This generates a "snake" pattern circling through the LEDs.
|
||||
// Fox example with 4 LEDs the sequence of lit LEDs will be the following.
|
||||
// 0 1 2 3
|
||||
@@ -398,7 +482,7 @@ pub fn wink_leds(pattern_seed: usize) {
|
||||
// * *
|
||||
// * * *
|
||||
// * *
|
||||
let count = led::count().flex_unwrap();
|
||||
let count = Leds::<S>::count().unwrap() as usize;
|
||||
let a = (pattern_seed / 2) % count;
|
||||
let b = ((pattern_seed + 1) / 2) % count;
|
||||
let c = ((pattern_seed + 3) / 2) % count;
|
||||
@@ -411,16 +495,17 @@ pub fn wink_leds(pattern_seed: usize) {
|
||||
_ => l,
|
||||
};
|
||||
if k == a || k == b || k == c {
|
||||
led::get(l).flex_unwrap().on().flex_unwrap();
|
||||
Leds::<S>::on(l as u32).unwrap();
|
||||
} else {
|
||||
led::get(l).flex_unwrap().off().flex_unwrap();
|
||||
Leds::<S>::off(l as u32).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn switch_off_leds() {
|
||||
for l in 0..led::count().flex_unwrap() {
|
||||
led::get(l).flex_unwrap().off().flex_unwrap();
|
||||
pub fn switch_off_leds<S: Syscalls>() {
|
||||
let count = Leds::<S>::count().unwrap();
|
||||
for l in 0..count {
|
||||
Leds::<S>::off(l).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
111
src/env/tock/phantom_buffer_storage.rs
vendored
Normal file
111
src/env/tock/phantom_buffer_storage.rs
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use alloc::borrow::Cow;
|
||||
use core::marker::PhantomData;
|
||||
use libtock_platform as platform;
|
||||
use libtock_platform::Syscalls;
|
||||
use persistent_store::{
|
||||
BufferCorruptFunction, BufferOptions, BufferStorage, Storage, StorageIndex, StorageResult,
|
||||
};
|
||||
|
||||
/// Wrapper with phantom data for the test storage implementation.
|
||||
pub struct PhantomBufferStorage<
|
||||
S: Syscalls,
|
||||
C: platform::subscribe::Config + platform::allow_ro::Config,
|
||||
> {
|
||||
storage: BufferStorage,
|
||||
s: PhantomData<S>,
|
||||
c: PhantomData<C>,
|
||||
}
|
||||
|
||||
impl<S, C> PhantomBufferStorage<S, C>
|
||||
where
|
||||
S: Syscalls,
|
||||
C: platform::allow_ro::Config + platform::subscribe::Config,
|
||||
{
|
||||
pub fn new(storage: Box<[u8]>, options: BufferOptions) -> Self {
|
||||
Self {
|
||||
storage: BufferStorage::new(storage, options),
|
||||
s: PhantomData,
|
||||
c: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn arm_interruption(&mut self, delay: usize) {
|
||||
self.storage.arm_interruption(delay);
|
||||
}
|
||||
|
||||
pub fn disarm_interruption(&mut self) -> usize {
|
||||
self.storage.disarm_interruption()
|
||||
}
|
||||
|
||||
pub fn reset_interruption(&mut self) {
|
||||
self.storage.reset_interruption();
|
||||
}
|
||||
|
||||
pub fn corrupt_operation(&mut self, corrupt: BufferCorruptFunction) {
|
||||
self.storage.corrupt_operation(corrupt);
|
||||
}
|
||||
|
||||
pub fn get_word_writes(&self, word: usize) -> usize {
|
||||
self.storage.get_word_writes(word)
|
||||
}
|
||||
|
||||
pub fn get_page_erases(&self, page: usize) -> usize {
|
||||
self.storage.get_page_erases(page)
|
||||
}
|
||||
|
||||
pub fn set_page_erases(&mut self, page: usize, cycle: usize) {
|
||||
self.storage.set_page_erases(page, cycle);
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, C> Storage for PhantomBufferStorage<S, C>
|
||||
where
|
||||
S: Syscalls,
|
||||
C: platform::allow_ro::Config + platform::subscribe::Config,
|
||||
{
|
||||
fn word_size(&self) -> usize {
|
||||
self.storage.word_size()
|
||||
}
|
||||
|
||||
fn page_size(&self) -> usize {
|
||||
self.storage.page_size()
|
||||
}
|
||||
|
||||
fn num_pages(&self) -> usize {
|
||||
self.storage.num_pages()
|
||||
}
|
||||
|
||||
fn max_word_writes(&self) -> usize {
|
||||
self.storage.max_word_writes()
|
||||
}
|
||||
|
||||
fn max_page_erases(&self) -> usize {
|
||||
self.storage.max_page_erases()
|
||||
}
|
||||
|
||||
fn read_slice(&self, index: StorageIndex, length: usize) -> StorageResult<Cow<[u8]>> {
|
||||
self.storage.read_slice(index, length)
|
||||
}
|
||||
|
||||
fn write_slice(&mut self, index: StorageIndex, value: &[u8]) -> StorageResult<()> {
|
||||
self.storage.write_slice(index, value)
|
||||
}
|
||||
|
||||
fn erase_page(&mut self, page: usize) -> StorageResult<()> {
|
||||
self.storage.erase_page(page)
|
||||
}
|
||||
}
|
||||
208
src/env/tock/storage.rs
vendored
208
src/env/tock/storage.rs
vendored
@@ -20,34 +20,37 @@ use super::TockEnv;
|
||||
use alloc::borrow::Cow;
|
||||
use alloc::vec::Vec;
|
||||
use core::cell::Cell;
|
||||
use libtock_core::{callback, syscalls};
|
||||
use core::marker::PhantomData;
|
||||
use libtock_platform as platform;
|
||||
use libtock_platform::{syscall_class, ErrorCode, RawSyscalls, Syscalls};
|
||||
use opensk::api::crypto::sha256::Sha256;
|
||||
use opensk::env::Sha;
|
||||
use persistent_store::{Storage, StorageError, StorageIndex, StorageResult};
|
||||
use platform::share;
|
||||
|
||||
const DRIVER_NUMBER: usize = 0x50003;
|
||||
const DRIVER_NUMBER: u32 = 0x50003;
|
||||
|
||||
const UPGRADE_PUBLIC_KEY: &[u8; 65] =
|
||||
include_bytes!(concat!(env!("OUT_DIR"), "/opensk_upgrade_pubkey.bin"));
|
||||
|
||||
mod subscribe_nr {
|
||||
pub const DONE: usize = 0;
|
||||
pub const DONE: u32 = 0;
|
||||
}
|
||||
|
||||
mod command_nr {
|
||||
pub const GET_INFO: usize = 1;
|
||||
pub const GET_INFO: u32 = 1;
|
||||
pub mod get_info_nr {
|
||||
pub const WORD_SIZE: usize = 0;
|
||||
pub const PAGE_SIZE: usize = 1;
|
||||
pub const MAX_WORD_WRITES: usize = 2;
|
||||
pub const MAX_PAGE_ERASES: usize = 3;
|
||||
pub const WORD_SIZE: u32 = 0;
|
||||
pub const PAGE_SIZE: u32 = 1;
|
||||
pub const MAX_WORD_WRITES: u32 = 2;
|
||||
pub const MAX_PAGE_ERASES: u32 = 3;
|
||||
}
|
||||
pub const WRITE_SLICE: usize = 2;
|
||||
pub const ERASE_PAGE: usize = 3;
|
||||
pub const WRITE_SLICE: u32 = 2;
|
||||
pub const ERASE_PAGE: u32 = 3;
|
||||
}
|
||||
|
||||
mod allow_nr {
|
||||
pub const WRITE_SLICE: usize = 0;
|
||||
mod ro_allow_nr {
|
||||
pub const WRITE_SLICE: u32 = 0;
|
||||
}
|
||||
|
||||
mod memop_nr {
|
||||
@@ -58,85 +61,98 @@ mod memop_nr {
|
||||
}
|
||||
|
||||
mod storage_type {
|
||||
pub const STORE: usize = 1;
|
||||
pub const PARTITION: usize = 2;
|
||||
pub const STORE: u32 = 1;
|
||||
pub const PARTITION: u32 = 2;
|
||||
}
|
||||
|
||||
fn get_info(nr: usize, arg: usize) -> StorageResult<usize> {
|
||||
let code = syscalls::command(DRIVER_NUMBER, command_nr::GET_INFO, nr, arg);
|
||||
code.map_err(|_| StorageError::CustomError)
|
||||
fn get_info<S: Syscalls>(nr: u32, arg: u32) -> StorageResult<u32> {
|
||||
let info = S::command(DRIVER_NUMBER, command_nr::GET_INFO, nr, arg)
|
||||
.to_result::<u32, ErrorCode>()
|
||||
.map_err(|_| StorageError::CustomError)?;
|
||||
Ok(info)
|
||||
}
|
||||
|
||||
fn memop(nr: u32, arg: usize) -> StorageResult<usize> {
|
||||
let code = unsafe { syscalls::raw::memop(nr, arg) };
|
||||
if code < 0 {
|
||||
Err(StorageError::CustomError)
|
||||
} else {
|
||||
Ok(code as usize)
|
||||
fn memop<S: RawSyscalls>(nr: u32, arg: u32) -> StorageResult<u32> {
|
||||
let registers = unsafe { S::syscall2::<{ syscall_class::MEMOP }>([nr.into(), arg.into()]) };
|
||||
|
||||
let r0 = registers[0].as_u32();
|
||||
let r1 = registers[1].as_u32();
|
||||
|
||||
// make sure r0 is the `success with u32` (129) return variant and then return the value in r1 as u32
|
||||
// see: https://github.com/tock/tock/blob/master/doc/reference/trd104-syscalls.md#32-return-values
|
||||
match (r0, r1) {
|
||||
(129, r1) => Ok(r1),
|
||||
(_, _) => Err(StorageError::CustomError),
|
||||
}
|
||||
}
|
||||
|
||||
fn block_command(driver: usize, cmd: usize, arg1: usize, arg2: usize) -> StorageResult<()> {
|
||||
let done = Cell::new(None);
|
||||
let mut alarm = |status| done.set(Some(status));
|
||||
let subscription = syscalls::subscribe::<callback::Identity1Consumer, _>(
|
||||
DRIVER_NUMBER,
|
||||
subscribe_nr::DONE,
|
||||
&mut alarm,
|
||||
);
|
||||
if subscription.is_err() {
|
||||
return Err(StorageError::CustomError);
|
||||
}
|
||||
fn block_command<S: Syscalls, C: platform::subscribe::Config + platform::allow_ro::Config>(
|
||||
driver: u32,
|
||||
cmd: u32,
|
||||
arg1: u32,
|
||||
arg2: u32,
|
||||
) -> StorageResult<()> {
|
||||
let called: Cell<Option<(u32,)>> = Cell::new(None);
|
||||
|
||||
let code = syscalls::command(driver, cmd, arg1, arg2);
|
||||
if code.is_err() {
|
||||
return Err(StorageError::CustomError);
|
||||
}
|
||||
|
||||
libtock_drivers::util::yieldk_for(|| done.get().is_some());
|
||||
if done.get().unwrap() == 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(StorageError::CustomError)
|
||||
}
|
||||
share::scope(|subscribe| {
|
||||
S::subscribe::<_, _, C, DRIVER_NUMBER, { subscribe_nr::DONE }>(subscribe, &called)
|
||||
.map_err(|_| StorageError::CustomError)?;
|
||||
S::command(driver, cmd, arg1, arg2)
|
||||
.to_result::<(), ErrorCode>()
|
||||
.map_err(|_| StorageError::CustomError)?;
|
||||
libtock_drivers::util::Util::<S>::yieldk_for(|| called.get().is_some());
|
||||
if called.get().unwrap().0 == 0 {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(StorageError::CustomError)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
unsafe fn read_slice(address: usize, length: usize) -> &'static [u8] {
|
||||
core::slice::from_raw_parts(address as *const u8, length)
|
||||
}
|
||||
|
||||
fn write_slice(ptr: usize, value: &[u8]) -> StorageResult<()> {
|
||||
let code = unsafe {
|
||||
syscalls::raw::allow(
|
||||
fn write_slice<S: Syscalls, C: platform::allow_ro::Config + platform::subscribe::Config>(
|
||||
ptr: usize,
|
||||
value: &[u8],
|
||||
) -> StorageResult<()> {
|
||||
share::scope(|allow_ro| {
|
||||
S::allow_ro::<C, DRIVER_NUMBER, { ro_allow_nr::WRITE_SLICE }>(allow_ro, value)
|
||||
.map_err(|_| StorageError::CustomError)?;
|
||||
block_command::<S, C>(
|
||||
DRIVER_NUMBER,
|
||||
allow_nr::WRITE_SLICE,
|
||||
// We rely on the driver not writing to the slice. This should use read-only allow
|
||||
// when available. See https://github.com/tock/tock/issues/1274.
|
||||
value.as_ptr() as *mut u8,
|
||||
value.len(),
|
||||
command_nr::WRITE_SLICE,
|
||||
ptr as u32,
|
||||
value.len() as u32,
|
||||
)
|
||||
};
|
||||
if code < 0 {
|
||||
return Err(StorageError::CustomError);
|
||||
}
|
||||
|
||||
block_command(DRIVER_NUMBER, command_nr::WRITE_SLICE, ptr, value.len())
|
||||
})
|
||||
}
|
||||
|
||||
fn erase_page(ptr: usize, page_length: usize) -> StorageResult<()> {
|
||||
block_command(DRIVER_NUMBER, command_nr::ERASE_PAGE, ptr, page_length)
|
||||
fn erase_page<S: Syscalls, C: platform::allow_ro::Config + platform::subscribe::Config>(
|
||||
ptr: usize,
|
||||
page_length: usize,
|
||||
) -> StorageResult<()> {
|
||||
block_command::<S, C>(
|
||||
DRIVER_NUMBER,
|
||||
command_nr::ERASE_PAGE,
|
||||
ptr as u32,
|
||||
page_length as u32,
|
||||
)
|
||||
}
|
||||
|
||||
pub struct TockStorage {
|
||||
pub struct TockStorage<S: Syscalls, C: platform::subscribe::Config + platform::allow_ro::Config> {
|
||||
word_size: usize,
|
||||
page_size: usize,
|
||||
num_pages: usize,
|
||||
max_word_writes: usize,
|
||||
max_page_erases: usize,
|
||||
storage_locations: Vec<&'static [u8]>,
|
||||
s: PhantomData<S>,
|
||||
c: PhantomData<C>,
|
||||
}
|
||||
|
||||
impl TockStorage {
|
||||
impl<S: Syscalls, C: platform::subscribe::Config + platform::allow_ro::Config> TockStorage<S, C> {
|
||||
/// Provides access to the embedded flash if available.
|
||||
///
|
||||
/// # Errors
|
||||
@@ -146,14 +162,16 @@ impl TockStorage {
|
||||
/// - The page size is a power of two.
|
||||
/// - The page size is a multiple of the word size.
|
||||
/// - The storage is page-aligned.
|
||||
pub fn new() -> StorageResult<TockStorage> {
|
||||
pub fn new() -> StorageResult<TockStorage<S, C>> {
|
||||
let mut syscall = TockStorage {
|
||||
word_size: get_info(command_nr::get_info_nr::WORD_SIZE, 0)?,
|
||||
page_size: get_info(command_nr::get_info_nr::PAGE_SIZE, 0)?,
|
||||
word_size: get_info::<S>(command_nr::get_info_nr::WORD_SIZE, 0)? as usize,
|
||||
page_size: get_info::<S>(command_nr::get_info_nr::PAGE_SIZE, 0)? as usize,
|
||||
num_pages: 0,
|
||||
max_word_writes: get_info(command_nr::get_info_nr::MAX_WORD_WRITES, 0)?,
|
||||
max_page_erases: get_info(command_nr::get_info_nr::MAX_PAGE_ERASES, 0)?,
|
||||
max_word_writes: get_info::<S>(command_nr::get_info_nr::MAX_WORD_WRITES, 0)? as usize,
|
||||
max_page_erases: get_info::<S>(command_nr::get_info_nr::MAX_PAGE_ERASES, 0)? as usize,
|
||||
storage_locations: Vec::new(),
|
||||
s: PhantomData,
|
||||
c: PhantomData,
|
||||
};
|
||||
if !syscall.word_size.is_power_of_two()
|
||||
|| !syscall.page_size.is_power_of_two()
|
||||
@@ -161,12 +179,13 @@ impl TockStorage {
|
||||
{
|
||||
return Err(StorageError::CustomError);
|
||||
}
|
||||
for i in 0..memop(memop_nr::STORAGE_CNT, 0)? {
|
||||
if memop(memop_nr::STORAGE_TYPE, i)? != storage_type::STORE {
|
||||
let num_storage_locations = memop::<S>(memop_nr::STORAGE_CNT, 0)?;
|
||||
for i in 0..num_storage_locations {
|
||||
if memop::<S>(memop_nr::STORAGE_TYPE, i)? != storage_type::STORE {
|
||||
continue;
|
||||
}
|
||||
let storage_ptr = memop(memop_nr::STORAGE_PTR, i)?;
|
||||
let storage_len = memop(memop_nr::STORAGE_LEN, i)?;
|
||||
let storage_ptr = memop::<S>(memop_nr::STORAGE_PTR, i)? as usize;
|
||||
let storage_len = memop::<S>(memop_nr::STORAGE_LEN, i)? as usize;
|
||||
if !syscall.is_page_aligned(storage_ptr) || !syscall.is_page_aligned(storage_len) {
|
||||
return Err(StorageError::CustomError);
|
||||
}
|
||||
@@ -187,7 +206,9 @@ impl TockStorage {
|
||||
}
|
||||
}
|
||||
|
||||
impl Storage for TockStorage {
|
||||
impl<S: Syscalls, C: platform::subscribe::Config + platform::allow_ro::Config> Storage
|
||||
for TockStorage<S, C>
|
||||
{
|
||||
fn word_size(&self) -> usize {
|
||||
self.word_size
|
||||
}
|
||||
@@ -218,26 +239,35 @@ impl Storage for TockStorage {
|
||||
return Err(StorageError::NotAligned);
|
||||
}
|
||||
let ptr = self.read_slice(index, value.len())?.as_ptr() as usize;
|
||||
write_slice(ptr, value)
|
||||
write_slice::<S, C>(ptr, value)
|
||||
}
|
||||
|
||||
fn erase_page(&mut self, page: usize) -> StorageResult<()> {
|
||||
let index = StorageIndex { page, byte: 0 };
|
||||
let length = self.page_size();
|
||||
let ptr = self.read_slice(index, length)?.as_ptr() as usize;
|
||||
erase_page(ptr, length)
|
||||
erase_page::<S, C>(ptr, length)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TockUpgradeStorage {
|
||||
pub struct TockUpgradeStorage<
|
||||
S: Syscalls,
|
||||
C: platform::subscribe::Config + platform::allow_ro::Config,
|
||||
> {
|
||||
page_size: usize,
|
||||
partition: Partition,
|
||||
metadata: ModRange,
|
||||
running_metadata: ModRange,
|
||||
identifier: u32,
|
||||
s: PhantomData<S>,
|
||||
c: PhantomData<C>,
|
||||
}
|
||||
|
||||
impl TockUpgradeStorage {
|
||||
impl<S, C> TockUpgradeStorage<S, C>
|
||||
where
|
||||
S: Syscalls,
|
||||
C: platform::allow_ro::Config + platform::subscribe::Config,
|
||||
{
|
||||
// Ideally, the kernel should tell us metadata and partitions directly.
|
||||
// This code only works for one layout, refactor this into the storage driver to support more.
|
||||
const METADATA_ADDRESS: usize = 0x4000;
|
||||
@@ -258,25 +288,27 @@ impl TockUpgradeStorage {
|
||||
/// Returns a `NotAligned` error if partitions or metadata ranges are
|
||||
/// - not exclusive or,
|
||||
/// - not consecutive.
|
||||
pub fn new() -> StorageResult<TockUpgradeStorage> {
|
||||
pub fn new() -> StorageResult<TockUpgradeStorage<S, C>> {
|
||||
let mut locations = TockUpgradeStorage {
|
||||
page_size: get_info(command_nr::get_info_nr::PAGE_SIZE, 0)?,
|
||||
page_size: get_info::<S>(command_nr::get_info_nr::PAGE_SIZE, 0)? as usize,
|
||||
partition: Partition::default(),
|
||||
metadata: ModRange::new_empty(),
|
||||
running_metadata: ModRange::new_empty(),
|
||||
identifier: Self::PARTITION_ADDRESS_A as u32,
|
||||
s: PhantomData,
|
||||
c: PhantomData,
|
||||
};
|
||||
if !locations.page_size.is_power_of_two() {
|
||||
return Err(StorageError::CustomError);
|
||||
}
|
||||
let mut firmware_range = ModRange::new_empty();
|
||||
for i in 0..memop(memop_nr::STORAGE_CNT, 0)? {
|
||||
let storage_type = memop(memop_nr::STORAGE_TYPE, i)?;
|
||||
for i in 0..memop::<S>(memop_nr::STORAGE_CNT, 0)? {
|
||||
let storage_type = memop::<S>(memop_nr::STORAGE_TYPE, i)?;
|
||||
if !matches!(storage_type, storage_type::PARTITION) {
|
||||
continue;
|
||||
};
|
||||
let storage_ptr = memop(memop_nr::STORAGE_PTR, i)?;
|
||||
let storage_len = memop(memop_nr::STORAGE_LEN, i)?;
|
||||
let storage_ptr = memop::<S>(memop_nr::STORAGE_PTR, i)? as usize;
|
||||
let storage_len = memop::<S>(memop_nr::STORAGE_LEN, i)? as usize;
|
||||
if !locations.is_page_aligned(storage_ptr) || !locations.is_page_aligned(storage_len) {
|
||||
return Err(StorageError::CustomError);
|
||||
}
|
||||
@@ -337,7 +369,7 @@ impl TockUpgradeStorage {
|
||||
/// Checks if the metadata's hash matches the partition's content.
|
||||
fn check_partition_hash(&self, metadata: &[u8]) -> StorageResult<()> {
|
||||
let start_address = self.metadata.start() + METADATA_SIGN_OFFSET;
|
||||
let mut hasher = Sha::<TockEnv>::new();
|
||||
let mut hasher = Sha::<TockEnv<S>>::new();
|
||||
for range in self.partition.ranges_from(start_address) {
|
||||
let partition_slice = unsafe { read_slice(range.start(), range.length()) };
|
||||
// The hash implementation handles this in chunks, so no memory issues.
|
||||
@@ -362,15 +394,15 @@ impl TockUpgradeStorage {
|
||||
let write_range = ModRange::new(address, data.len());
|
||||
if self.contains_metadata(&write_range)? {
|
||||
let new_metadata = &data[self.metadata.start() - address..][..self.metadata.length()];
|
||||
check_metadata::<TockEnv>(self, UPGRADE_PUBLIC_KEY, new_metadata)?;
|
||||
check_metadata::<TockEnv<S, C>, S, C>(self, UPGRADE_PUBLIC_KEY, new_metadata)?;
|
||||
}
|
||||
|
||||
// Erases all pages that have their first byte in the write range.
|
||||
// Since we expect calls in order, we don't want to erase half-written pages.
|
||||
for address in write_range.aligned_iter(self.page_size) {
|
||||
erase_page(address, self.page_size)?;
|
||||
erase_page::<S, C>(address, self.page_size)?;
|
||||
}
|
||||
write_slice(address, &data)?;
|
||||
write_slice::<S, C>(address, &data)?;
|
||||
let written_slice = unsafe { read_slice(address, data.len()) };
|
||||
if written_slice != data {
|
||||
return Err(StorageError::CustomError);
|
||||
@@ -378,7 +410,7 @@ impl TockUpgradeStorage {
|
||||
// Case: Last slice is written.
|
||||
if data.len() == self.partition.length() - offset {
|
||||
let metadata = unsafe { read_slice(self.metadata.start(), self.metadata.length()) };
|
||||
self.check_partition_hash(&metadata)?;
|
||||
self.check_partition_hash(metadata)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
40
src/env/tock/upgrade_helper.rs
vendored
40
src/env/tock/upgrade_helper.rs
vendored
@@ -21,6 +21,8 @@ use crate::env::tock::buffer_upgrade_storage::BufferUpgradeStorage;
|
||||
use crate::env::tock::storage::TockUpgradeStorage;
|
||||
use arrayref::array_ref;
|
||||
use byteorder::{ByteOrder, LittleEndian};
|
||||
use libtock_platform as platform;
|
||||
use libtock_platform::Syscalls;
|
||||
use opensk::api::crypto::ecdsa::{PublicKey as _, Signature as _};
|
||||
use opensk::env::{EcdsaPk, EcdsaSignature, Env};
|
||||
use persistent_store::{StorageError, StorageResult};
|
||||
@@ -39,9 +41,13 @@ pub const METADATA_SIGN_OFFSET: usize = 0x800;
|
||||
///
|
||||
/// Checks signature correctness against the hash, and whether the partition offset matches.
|
||||
/// Whether the hash matches the partition content is not tested here!
|
||||
pub fn check_metadata<E: Env>(
|
||||
#[cfg(not(feature = "std"))] upgrade_locations: &TockUpgradeStorage,
|
||||
#[cfg(feature = "std")] upgrade_locations: &BufferUpgradeStorage,
|
||||
pub fn check_metadata<
|
||||
E: Env,
|
||||
S: Syscalls,
|
||||
C: platform::subscribe::Config + platform::allow_ro::Config,
|
||||
>(
|
||||
#[cfg(not(feature = "std"))] upgrade_locations: &TockUpgradeStorage<S, C>,
|
||||
#[cfg(feature = "std")] upgrade_locations: &BufferUpgradeStorage<S, C>,
|
||||
public_key_bytes: &[u8],
|
||||
metadata: &[u8],
|
||||
) -> StorageResult<()> {
|
||||
@@ -104,11 +110,13 @@ fn verify_signature<E: Env>(
|
||||
mod test {
|
||||
use super::*;
|
||||
use arrayref::mut_array_refs;
|
||||
use libtock_unittest::fake::Syscalls;
|
||||
use opensk::api::crypto::ecdsa::SecretKey as _;
|
||||
use opensk::api::crypto::sha256::Sha256;
|
||||
use opensk::api::crypto::{EC_FIELD_SIZE, EC_SIGNATURE_SIZE};
|
||||
use opensk::env::test::TestEnv;
|
||||
use opensk::env::{EcdsaSk, Sha};
|
||||
use platform::DefaultConfig;
|
||||
|
||||
fn to_uncompressed(public_key: &EcdsaPk<TestEnv>) -> [u8; 1 + 2 * EC_FIELD_SIZE] {
|
||||
// Formatting according to:
|
||||
@@ -147,20 +155,28 @@ mod test {
|
||||
let public_key_bytes = to_uncompressed(&public_key);
|
||||
|
||||
assert_eq!(
|
||||
check_metadata::<TestEnv>(&upgrade_locations, &public_key_bytes, &metadata),
|
||||
check_metadata::<TestEnv, Syscalls, DefaultConfig>(
|
||||
&upgrade_locations,
|
||||
&public_key_bytes,
|
||||
&metadata
|
||||
),
|
||||
Ok(())
|
||||
);
|
||||
|
||||
// Manipulating the partition address fails.
|
||||
metadata[METADATA_SIGN_OFFSET + 8] = 0x88;
|
||||
assert_eq!(
|
||||
check_metadata::<TestEnv>(&upgrade_locations, &public_key_bytes, &metadata),
|
||||
check_metadata::<TestEnv, Syscalls, DefaultConfig>(
|
||||
&upgrade_locations,
|
||||
&public_key_bytes,
|
||||
&metadata
|
||||
),
|
||||
Err(StorageError::CustomError)
|
||||
);
|
||||
metadata[METADATA_SIGN_OFFSET + 8] = 0x00;
|
||||
// Wrong metadata length fails.
|
||||
assert_eq!(
|
||||
check_metadata::<TestEnv>(
|
||||
check_metadata::<TestEnv, Syscalls, DefaultConfig>(
|
||||
&upgrade_locations,
|
||||
&public_key_bytes,
|
||||
&metadata[..METADATA_LEN - 1]
|
||||
@@ -170,14 +186,22 @@ mod test {
|
||||
// Manipulating the hash fails.
|
||||
metadata[0] ^= 0x01;
|
||||
assert_eq!(
|
||||
check_metadata::<TestEnv>(&upgrade_locations, &public_key_bytes, &metadata),
|
||||
check_metadata::<TestEnv, Syscalls, DefaultConfig>(
|
||||
&upgrade_locations,
|
||||
&public_key_bytes,
|
||||
&metadata
|
||||
),
|
||||
Err(StorageError::CustomError)
|
||||
);
|
||||
metadata[0] ^= 0x01;
|
||||
// Manipulating the signature fails.
|
||||
metadata[32] ^= 0x01;
|
||||
assert_eq!(
|
||||
check_metadata::<TestEnv>(&upgrade_locations, &public_key_bytes, &metadata),
|
||||
check_metadata::<TestEnv, Syscalls, DefaultConfig>(
|
||||
&upgrade_locations,
|
||||
&public_key_bytes,
|
||||
&metadata
|
||||
),
|
||||
Err(StorageError::CustomError)
|
||||
);
|
||||
}
|
||||
|
||||
168
src/main.rs
168
src/main.rs
@@ -13,6 +13,7 @@
|
||||
// limitations under the License.
|
||||
|
||||
#![cfg_attr(not(feature = "std"), no_std)]
|
||||
#![cfg_attr(not(feature = "std"), no_main)]
|
||||
|
||||
extern crate alloc;
|
||||
extern crate arrayref;
|
||||
@@ -21,8 +22,6 @@ extern crate byteorder;
|
||||
extern crate core;
|
||||
extern crate lang_items;
|
||||
|
||||
#[cfg(feature = "with_ctap1")]
|
||||
use core::cell::Cell;
|
||||
use core::convert::TryFrom;
|
||||
#[cfg(feature = "debug_ctap")]
|
||||
use core::fmt::Write;
|
||||
@@ -30,22 +29,31 @@ use core::fmt::Write;
|
||||
use ctap2::env::tock::blink_leds;
|
||||
use ctap2::env::tock::{switch_off_leds, wink_leds, TockEnv};
|
||||
#[cfg(feature = "with_ctap1")]
|
||||
use libtock_drivers::buttons::{self, ButtonState};
|
||||
use libtock_buttons::Buttons;
|
||||
#[cfg(feature = "debug_ctap")]
|
||||
use libtock_drivers::console::Console;
|
||||
use libtock_console::Console;
|
||||
#[cfg(feature = "debug_ctap")]
|
||||
use libtock_console::ConsoleWriter;
|
||||
use libtock_drivers::result::FlexUnwrap;
|
||||
use libtock_drivers::timer::Duration;
|
||||
use libtock_drivers::usb_ctap_hid;
|
||||
#[cfg(not(feature = "std"))]
|
||||
use libtock_runtime::{set_main, stack_size, TockSyscalls};
|
||||
#[cfg(feature = "std")]
|
||||
use libtock_unittest::fake;
|
||||
use opensk::api::clock::Clock;
|
||||
use opensk::api::connection::{HidConnection, SendOrRecvStatus, UsbEndpoint};
|
||||
use opensk::api::connection::UsbEndpoint;
|
||||
use opensk::ctap::hid::HidPacketIterator;
|
||||
use opensk::ctap::KEEPALIVE_DELAY_MS;
|
||||
use opensk::env::Env;
|
||||
use opensk::Transport;
|
||||
|
||||
libtock_core::stack_size! {0x4000}
|
||||
#[cfg(not(feature = "std"))]
|
||||
stack_size! {0x4000}
|
||||
#[cfg(not(feature = "std"))]
|
||||
set_main! {main}
|
||||
|
||||
const SEND_TIMEOUT_MS: usize = 1000;
|
||||
const SEND_TIMEOUT_MS: Duration<isize> = Duration::from_ms(1000);
|
||||
const KEEPALIVE_DELAY_MS_TOCK: Duration<isize> = Duration::from_ms(KEEPALIVE_DELAY_MS as isize);
|
||||
|
||||
#[cfg(not(feature = "vendor_hid"))]
|
||||
@@ -56,19 +64,18 @@ const NUM_ENDPOINTS: usize = 2;
|
||||
// The reply/replies that are queued for each endpoint.
|
||||
struct EndpointReply {
|
||||
endpoint: UsbEndpoint,
|
||||
transport: Transport,
|
||||
reply: HidPacketIterator,
|
||||
}
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
type SyscallImplementation = fake::Syscalls;
|
||||
#[cfg(not(feature = "std"))]
|
||||
type SyscallImplementation = TockSyscalls;
|
||||
|
||||
impl EndpointReply {
|
||||
pub fn new(endpoint: UsbEndpoint) -> Self {
|
||||
EndpointReply {
|
||||
endpoint,
|
||||
transport: match endpoint {
|
||||
UsbEndpoint::MainHid => Transport::MainHid,
|
||||
#[cfg(feature = "vendor_hid")]
|
||||
UsbEndpoint::VendorHid => Transport::VendorHid,
|
||||
},
|
||||
reply: HidPacketIterator::none(),
|
||||
}
|
||||
}
|
||||
@@ -76,7 +83,7 @@ impl EndpointReply {
|
||||
|
||||
// A single packet to send.
|
||||
struct SendPacket {
|
||||
transport: Transport,
|
||||
endpoint: UsbEndpoint,
|
||||
packet: [u8; 64],
|
||||
}
|
||||
|
||||
@@ -99,97 +106,103 @@ impl EndpointReplies {
|
||||
for ep in self.replies.iter_mut() {
|
||||
if let Some(packet) = ep.reply.next() {
|
||||
return Some(SendPacket {
|
||||
transport: ep.transport,
|
||||
endpoint: ep.endpoint,
|
||||
packet,
|
||||
});
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn clear(&mut self, endpoint: UsbEndpoint) {
|
||||
for ep in self.replies.iter_mut() {
|
||||
if ep.endpoint == endpoint {
|
||||
*ep = EndpointReply::new(endpoint);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
#[cfg(feature = "debug_ctap")]
|
||||
let mut writer = Console::<SyscallImplementation>::writer();
|
||||
#[cfg(feature = "debug_ctap")]
|
||||
{
|
||||
writeln!(writer, "Hello world from OpenSK!").ok().unwrap();
|
||||
}
|
||||
// Setup USB driver.
|
||||
if !usb_ctap_hid::setup() {
|
||||
if !usb_ctap_hid::UsbCtapHid::<SyscallImplementation>::setup() {
|
||||
panic!("Cannot setup USB driver");
|
||||
}
|
||||
|
||||
let env = TockEnv::default();
|
||||
let env = TockEnv::<SyscallImplementation>::default();
|
||||
let mut ctap = opensk::Ctap::new(env);
|
||||
|
||||
let mut led_counter = 0;
|
||||
let mut led_blink_timer = <<TockEnv as Env>::Clock as Clock>::Timer::default();
|
||||
let mut led_blink_timer =
|
||||
<<TockEnv<SyscallImplementation> as Env>::Clock as Clock>::Timer::default();
|
||||
|
||||
let mut replies = EndpointReplies::new();
|
||||
|
||||
// Main loop. If CTAP1 is used, we register button presses for U2F while receiving and waiting.
|
||||
// The way TockOS and apps currently interact, callbacks need a yield syscall to execute,
|
||||
// making consistent blinking patterns and sending keepalives harder.
|
||||
|
||||
#[cfg(feature = "debug_ctap")]
|
||||
writeln!(writer, "Entering main ctap loop").unwrap();
|
||||
loop {
|
||||
// Create the button callback, used for CTAP1.
|
||||
#[cfg(feature = "with_ctap1")]
|
||||
let button_touched = Cell::new(false);
|
||||
#[cfg(feature = "with_ctap1")]
|
||||
let mut buttons_callback = buttons::with_callback(|_button_num, state| {
|
||||
match state {
|
||||
ButtonState::Pressed => button_touched.set(true),
|
||||
ButtonState::Released => (),
|
||||
};
|
||||
});
|
||||
#[cfg(feature = "with_ctap1")]
|
||||
let mut buttons = buttons_callback.init().flex_unwrap();
|
||||
// At the moment, all buttons are accepted. You can customize your setup here.
|
||||
#[cfg(feature = "with_ctap1")]
|
||||
for mut button in &mut buttons {
|
||||
button.enable().flex_unwrap();
|
||||
}
|
||||
let num_buttons = Buttons::<SyscallImplementation>::count().ok().unwrap();
|
||||
|
||||
// Variable for use in both the send_and_maybe_recv and recv cases.
|
||||
let mut usb_endpoint: Option<UsbEndpoint> = None;
|
||||
let mut pkt_request = [0; 64];
|
||||
|
||||
if let Some(mut packet) = replies.next_packet() {
|
||||
// send and receive.
|
||||
let hid_connection = packet.transport.hid_connection(ctap.env());
|
||||
match hid_connection.send_and_maybe_recv(&mut packet.packet, SEND_TIMEOUT_MS) {
|
||||
Ok(SendOrRecvStatus::Timeout) => {
|
||||
if let Some(packet) = replies.next_packet() {
|
||||
match usb_ctap_hid::UsbCtapHid::<SyscallImplementation>::send(
|
||||
&packet.packet,
|
||||
SEND_TIMEOUT_MS,
|
||||
packet.endpoint as u32,
|
||||
)
|
||||
.flex_unwrap()
|
||||
{
|
||||
usb_ctap_hid::SendOrRecvStatus::Sent => {
|
||||
#[cfg(feature = "debug_ctap")]
|
||||
print_packet_notice(
|
||||
"Sending packet timed out",
|
||||
print_packet_notice::<SyscallImplementation>(
|
||||
"Sent packet",
|
||||
ctap.env().clock().timestamp_us(),
|
||||
&mut writer,
|
||||
);
|
||||
// TODO: reset the ctap_hid state.
|
||||
// Since sending the packet timed out, we cancel this reply.
|
||||
break;
|
||||
}
|
||||
Ok(SendOrRecvStatus::Sent) => {
|
||||
usb_ctap_hid::SendOrRecvStatus::Timeout => {
|
||||
#[cfg(feature = "debug_ctap")]
|
||||
print_packet_notice("Sent packet", ctap.env().clock().timestamp_us());
|
||||
}
|
||||
Ok(SendOrRecvStatus::Received(ep)) => {
|
||||
#[cfg(feature = "debug_ctap")]
|
||||
print_packet_notice(
|
||||
"Received another packet",
|
||||
print_packet_notice::<SyscallImplementation>(
|
||||
"Timeout while sending packet",
|
||||
ctap.env().clock().timestamp_us(),
|
||||
&mut writer,
|
||||
);
|
||||
usb_endpoint = Some(ep);
|
||||
|
||||
// Copy to incoming packet to local buffer to be consistent
|
||||
// with the receive flow.
|
||||
pkt_request = packet.packet;
|
||||
// The client is unresponsive, so we discard all pending packets.
|
||||
replies.clear(packet.endpoint);
|
||||
}
|
||||
Err(_) => panic!("Error sending packet"),
|
||||
}
|
||||
_ => panic!("Unexpected status on USB transmission"),
|
||||
};
|
||||
} else {
|
||||
// receive
|
||||
usb_endpoint =
|
||||
match usb_ctap_hid::recv_with_timeout(&mut pkt_request, KEEPALIVE_DELAY_MS_TOCK)
|
||||
.flex_unwrap()
|
||||
match usb_ctap_hid::UsbCtapHid::<SyscallImplementation>::recv_with_timeout(
|
||||
&mut pkt_request,
|
||||
KEEPALIVE_DELAY_MS_TOCK,
|
||||
)
|
||||
.flex_unwrap()
|
||||
{
|
||||
usb_ctap_hid::SendOrRecvStatus::Received(endpoint) => {
|
||||
#[cfg(feature = "debug_ctap")]
|
||||
print_packet_notice("Received packet", ctap.env().clock().timestamp_us());
|
||||
UsbEndpoint::try_from(endpoint).ok()
|
||||
print_packet_notice::<SyscallImplementation>(
|
||||
"Received packet",
|
||||
ctap.env().clock().timestamp_us(),
|
||||
&mut writer,
|
||||
);
|
||||
UsbEndpoint::try_from(endpoint as usize).ok()
|
||||
}
|
||||
usb_ctap_hid::SendOrRecvStatus::Sent => {
|
||||
panic!("Returned transmit status on receive")
|
||||
@@ -200,17 +213,10 @@ fn main() {
|
||||
|
||||
#[cfg(feature = "with_ctap1")]
|
||||
{
|
||||
if button_touched.get() {
|
||||
let button_touched = (0..num_buttons).any(Buttons::<SyscallImplementation>::is_pressed);
|
||||
if button_touched {
|
||||
ctap.u2f_grant_user_presence();
|
||||
}
|
||||
// Cleanup button callbacks. We miss button presses while processing though.
|
||||
// Heavy computation mostly follows a registered touch luckily. Unregistering
|
||||
// callbacks is important to not clash with those from check_user_presence.
|
||||
for mut button in &mut buttons {
|
||||
button.disable().flex_unwrap();
|
||||
}
|
||||
drop(buttons);
|
||||
drop(buttons_callback);
|
||||
}
|
||||
|
||||
// This call is making sure that even for long inactivity, wrapping clock values
|
||||
@@ -231,7 +237,7 @@ fn main() {
|
||||
if ep.reply.has_data() {
|
||||
#[cfg(feature = "debug_ctap")]
|
||||
writeln!(
|
||||
Console::new(),
|
||||
Console::<SyscallImplementation>::writer(),
|
||||
"Warning overwriting existing reply for endpoint {}",
|
||||
endpoint as usize
|
||||
)
|
||||
@@ -252,26 +258,30 @@ fn main() {
|
||||
}
|
||||
|
||||
if ctap.should_wink() {
|
||||
wink_leds(led_counter);
|
||||
wink_leds::<SyscallImplementation>(led_counter);
|
||||
} else {
|
||||
#[cfg(not(feature = "with_ctap1"))]
|
||||
switch_off_leds();
|
||||
switch_off_leds::<SyscallImplementation>();
|
||||
#[cfg(feature = "with_ctap1")]
|
||||
if ctap.u2f_needs_user_presence() {
|
||||
// Flash the LEDs with an almost regular pattern. The inaccuracy comes from
|
||||
// delay caused by processing and sending of packets.
|
||||
blink_leds(led_counter);
|
||||
blink_leds::<SyscallImplementation>(led_counter);
|
||||
} else {
|
||||
switch_off_leds();
|
||||
switch_off_leds::<SyscallImplementation>();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "debug_ctap")]
|
||||
fn print_packet_notice(notice_text: &str, now_us: usize) {
|
||||
fn print_packet_notice<S: libtock_platform::Syscalls>(
|
||||
notice_text: &str,
|
||||
now_us: usize,
|
||||
writer: &mut ConsoleWriter<S>,
|
||||
) {
|
||||
writeln!(
|
||||
Console::new(),
|
||||
writer,
|
||||
"{} at {}.{:06} s",
|
||||
notice_text,
|
||||
now_us / 1_000_000,
|
||||
|
||||
Reference in New Issue
Block a user