pkarr/client/
cache.rs

1//! Trait and inmemory implementation of [Cache]
2
3use dyn_clone::DynClone;
4use lru::LruCache;
5use std::fmt::Debug;
6use std::num::NonZeroUsize;
7use std::sync::{Arc, RwLock};
8
9use crate::SignedPacket;
10
11/// The sha1 hash of the [crate::PublicKey] used as the key in [Cache].
12pub type CacheKey = [u8; 20];
13
14impl From<&crate::PublicKey> for CacheKey {
15    fn from(public_key: &crate::PublicKey) -> CacheKey {
16        let mut encoded = vec![];
17
18        encoded.extend(public_key.as_bytes());
19
20        let mut hasher = sha1_smol::Sha1::new();
21        hasher.update(&encoded);
22        hasher.digest().bytes()
23    }
24}
25
26impl From<crate::PublicKey> for CacheKey {
27    fn from(value: crate::PublicKey) -> Self {
28        (&value).into()
29    }
30}
31
32/// A trait for a [SignedPacket]s cache for Pkarr [Client][crate::Client].
33pub trait Cache: Debug + Send + Sync + DynClone {
34    /// Returns the maximum capacity of [SignedPacket]s allowed in this cache.
35    fn capacity(&self) -> usize {
36        // backward compatibility
37        0
38    }
39    /// Returns the number of [SignedPacket]s in this cache.
40    fn len(&self) -> usize;
41    /// Returns true if this cache is empty.
42    fn is_empty(&self) -> bool {
43        self.len() == 0
44    }
45    /// Puts [SignedPacket] into cache.
46    fn put(&self, key: &CacheKey, signed_packet: &SignedPacket);
47    /// Reads [SignedPacket] from cache, while moving it to the head of the LRU list.
48    fn get(&self, key: &CacheKey) -> Option<SignedPacket>;
49    /// Reads [SignedPacket] from cache, without changing the LRU list.
50    ///
51    /// Used for internal reads that are not initiated by the user directly,
52    /// like comparing an received signed packet with existing one.
53    ///
54    /// Useful to implement differently from [Cache::get], if you are implementing
55    /// persistent cache where writes are slower than reads.
56    ///
57    /// Otherwise it will just use [Cache::get].
58    fn get_read_only(&self, key: &CacheKey) -> Option<SignedPacket> {
59        self.get(key)
60    }
61}
62
63dyn_clone::clone_trait_object!(Cache);
64
65/// A thread safe wrapper around [lru::LruCache]
66#[derive(Debug, Clone)]
67pub struct InMemoryCache {
68    inner: Arc<RwLock<LruCache<CacheKey, SignedPacket>>>,
69}
70
71impl InMemoryCache {
72    /// Creates a new `LRU` cache that holds at most `cap` items.
73    pub fn new(capacity: NonZeroUsize) -> Self {
74        Self {
75            inner: Arc::new(RwLock::new(LruCache::new(capacity))),
76        }
77    }
78}
79
80impl Cache for InMemoryCache {
81    fn capacity(&self) -> usize {
82        self.inner
83            .read()
84            .expect("InMemoryCache RwLock")
85            .cap()
86            .into()
87    }
88
89    fn len(&self) -> usize {
90        self.inner.read().expect("InMemoryCache RwLock").len()
91    }
92
93    /// Puts [SignedPacket], if a version of the  packet already exists,
94    /// and it has the same [SignedPacket::as_bytes], then only [SignedPacket::last_seen] will be
95    /// updated, otherwise the input will be cloned.
96    fn put(&self, key: &CacheKey, signed_packet: &SignedPacket) {
97        let mut lock = self.inner.write().expect("InMemoryCache RwLock");
98
99        match lock.get_mut(key) {
100            Some(existing) => {
101                if existing.as_bytes() == signed_packet.as_bytes() {
102                    // just refresh the last_seen
103                    existing.set_last_seen(signed_packet.last_seen())
104                } else {
105                    lock.put(*key, signed_packet.clone());
106                }
107            }
108            None => {
109                lock.put(*key, signed_packet.clone());
110            }
111        }
112    }
113
114    fn get(&self, key: &CacheKey) -> Option<SignedPacket> {
115        self.inner
116            .write()
117            .expect("InMemoryCache RwLock")
118            .get(key)
119            .cloned()
120    }
121
122    fn get_read_only(&self, key: &CacheKey) -> Option<SignedPacket> {
123        self.inner
124            .read()
125            .expect("InMemoryCache RwLock")
126            .peek(key)
127            .cloned()
128    }
129}