helia_ipns/
local_store.rs1use crate::errors::IpnsError;
4use crate::record::IpnsRecord;
5use serde::{Deserialize, Serialize};
6use std::collections::HashMap;
7use std::sync::{Arc, RwLock};
8use std::time::{SystemTime, UNIX_EPOCH};
9
10#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct RecordMetadata {
13 pub key_name: String,
15
16 pub lifetime: u64,
18
19 pub created: u64,
21}
22
23impl RecordMetadata {
24 pub fn new(key_name: String, lifetime: u64) -> Self {
26 let created = SystemTime::now()
27 .duration_since(UNIX_EPOCH)
28 .unwrap()
29 .as_millis() as u64;
30
31 Self {
32 key_name,
33 lifetime,
34 created,
35 }
36 }
37
38 pub fn created_time(&self) -> SystemTime {
40 UNIX_EPOCH + std::time::Duration::from_millis(self.created)
41 }
42
43 pub fn should_republish(&self, dht_expiry_ms: u64, republish_threshold_ms: u64) -> bool {
45 let now = SystemTime::now()
46 .duration_since(UNIX_EPOCH)
47 .unwrap()
48 .as_millis() as u64;
49
50 let dht_expiry = self.created + dht_expiry_ms;
51 let record_expiry = self.created + self.lifetime;
52
53 if dht_expiry.saturating_sub(now) <= republish_threshold_ms {
55 return true;
56 }
57
58 if record_expiry.saturating_sub(now) <= republish_threshold_ms {
60 return true;
61 }
62
63 false
64 }
65}
66
67#[derive(Debug, Clone)]
69pub struct StoredRecord {
70 pub record: Vec<u8>,
72
73 pub metadata: Option<RecordMetadata>,
75
76 pub created: u64,
78}
79
80#[derive(Debug, Clone)]
84pub struct LocalStore {
85 records: Arc<RwLock<HashMap<Vec<u8>, StoredRecord>>>,
86}
87
88impl LocalStore {
89 pub fn new() -> Self {
91 Self {
92 records: Arc::new(RwLock::new(HashMap::new())),
93 }
94 }
95
96 pub fn put(
98 &self,
99 routing_key: &[u8],
100 record: Vec<u8>,
101 metadata: Option<RecordMetadata>,
102 ) -> Result<(), IpnsError> {
103 let created = SystemTime::now()
104 .duration_since(UNIX_EPOCH)
105 .unwrap()
106 .as_millis() as u64;
107
108 let stored = StoredRecord {
109 record,
110 metadata,
111 created,
112 };
113
114 let mut records = self.records.write().unwrap();
115 records.insert(routing_key.to_vec(), stored);
116
117 tracing::debug!(
118 "Stored IPNS record for routing key: {}",
119 bs58::encode(routing_key).into_string()
120 );
121
122 Ok(())
123 }
124
125 pub fn get(&self, routing_key: &[u8]) -> Result<StoredRecord, IpnsError> {
127 let records = self.records.read().unwrap();
128
129 records.get(routing_key).cloned().ok_or_else(|| {
130 IpnsError::NotFound(format!(
131 "No record found for routing key: {}",
132 bs58::encode(routing_key).into_string()
133 ))
134 })
135 }
136
137 pub fn has(&self, routing_key: &[u8]) -> bool {
139 let records = self.records.read().unwrap();
140 records.contains_key(routing_key)
141 }
142
143 pub fn delete(&self, routing_key: &[u8]) -> Result<(), IpnsError> {
145 let mut records = self.records.write().unwrap();
146
147 if records.remove(routing_key).is_some() {
148 tracing::debug!(
149 "Deleted IPNS record for routing key: {}",
150 bs58::encode(routing_key).into_string()
151 );
152 Ok(())
153 } else {
154 Err(IpnsError::NotFound(format!(
155 "No record found for routing key: {}",
156 bs58::encode(routing_key).into_string()
157 )))
158 }
159 }
160
161 pub fn list(&self) -> Vec<(Vec<u8>, StoredRecord)> {
163 let records = self.records.read().unwrap();
164 records
165 .iter()
166 .map(|(k, v)| (k.clone(), v.clone()))
167 .collect()
168 }
169
170 pub fn clear(&self) {
172 let mut records = self.records.write().unwrap();
173 records.clear();
174 tracing::debug!("Cleared all IPNS records from local store");
175 }
176
177 pub fn len(&self) -> usize {
179 let records = self.records.read().unwrap();
180 records.len()
181 }
182
183 pub fn is_empty(&self) -> bool {
185 self.len() == 0
186 }
187}
188
189impl Default for LocalStore {
190 fn default() -> Self {
191 Self::new()
192 }
193}
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198
199 #[test]
200 fn test_local_store_operations() {
201 let store = LocalStore::new();
202 let routing_key = b"test-key";
203 let record = b"test-record".to_vec();
204
205 assert!(store.is_empty());
207 assert!(!store.has(routing_key));
208
209 let metadata = RecordMetadata::new("my-key".to_string(), 48 * 60 * 60 * 1000);
211 store
212 .put(routing_key, record.clone(), Some(metadata.clone()))
213 .unwrap();
214
215 assert!(!store.is_empty());
217 assert!(store.has(routing_key));
218 assert_eq!(store.len(), 1);
219
220 let stored = store.get(routing_key).unwrap();
222 assert_eq!(stored.record, record);
223 assert!(stored.metadata.is_some());
224 assert_eq!(stored.metadata.unwrap().key_name, "my-key");
225
226 store.delete(routing_key).unwrap();
228 assert!(store.is_empty());
229 assert!(!store.has(routing_key));
230 }
231
232 #[test]
233 fn test_should_republish() {
234 let metadata = RecordMetadata {
235 key_name: "test".to_string(),
236 lifetime: 48 * 60 * 60 * 1000, created: SystemTime::now()
238 .duration_since(UNIX_EPOCH)
239 .unwrap()
240 .as_millis() as u64
241 - (20 * 60 * 60 * 1000), };
243
244 let dht_expiry_ms = 24 * 60 * 60 * 1000; let threshold_ms = 4 * 60 * 60 * 1000; assert!(metadata.should_republish(dht_expiry_ms, threshold_ms));
249 }
250}