1use std::collections::HashMap;
18use std::sync::Mutex;
19
20use async_trait::async_trait;
21use serde::{Deserialize, Serialize};
22
23#[cfg(feature = "redb-backend")]
24mod redb_store;
25#[cfg(feature = "redb-backend")]
26pub use redb_store::RedbStore;
27
28pub type Guid = [u8; 16];
30
31#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
33pub struct HandleRecord {
34 pub create_guid: Guid,
36 pub path: String,
38 pub share: String,
40 pub session_id: u64,
42 pub owner_user: String,
45 pub access: u32,
47 pub share_access: u32,
49 pub create_options: u32,
51 pub is_dir: bool,
53 pub flags: u32,
55 pub match_guid: Option<Guid>,
57 pub owner_node: String,
59 pub lease_key: Option<Guid>,
61 pub lease_state: u32,
64 pub client_guid: Guid,
67 pub timeout_ms: u64,
69 pub deadline_ms: u64,
71 pub delete_on_close: bool,
73}
74
75impl HandleRecord {
76 pub fn is_persistent(&self) -> bool {
78 self.deadline_ms == 0
79 }
80
81 fn expired_at(&self, now_ms: u64) -> bool {
82 !self.is_persistent() && self.deadline_ms <= now_ms
83 }
84}
85
86#[derive(Debug, thiserror::Error)]
88pub enum StoreError {
89 #[error("handle store backend error: {0}")]
91 Backend(String),
92 #[error("handle store serialization error: {0}")]
94 Serde(String),
95}
96
97#[async_trait]
102pub trait HandleStore: Send + Sync {
103 async fn put(&self, record: HandleRecord) -> Result<(), StoreError>;
105
106 async fn get(&self, create_guid: &Guid) -> Result<Option<HandleRecord>, StoreError>;
108
109 async fn take(
114 &self,
115 create_guid: &Guid,
116 match_guid: Option<Guid>,
117 now_ms: u64,
118 ) -> Result<Option<HandleRecord>, StoreError>;
119
120 async fn reclaim(
124 &self,
125 create_guid: &Guid,
126 owner: &str,
127 now_ms: u64,
128 ) -> Result<Option<HandleRecord>, StoreError>;
129
130 async fn remove(&self, create_guid: &Guid) -> Result<(), StoreError>;
132
133 async fn sweep_expired(&self, now_ms: u64) -> Result<Vec<Guid>, StoreError>;
136
137 async fn list(&self) -> Result<Vec<HandleRecord>, StoreError>;
139}
140
141#[derive(Default)]
143pub struct MemStore {
144 map: Mutex<HashMap<Guid, HandleRecord>>,
145}
146
147impl MemStore {
148 pub fn new() -> MemStore {
150 MemStore { map: Mutex::new(HashMap::new()) }
151 }
152
153 fn lock(&self) -> std::sync::MutexGuard<'_, HashMap<Guid, HandleRecord>> {
154 self.map.lock().unwrap_or_else(|e| e.into_inner())
155 }
156}
157
158#[async_trait]
159impl HandleStore for MemStore {
160 async fn put(&self, record: HandleRecord) -> Result<(), StoreError> {
161 self.lock().insert(record.create_guid, record);
162 Ok(())
163 }
164
165 async fn get(&self, create_guid: &Guid) -> Result<Option<HandleRecord>, StoreError> {
166 Ok(self.lock().get(create_guid).cloned())
167 }
168
169 async fn take(
170 &self,
171 create_guid: &Guid,
172 match_guid: Option<Guid>,
173 now_ms: u64,
174 ) -> Result<Option<HandleRecord>, StoreError> {
175 let mut map = self.lock();
176 let Some(record) = map.get(create_guid) else {
177 return Ok(None);
178 };
179 if record.expired_at(now_ms) {
180 map.remove(create_guid);
181 return Ok(None);
182 }
183 if let Some(g) = match_guid
184 && record.match_guid != Some(g) {
185 return Ok(None);
186 }
187 Ok(map.remove(create_guid))
188 }
189
190 async fn reclaim(
191 &self,
192 create_guid: &Guid,
193 owner: &str,
194 now_ms: u64,
195 ) -> Result<Option<HandleRecord>, StoreError> {
196 let mut map = self.lock();
197 let Some(record) = map.get_mut(create_guid) else {
198 return Ok(None);
199 };
200 if !record.owner_node.is_empty() && record.owner_node != owner && !record.expired_at(now_ms) {
201 return Ok(None);
202 }
203 record.owner_node = owner.to_string();
204 if record.timeout_ms > 0 {
205 record.deadline_ms = now_ms + record.timeout_ms;
206 }
207 Ok(Some(record.clone()))
208 }
209
210 async fn remove(&self, create_guid: &Guid) -> Result<(), StoreError> {
211 self.lock().remove(create_guid);
212 Ok(())
213 }
214
215 async fn sweep_expired(&self, now_ms: u64) -> Result<Vec<Guid>, StoreError> {
216 let mut map = self.lock();
217 let expired: Vec<Guid> =
218 map.values().filter(|r| r.expired_at(now_ms)).map(|r| r.create_guid).collect();
219 for guid in &expired {
220 map.remove(guid);
221 }
222 Ok(expired)
223 }
224
225 async fn list(&self) -> Result<Vec<HandleRecord>, StoreError> {
226 Ok(self.lock().values().cloned().collect())
227 }
228}
229
230#[cfg(test)]
231pub(crate) fn sample(guid_byte: u8, timeout_ms: u64) -> HandleRecord {
232 HandleRecord {
233 create_guid: [guid_byte; 16],
234 path: "dir/file.bin".into(),
235 share: "public".into(),
236 session_id: 0x1234,
237 owner_user: "admin".into(),
238 access: 0x0012_019f,
239 share_access: 0x7,
240 create_options: 0x40,
241 is_dir: false,
242 flags: 0x2,
243 match_guid: None,
244 owner_node: String::new(),
245 lease_key: None,
246 lease_state: 0,
247 client_guid: [0u8; 16],
248 timeout_ms,
249 deadline_ms: if timeout_ms == 0 { 0 } else { timeout_ms },
250 delete_on_close: false,
251 }
252}
253
254#[cfg(test)]
255mod mem_tests {
256 use super::*;
257
258 #[tokio::test]
259 async fn put_get_remove_roundtrip() {
260 let store = MemStore::new();
261 let rec = sample(1, 1000);
262 store.put(rec.clone()).await.unwrap();
263 assert_eq!(store.get(&rec.create_guid).await.unwrap().as_ref(), Some(&rec));
264 store.remove(&rec.create_guid).await.unwrap();
265 assert!(store.get(&rec.create_guid).await.unwrap().is_none());
266 }
267
268 #[tokio::test]
269 async fn reclaim_is_exclusive_until_expiry() {
270 let store = MemStore::new();
271 store.put(sample(2, 1000)).await.unwrap();
272 let guid = [2u8; 16];
273 assert!(store.reclaim(&guid, "nodeA", 0).await.unwrap().is_some());
275 assert!(store.reclaim(&guid, "nodeB", 500).await.unwrap().is_none());
277 let claimed = store.reclaim(&guid, "nodeB", 2000).await.unwrap();
279 assert_eq!(claimed.unwrap().owner_node, "nodeB");
280 }
281
282 #[tokio::test]
283 async fn sweep_drops_expired_but_keeps_persistent() {
284 let store = MemStore::new();
285 store.put(sample(3, 1000)).await.unwrap(); store.put(sample(4, 0)).await.unwrap(); let dropped = store.sweep_expired(2000).await.unwrap();
288 assert_eq!(dropped, vec![[3u8; 16]]);
289 assert!(store.get(&[3u8; 16]).await.unwrap().is_none());
290 assert!(store.get(&[4u8; 16]).await.unwrap().is_some());
291 }
292
293 #[tokio::test]
294 async fn take_validates_guid_and_removes() {
295 let store = MemStore::new();
296 let mut rec = sample(5, 1000);
297 rec.match_guid = Some([0xAB; 16]);
298 store.put(rec).await.unwrap();
299 let guid = [5u8; 16];
300 assert!(store.take(&guid, Some([0x00; 16]), 0).await.unwrap().is_none());
302 assert!(store.get(&guid).await.unwrap().is_some());
303 assert!(store.take(&guid, Some([0xAB; 16]), 0).await.unwrap().is_some());
305 assert!(store.get(&guid).await.unwrap().is_none());
306 }
307}