bcp_encoder/
content_store.rs1use std::collections::HashMap;
2use std::sync::RwLock;
3
4use bcp_types::content_store::ContentStore;
5
6pub struct MemoryContentStore {
33 store: RwLock<HashMap<[u8; 32], Vec<u8>>>,
34}
35
36impl MemoryContentStore {
37 #[must_use]
39 pub fn new() -> Self {
40 Self {
41 store: RwLock::new(HashMap::new()),
42 }
43 }
44
45 #[must_use]
47 pub fn len(&self) -> usize {
48 self.store
49 .read()
50 .expect("content store lock poisoned")
51 .len()
52 }
53
54 #[must_use]
56 pub fn is_empty(&self) -> bool {
57 self.len() == 0
58 }
59
60 #[must_use]
65 pub fn total_bytes(&self) -> usize {
66 self.store
67 .read()
68 .expect("content store lock poisoned")
69 .values()
70 .map(Vec::len)
71 .sum()
72 }
73}
74
75impl Default for MemoryContentStore {
76 fn default() -> Self {
77 Self::new()
78 }
79}
80
81impl ContentStore for MemoryContentStore {
82 fn get(&self, hash: &[u8; 32]) -> Option<Vec<u8>> {
83 self.store
84 .read()
85 .expect("content store lock poisoned")
86 .get(hash)
87 .cloned()
88 }
89
90 fn put(&self, content: &[u8]) -> [u8; 32] {
91 let hash: [u8; 32] = blake3::hash(content).into();
92 let mut store = self.store.write().expect("content store lock poisoned");
93 store.entry(hash).or_insert_with(|| content.to_vec());
94 hash
95 }
96
97 fn contains(&self, hash: &[u8; 32]) -> bool {
98 self.store
99 .read()
100 .expect("content store lock poisoned")
101 .contains_key(hash)
102 }
103}
104
105#[cfg(test)]
106mod tests {
107 use super::*;
108
109 #[test]
110 fn put_get_roundtrip() {
111 let store = MemoryContentStore::new();
112 let data = b"fn main() { println!(\"hello\"); }";
113 let hash = store.put(data);
114 let retrieved = store.get(&hash).expect("should find stored content");
115 assert_eq!(retrieved, data);
116 }
117
118 #[test]
119 fn put_returns_deterministic_hash() {
120 let store = MemoryContentStore::new();
121 let data = b"deterministic content";
122 let hash1 = store.put(data);
123 let hash2 = store.put(data);
124 assert_eq!(hash1, hash2);
125 }
126
127 #[test]
128 fn dedup_stores_only_once() {
129 let store = MemoryContentStore::new();
130 let data = b"duplicate content";
131 store.put(data);
132 store.put(data);
133 assert_eq!(store.len(), 1);
134 }
135
136 #[test]
137 fn contains_returns_true_for_stored_hash() {
138 let store = MemoryContentStore::new();
139 let data = b"some content";
140 let hash = store.put(data);
141 assert!(store.contains(&hash));
142 }
143
144 #[test]
145 fn contains_returns_false_for_unknown_hash() {
146 let store = MemoryContentStore::new();
147 let fake_hash = [0u8; 32];
148 assert!(!store.contains(&fake_hash));
149 }
150
151 #[test]
152 fn get_returns_none_for_unknown_hash() {
153 let store = MemoryContentStore::new();
154 let fake_hash = [0xFF; 32];
155 assert!(store.get(&fake_hash).is_none());
156 }
157
158 #[test]
159 fn len_and_total_bytes() {
160 let store = MemoryContentStore::new();
161 assert_eq!(store.len(), 0);
162 assert!(store.is_empty());
163 assert_eq!(store.total_bytes(), 0);
164
165 store.put(b"hello"); store.put(b"world"); assert_eq!(store.len(), 2);
168 assert!(!store.is_empty());
169 assert_eq!(store.total_bytes(), 10);
170 }
171
172 #[test]
173 fn blake3_hash_is_32_bytes() {
174 let store = MemoryContentStore::new();
175 let hash = store.put(b"test");
176 assert_eq!(hash.len(), 32);
177 }
178
179 #[test]
180 fn different_content_produces_different_hashes() {
181 let store = MemoryContentStore::new();
182 let hash1 = store.put(b"content A");
183 let hash2 = store.put(b"content B");
184 assert_ne!(hash1, hash2);
185 }
186}