use std::sync::{Arc, RwLock};
use lru::LruCache;
use crate::error::InternalError;
#[derive(Clone)]
pub struct DataCache {
min_data_size: usize,
inner: Arc<RwLock<Inner>>,
}
impl DataCache {
pub fn new(min_data_size: usize, cache_size: u16) -> Self {
Self {
min_data_size,
inner: Arc::new(RwLock::new(Inner {
cache: LruCache::new(cache_size as usize),
})),
}
}
pub fn cacheable(&self, data: &[u8]) -> bool {
data.len() >= self.min_data_size
}
pub fn insert(
&self,
address: String,
data_hash: String,
data: Vec<u8>,
) -> Result<(), InternalError> {
if !self.cacheable(&data) {
return Ok(());
}
let mut inner = self
.inner
.write()
.map_err(|_| InternalError::with_message("DataCache lock was poisoned".into()))?;
inner.cache.put(address, (data_hash, data));
Ok(())
}
pub fn peek_last_known_data_for_address(
&self,
address: &str,
) -> Result<Option<(String, Vec<u8>)>, InternalError> {
let inner = self
.inner
.read()
.map_err(|_| InternalError::with_message("DataCache lock was poisoned".into()))?;
Ok(inner
.cache
.peek(address)
.map(|(hash, data)| (hash.clone(), data.clone())))
}
pub fn touch_entry(&self, address: &str) -> Result<(), InternalError> {
self.inner
.write()
.map_err(|_| InternalError::with_message("DataCache lock was poisoned".into()))?
.cache
.get(address);
Ok(())
}
}
struct Inner {
cache: LruCache<String, (String, Vec<u8>)>,
}
#[cfg(test)]
mod tests {
use super::*;
use sha2::{Digest, Sha512};
#[test]
fn test_cacheable() {
let cache = DataCache::new(10, 16);
assert!(!cache.cacheable(b"hello"));
assert!(cache.cacheable(b"hello world"));
}
#[test]
fn test_peek_last_known_data_hash_for_address() -> Result<(), InternalError> {
let cache = DataCache::new(10, 16);
let data = b"hello world".to_vec();
let hash = hash(&data);
cache.insert("abc01234".into(), hash.clone(), data.clone())?;
assert_eq!(
Some((hash, data)),
cache.peek_last_known_data_for_address("abc01234")?
);
Ok(())
}
#[test]
fn test_cache_limits() -> Result<(), InternalError> {
let values = (0..20)
.map(|i| {
(
format!("0000{:02x}", i),
hash(format!("hello-world-{}", i).as_bytes()),
format!("hello-world-{}", i).as_bytes().to_vec(),
)
})
.collect::<Vec<_>>();
let cache = DataCache::new(10, 16);
for (addr, hash, data) in values.iter().cloned() {
cache.insert(addr, hash, data)?;
}
for i in 0..4 {
assert_eq!(
None,
cache.peek_last_known_data_for_address(&format!("0000{:02x}", i))?
);
}
for (addr, hash, data) in &values[4..] {
assert_eq!(
Some((hash.clone(), data.clone())),
cache.peek_last_known_data_for_address(&addr)?,
);
}
Ok(())
}
#[test]
fn test_cache_limits_with_touch() -> Result<(), InternalError> {
let values = (0..20)
.map(|i| {
(
format!("0000{:02x}", i),
hash(format!("hello-world-{}", i).as_bytes()),
format!("hello-world-{}", i).as_bytes().to_vec(),
)
})
.collect::<Vec<_>>();
let cache = DataCache::new(10, 16);
for (addr, hash, data) in values.iter().cloned() {
cache.insert(addr, hash, data)?;
cache.touch_entry("000000")?;
}
assert_eq!(
Some((
hash("hello-world-0".as_bytes()),
"hello-world-0".as_bytes().to_vec()
)),
cache.peek_last_known_data_for_address("000000")?,
);
for i in 1..5 {
assert_eq!(
None,
cache.peek_last_known_data_for_address(&format!("0000{:02x}", i))?
);
}
for (addr, hash, data) in &values[5..] {
assert_eq!(
Some((hash.clone(), data.clone())),
cache.peek_last_known_data_for_address(&addr)?,
);
}
Ok(())
}
fn hash(bytes: &[u8]) -> String {
Sha512::digest(bytes)
.iter()
.map(|b| format!("{:02x}", b))
.collect()
}
}