use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use anyhow::Result;
use async_lock::Mutex;
use crate::libsignal::protocol::{ProtocolAddress, SenderKeyRecord, SessionRecord};
use crate::libsignal::store::sender_key_name::SenderKeyName;
use crate::store::traits::SignalStore;
fn evict_clean_entries<V>(
cache: &mut HashMap<Arc<str>, Option<V>>,
dirty: &HashSet<Arc<str>>,
deleted: Option<&HashSet<Arc<str>>>,
max_entries: usize,
) {
let overflow = cache.len().saturating_sub(max_entries);
if overflow == 0 {
return;
}
let mut negative = Vec::with_capacity(overflow);
let mut positive = Vec::with_capacity(overflow);
for (k, v) in cache.iter() {
if dirty.contains(k.as_ref()) {
continue;
}
if let Some(del) = deleted
&& del.contains(k.as_ref())
{
continue;
}
if v.is_none() {
negative.push(k.clone());
} else {
positive.push(k.clone());
}
}
for key in negative.into_iter().chain(positive).take(overflow) {
cache.remove(&key);
}
}
const DEFAULT_MAX_CACHE_ENTRIES: usize = 2_000;
pub struct SignalStoreCache {
sessions: Mutex<SessionStoreState>,
identities: Mutex<ByteStoreState>,
sender_keys: Mutex<SenderKeyStoreState>,
flush_encode_buf: Mutex<Vec<u8>>,
max_entries: usize,
}
enum SessionEntry {
Present(Box<SessionRecord>),
Absent,
CheckedOut,
}
struct SessionStoreState {
cache: HashMap<Arc<str>, SessionEntry>,
dirty: HashSet<Arc<str>>,
deleted: HashSet<Arc<str>>,
}
impl SessionStoreState {
fn new() -> Self {
Self {
cache: HashMap::new(),
dirty: HashSet::new(),
deleted: HashSet::new(),
}
}
fn key_for(&self, address: &str) -> Arc<str> {
match self.cache.get_key_value(address) {
Some((existing, _)) => existing.clone(),
None => Arc::from(address),
}
}
fn put(&mut self, address: &str, record: SessionRecord) {
let addr = self.key_for(address);
self.cache
.insert(addr.clone(), SessionEntry::Present(Box::new(record)));
self.dirty.insert(addr.clone());
self.deleted.remove(&addr);
}
fn delete(&mut self, address: &str) {
let addr = self.key_for(address);
self.cache.insert(addr.clone(), SessionEntry::Absent);
self.deleted.insert(addr.clone());
self.dirty.remove(&addr);
}
fn clear(&mut self) {
self.cache.clear();
self.dirty.clear();
self.deleted.clear();
}
fn evict_if_needed(&mut self, max_entries: usize) {
let overflow = self.cache.len().saturating_sub(max_entries);
if overflow == 0 {
return;
}
let mut negative = Vec::with_capacity(overflow);
let mut positive = Vec::with_capacity(overflow);
for (k, v) in self.cache.iter() {
if self.dirty.contains(k.as_ref()) || self.deleted.contains(k.as_ref()) {
continue;
}
match v {
SessionEntry::CheckedOut => continue, SessionEntry::Absent => negative.push(k.clone()),
SessionEntry::Present(_) => positive.push(k.clone()),
}
}
for key in negative.into_iter().chain(positive).take(overflow) {
self.cache.remove(&key);
}
}
}
struct SenderKeyStoreState {
cache: HashMap<Arc<str>, Option<SenderKeyRecord>>,
dirty: HashSet<Arc<str>>,
}
impl SenderKeyStoreState {
fn new() -> Self {
Self {
cache: HashMap::new(),
dirty: HashSet::new(),
}
}
fn key_for(&self, address: &str) -> Arc<str> {
match self.cache.get_key_value(address) {
Some((existing, _)) => existing.clone(),
None => Arc::from(address),
}
}
fn put(&mut self, address: &str, record: SenderKeyRecord) {
let addr = self.key_for(address);
self.cache.insert(addr.clone(), Some(record));
self.dirty.insert(addr.clone());
}
fn delete(&mut self, address: &str) {
let addr = self.key_for(address);
self.cache.insert(addr.clone(), None);
self.dirty.insert(addr);
}
fn clear(&mut self) {
self.cache.clear();
self.dirty.clear();
}
fn evict_if_needed(&mut self, max_entries: usize) {
evict_clean_entries(&mut self.cache, &self.dirty, None, max_entries);
}
}
struct ByteStoreState {
cache: HashMap<Arc<str>, Option<Arc<[u8]>>>,
dirty: HashSet<Arc<str>>,
deleted: HashSet<Arc<str>>,
}
impl ByteStoreState {
fn new() -> Self {
Self {
cache: HashMap::new(),
dirty: HashSet::new(),
deleted: HashSet::new(),
}
}
fn key_for(&self, address: &str) -> Arc<str> {
match self.cache.get_key_value(address) {
Some((existing, _)) => existing.clone(),
None => Arc::from(address),
}
}
fn put_dedup(&mut self, address: &str, data: &[u8]) {
if let Some(Some(existing)) = self.cache.get(address)
&& existing.as_ref() == data
{
return;
}
self.put(address, data);
}
fn put(&mut self, address: &str, data: &[u8]) {
let addr = self.key_for(address);
self.cache.insert(addr.clone(), Some(Arc::from(data)));
self.dirty.insert(addr.clone());
self.deleted.remove(&addr);
}
fn delete(&mut self, address: &str) {
let addr = self.key_for(address);
self.cache.insert(addr.clone(), None);
self.deleted.insert(addr.clone());
self.dirty.remove(&addr);
}
fn clear(&mut self) {
self.cache.clear();
self.dirty.clear();
self.deleted.clear();
}
fn evict_if_needed(&mut self, max_entries: usize) {
evict_clean_entries(
&mut self.cache,
&self.dirty,
Some(&self.deleted),
max_entries,
);
}
}
impl Default for SignalStoreCache {
fn default() -> Self {
Self::new()
}
}
impl SignalStoreCache {
pub fn new() -> Self {
Self::with_max_entries(DEFAULT_MAX_CACHE_ENTRIES)
}
pub fn with_max_entries(max_entries: usize) -> Self {
Self {
sessions: Mutex::new(SessionStoreState::new()),
identities: Mutex::new(ByteStoreState::new()),
sender_keys: Mutex::new(SenderKeyStoreState::new()),
flush_encode_buf: Mutex::new(Vec::with_capacity(4096)),
max_entries,
}
}
pub async fn get_session(
&self,
address: &ProtocolAddress,
backend: &dyn SignalStore,
) -> Result<Option<SessionRecord>> {
let key = address.as_str();
{
let mut state = self.sessions.lock().await;
if let Some(entry) = state.cache.get_mut(key) {
if matches!(entry, SessionEntry::Present(_)) {
let SessionEntry::Present(record) =
std::mem::replace(entry, SessionEntry::CheckedOut)
else {
unreachable!()
};
return Ok(Some(*record));
}
return Ok(None);
}
}
let backend_result = backend.get_session(key).await?;
let mut state = self.sessions.lock().await;
match backend_result {
Some(bytes) => {
if state.cache.contains_key(key) {
return Ok(Some(SessionRecord::deserialize(&bytes)?));
}
let record = SessionRecord::deserialize(&bytes)?;
state.cache.insert(Arc::from(key), SessionEntry::CheckedOut);
state.evict_if_needed(self.max_entries);
Ok(Some(record))
}
None => {
if !state.cache.contains_key(key) {
state.cache.insert(Arc::from(key), SessionEntry::Absent);
state.evict_if_needed(self.max_entries);
}
Ok(None)
}
}
}
pub async fn peek_session(
&self,
address: &ProtocolAddress,
backend: &dyn SignalStore,
) -> Result<Option<SessionRecord>> {
let key = address.as_str();
{
let state = self.sessions.lock().await;
if let Some(entry) = state.cache.get(key) {
return match entry {
SessionEntry::Present(record) => Ok(Some((**record).clone())),
_ => Ok(None),
};
}
}
let backend_result = backend.get_session(key).await?;
let mut state = self.sessions.lock().await;
match backend_result {
Some(bytes) => {
let record = SessionRecord::deserialize(&bytes)?;
if !state.cache.contains_key(key) {
state.cache.insert(
Arc::from(key),
SessionEntry::Present(Box::new(record.clone())),
);
state.evict_if_needed(self.max_entries);
}
Ok(Some(record))
}
None => {
if !state.cache.contains_key(key) {
state.cache.insert(Arc::from(key), SessionEntry::Absent);
state.evict_if_needed(self.max_entries);
}
Ok(None)
}
}
}
pub async fn put_session(&self, address: &ProtocolAddress, record: SessionRecord) {
let mut state = self.sessions.lock().await;
state.put(address.as_str(), record);
state.evict_if_needed(self.max_entries);
}
pub async fn delete_session(&self, address: &ProtocolAddress) {
let mut state = self.sessions.lock().await;
state.delete(address.as_str());
state.evict_if_needed(self.max_entries);
}
pub async fn has_session(
&self,
address: &ProtocolAddress,
backend: &dyn SignalStore,
) -> Result<bool> {
let key = address.as_str();
{
let state = self.sessions.lock().await;
if let Some(entry) = state.cache.get(key) {
return Ok(!matches!(entry, SessionEntry::Absent));
}
}
let exists = backend.has_session(key).await?;
if !exists {
let mut state = self.sessions.lock().await;
if !state.cache.contains_key(key) {
state.cache.insert(Arc::from(key), SessionEntry::Absent);
state.evict_if_needed(self.max_entries);
}
}
Ok(exists)
}
pub async fn get_identity(
&self,
address: &ProtocolAddress,
backend: &dyn SignalStore,
) -> Result<Option<Arc<[u8]>>> {
let key = address.as_str();
{
let state = self.identities.lock().await;
if let Some(cached) = state.cache.get(key) {
return Ok(cached.clone());
}
}
let data = backend.load_identity(key).await?;
let arc_data = data.map(Arc::from);
let mut state = self.identities.lock().await;
if let Some(cached) = state.cache.get(key) {
return Ok(cached.clone());
}
state.cache.insert(Arc::from(key), arc_data.clone());
state.evict_if_needed(self.max_entries);
Ok(arc_data)
}
pub async fn put_identity(&self, address: &ProtocolAddress, data: &[u8]) {
let mut state = self.identities.lock().await;
state.put_dedup(address.as_str(), data);
state.evict_if_needed(self.max_entries);
}
pub async fn delete_identity(&self, address: &ProtocolAddress) {
let mut state = self.identities.lock().await;
state.delete(address.as_str());
state.evict_if_needed(self.max_entries);
}
pub async fn get_sender_key(
&self,
name: &SenderKeyName,
backend: &dyn SignalStore,
) -> Result<Option<SenderKeyRecord>> {
let key = name.cache_key();
let mut state = self.sender_keys.lock().await;
if let Some(cached) = state.cache.get(key) {
return Ok(cached.clone());
}
let record = match backend.get_sender_key(key).await? {
Some(bytes) => Some(SenderKeyRecord::deserialize(&bytes)?),
None => None,
};
state.cache.insert(Arc::from(key), record.clone());
state.evict_if_needed(self.max_entries);
Ok(record)
}
pub async fn put_sender_key(&self, name: &SenderKeyName, record: SenderKeyRecord) {
let mut state = self.sender_keys.lock().await;
state.put(name.cache_key(), record);
state.evict_if_needed(self.max_entries);
}
pub async fn delete_sender_key(&self, cache_key: &str) {
let mut state = self.sender_keys.lock().await;
state.delete(cache_key);
state.evict_if_needed(self.max_entries);
}
pub async fn flush(&self, backend: &dyn SignalStore) -> Result<()> {
{
let mut state = self.sessions.lock().await;
let dirty_keys: Vec<_> = state.dirty.iter().cloned().collect();
let deleted_keys: Vec<_> = state.deleted.iter().cloned().collect();
let mut encode_buf = self.flush_encode_buf.lock().await;
for address in &dirty_keys {
match state.cache.get(address.as_ref()) {
Some(SessionEntry::Present(record)) => {
record.serialize_into(&mut encode_buf);
backend.put_session(address, &encode_buf).await?;
}
Some(SessionEntry::CheckedOut) => continue,
_ => {}
}
}
for address in &deleted_keys {
backend.delete_session(address).await?;
}
for key in &dirty_keys {
if !matches!(
state.cache.get(key.as_ref()),
Some(SessionEntry::CheckedOut)
) {
state.dirty.remove(key);
}
}
for key in &deleted_keys {
state.deleted.remove(key);
}
state.evict_if_needed(self.max_entries);
}
{
let mut state = self.identities.lock().await;
let dirty_keys: Vec<_> = state.dirty.iter().cloned().collect();
let deleted_keys: Vec<_> = state.deleted.iter().cloned().collect();
for address in &dirty_keys {
if let Some(Some(data)) = state.cache.get(address.as_ref()) {
let key: [u8; 32] = data.as_ref().try_into().map_err(|_| {
anyhow::anyhow!(
"Corrupted identity key for {address}: expected 32 bytes, got {}",
data.len()
)
})?;
backend.put_identity(address, key).await?;
}
}
for address in &deleted_keys {
backend.delete_identity(address).await?;
}
for key in &dirty_keys {
state.dirty.remove(key);
}
for key in &deleted_keys {
state.deleted.remove(key);
}
state.evict_if_needed(self.max_entries);
}
{
let mut state = self.sender_keys.lock().await;
let dirty_keys: Vec<_> = state.dirty.iter().cloned().collect();
for name in &dirty_keys {
match state.cache.get(name.as_ref()) {
Some(Some(record)) => {
let bytes = record
.serialize()
.map_err(|e| anyhow::anyhow!("sender key serialize for {name}: {e}"))?;
backend.put_sender_key(name, &bytes).await?;
}
Some(None) => {
backend.delete_sender_key(name).await?;
}
None => {}
}
}
for key in &dirty_keys {
state.dirty.remove(key);
}
state.evict_if_needed(self.max_entries);
}
Ok(())
}
#[cfg(feature = "debug-diagnostics")]
pub async fn entry_counts(&self) -> (usize, usize, usize) {
let s = self.sessions.lock().await;
let i = self.identities.lock().await;
let sk = self.sender_keys.lock().await;
(s.cache.len(), i.cache.len(), sk.cache.len())
}
pub async fn clear(&self) {
self.sessions.lock().await.clear();
self.identities.lock().await.clear();
self.sender_keys.lock().await.clear();
}
}