parent
47e2193e3b
commit
e8f5eb97d3
@ -0,0 +1,132 @@
|
||||
//! Tiny last-recently-used (LRU) cache.
|
||||
//!
|
||||
//! Heavily inspired by [uluru](https://github.com/servo/uluru) crate, with
|
||||
//! minor changes (using the `heapless` crate for the backing vector, and
|
||||
//! `u8` index type).
|
||||
|
||||
use core::mem;
|
||||
|
||||
use heapless::Vec;
|
||||
|
||||
pub struct LruCache<T, const N: usize> {
|
||||
/// Values are stored together with indices of a doubly-linked list.
|
||||
entries: Vec<Entry<T>, N>,
|
||||
/// Points to the most-recently-used entry in `entries`.
|
||||
head: u8,
|
||||
/// Points to the least-recently-used entry in `entries`.
|
||||
tail: u8,
|
||||
}
|
||||
|
||||
struct Entry<T> {
|
||||
val: T,
|
||||
/// Index of previous (more recently used) entry in the linked-list.
|
||||
prev: u8,
|
||||
/// Index of next (less recently used) entry in the linked-list.
|
||||
next: u8,
|
||||
}
|
||||
|
||||
impl<T, const N: usize> Default for LruCache<T, N> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, const N: usize> LruCache<T, N> {
|
||||
/// Create a new, empty, LRU cache.
|
||||
pub const fn new() -> Self {
|
||||
debug_assert!(N < u8::MAX as usize, "Capacity overflow");
|
||||
|
||||
Self {
|
||||
entries: Vec::new(),
|
||||
head: 0,
|
||||
tail: 0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, val: T) -> Option<T> {
|
||||
let entry = Entry {
|
||||
val,
|
||||
prev: 0,
|
||||
next: 0,
|
||||
};
|
||||
if self.is_full() {
|
||||
let tail = self.pop_tail();
|
||||
let prev = mem::replace(&mut self.entries[tail as usize], entry);
|
||||
self.push_head(tail);
|
||||
Some(prev.val)
|
||||
} else {
|
||||
let _ = self.entries.push(entry);
|
||||
self.push_head((self.len() - 1) as u8);
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn find(&mut self, mut pred: impl FnMut(&T) -> bool) -> Option<&mut T> {
|
||||
for (i, entry) in self.entries.iter().enumerate() {
|
||||
if pred(&entry.val) {
|
||||
self.touch_index(i as u8);
|
||||
return self.front_mut();
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn front(&self) -> Option<&T> {
|
||||
self.entries.get(self.head as usize).map(|e| &e.val)
|
||||
}
|
||||
|
||||
pub fn front_mut(&mut self) -> Option<&mut T> {
|
||||
self.entries.get_mut(self.head as usize).map(|e| &mut e.val)
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.entries.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.entries.is_empty()
|
||||
}
|
||||
|
||||
pub fn is_full(&self) -> bool {
|
||||
self.entries.is_full()
|
||||
}
|
||||
|
||||
fn touch_index(&mut self, i: u8) {
|
||||
if i != self.head {
|
||||
self.remove(i);
|
||||
self.push_head(i);
|
||||
}
|
||||
}
|
||||
|
||||
fn remove(&mut self, i: u8) {
|
||||
let prev = self.entries[i as usize].prev;
|
||||
let next = self.entries[i as usize].next;
|
||||
if i == self.head {
|
||||
self.head = next;
|
||||
} else {
|
||||
self.entries[prev as usize].next = next;
|
||||
}
|
||||
if i == self.tail {
|
||||
self.tail = prev;
|
||||
} else {
|
||||
self.entries[next as usize].prev = prev;
|
||||
}
|
||||
}
|
||||
|
||||
fn push_head(&mut self, i: u8) {
|
||||
if self.entries.len() == 1 {
|
||||
self.tail = i;
|
||||
} else {
|
||||
self.entries[i as usize].next = self.head;
|
||||
self.entries[self.head as usize].prev = i;
|
||||
}
|
||||
self.head = i;
|
||||
}
|
||||
|
||||
fn pop_tail(&mut self) -> u8 {
|
||||
let old_tail = self.tail;
|
||||
let new_tail = self.entries[old_tail as usize].prev;
|
||||
self.tail = new_tail;
|
||||
old_tail
|
||||
}
|
||||
}
|
@ -0,0 +1,149 @@
|
||||
use core::ops::Deref;
|
||||
|
||||
use spin::{Mutex, MutexGuard};
|
||||
use zeroize::Zeroizing;
|
||||
|
||||
use crate::{lru::LruCache, trezorhal::random};
|
||||
|
||||
pub enum CacheError {
|
||||
InvalidSessionId,
|
||||
InvalidValue,
|
||||
}
|
||||
|
||||
pub struct Cache {
|
||||
sessions: LruCache<SessionCache, 10>,
|
||||
active_id: Option<SessionId>,
|
||||
}
|
||||
|
||||
impl Cache {
|
||||
pub const fn new() -> Self {
|
||||
Self {
|
||||
sessions: LruCache::new(),
|
||||
active_id: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn global() -> MutexGuard<'static, Self> {
|
||||
static GLOBAL_CACHE: Mutex<Cache> = Mutex::new(Cache::new());
|
||||
|
||||
GLOBAL_CACHE.lock()
|
||||
}
|
||||
|
||||
pub fn start_session(&mut self, received_session_id_bytes: Option<&[u8]>) -> SessionId {
|
||||
// If we have received a session ID, take a look to the cache and return an ID
|
||||
// of existing entry.
|
||||
let received_session_id =
|
||||
received_session_id_bytes.and_then(|bytes| SessionId::try_from(bytes).ok());
|
||||
if let Some(received_id) = received_session_id {
|
||||
if self.sessions.find(|s| s.id == Some(received_id)).is_some() {
|
||||
return received_id;
|
||||
}
|
||||
}
|
||||
// Either we haven't received an ID, or the entry doesn't exist (because of
|
||||
// previous eviction). Create a new session.
|
||||
let id = SessionId::random();
|
||||
self.sessions.insert(SessionCache::new(id));
|
||||
self.active_id = Some(id);
|
||||
id
|
||||
}
|
||||
|
||||
pub fn end_session(&mut self) {
|
||||
if let Some(active_id) = self.active_id {
|
||||
if let Some(active_session) = self.sessions.find(|s| s.id == Some(active_id)) {
|
||||
active_session.clear();
|
||||
}
|
||||
self.active_id = None;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_active_session(&self) -> bool {
|
||||
self.active_id.is_some()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct SessionCache {
|
||||
id: Option<SessionId>,
|
||||
fields: Option<SessionFields>,
|
||||
}
|
||||
|
||||
impl SessionCache {
|
||||
fn new(id: SessionId) -> Self {
|
||||
Self {
|
||||
id: Some(id),
|
||||
fields: Some(SessionFields::default()),
|
||||
}
|
||||
}
|
||||
|
||||
fn clear(&mut self) {
|
||||
self.id = None;
|
||||
self.fields = None;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||
pub struct SessionId([u8; Self::LENGTH]);
|
||||
|
||||
impl SessionId {
|
||||
const LENGTH: usize = 32;
|
||||
|
||||
fn random() -> Self {
|
||||
let mut id = [0; Self::LENGTH];
|
||||
random::bytes(&mut id);
|
||||
Self(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for SessionId {
|
||||
type Target = [u8; Self::LENGTH];
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for SessionId {
|
||||
type Error = CacheError;
|
||||
|
||||
fn try_from(value: &[u8]) -> Result<Self, Self::Error> {
|
||||
Ok(Self(
|
||||
value
|
||||
.try_into()
|
||||
.map_err(|_| Self::Error::InvalidSessionId)?,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct SessionFields {
|
||||
seed: Field<64>,
|
||||
auth_type: Field<2>,
|
||||
auth_data: Field<128>,
|
||||
nonce: Field<32>,
|
||||
|
||||
#[cfg(not(feature = "bitcoin_only"))]
|
||||
derive_cardano: Field<1>,
|
||||
#[cfg(not(feature = "bitcoin_only"))]
|
||||
cardano_icarus_secret: Field<96>,
|
||||
#[cfg(not(feature = "bitcoin_only"))]
|
||||
cardano_icarus_trezor_secret: Field<96>,
|
||||
#[cfg(not(feature = "bitcoin_only"))]
|
||||
monero_live_refresh: Field<1>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Field<const N: usize> {
|
||||
data: Option<Zeroizing<[u8; N]>>,
|
||||
}
|
||||
|
||||
impl<const N: usize> Field<N> {
|
||||
fn set(&mut self, value: &[u8]) -> Result<(), CacheError> {
|
||||
self.data = Some(Zeroizing::new(
|
||||
value.try_into().map_err(|_| CacheError::InvalidValue)?,
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn unset(&mut self) {
|
||||
self.data = None;
|
||||
}
|
||||
}
|
@ -0,0 +1 @@
|
||||
mod cache;
|
Loading…
Reference in new issue