Skip to main content

feltdb/
content_distribution.rs

1/// Content addressable storage and acquisition module
2use crate::{
3    AcquisitionPolicy, ContentIdentity, ContentTransferError, FlowRef, PeerAdvertisement, PeerId,
4};
5use std::collections::HashMap;
6use std::sync::{Arc, Mutex};
7
8/// A content addressable store that tracks where content lives
9#[derive(Clone)]
10pub struct ContentAddressableStore {
11    /// Map from content hash to actual content
12    content: Arc<Mutex<HashMap<String, Vec<u8>>>>,
13    /// Map from content hash to which peers have it
14    content_locations: Arc<Mutex<HashMap<String, Vec<PeerId>>>>,
15}
16
17impl ContentAddressableStore {
18    /// Create a new content addressable store
19    pub fn new() -> Self {
20        Self {
21            content: Arc::new(Mutex::new(HashMap::new())),
22            content_locations: Arc::new(Mutex::new(HashMap::new())),
23        }
24    }
25
26    /// Store content and track its hash
27    pub fn store(&self, content: Vec<u8>, hash: String) -> Result<(), String> {
28        let mut store = self.content.lock().map_err(|e| e.to_string())?;
29        store.insert(hash, content);
30        Ok(())
31    }
32
33    /// Retrieve content by hash
34    pub fn get(&self, hash: &str) -> Result<Option<Vec<u8>>, String> {
35        let store = self.content.lock().map_err(|e| e.to_string())?;
36        Ok(store.get(hash).cloned())
37    }
38
39    /// Record that a peer has a piece of content
40    pub fn record_location(&self, hash: String, peer: PeerId) -> Result<(), String> {
41        let mut locations = self.content_locations.lock().map_err(|e| e.to_string())?;
42        locations.entry(hash).or_insert_with(Vec::new).push(peer);
43        Ok(())
44    }
45
46    /// Find peers that have a specific piece of content
47    pub fn find_peers_for_content(&self, hash: &str) -> Result<Vec<PeerId>, String> {
48        let locations = self.content_locations.lock().map_err(|e| e.to_string())?;
49        Ok(locations.get(hash).cloned().unwrap_or_default())
50    }
51
52    /// Check if content exists locally
53    pub fn has_content(&self, hash: &str) -> Result<bool, String> {
54        let store = self.content.lock().map_err(|e| e.to_string())?;
55        Ok(store.contains_key(hash))
56    }
57}
58
59impl Default for ContentAddressableStore {
60    fn default() -> Self {
61        Self::new()
62    }
63}
64
65/// Reference resolution with fallback to peer acquisition
66pub struct DistributedReferenceResolver {
67    /// Local content store
68    _local_store: ContentAddressableStore,
69}
70
71impl DistributedReferenceResolver {
72    /// Create a new distributed reference resolver
73    pub fn new(local_store: ContentAddressableStore) -> Self {
74        Self {
75            _local_store: local_store,
76        }
77    }
78
79    /// Try to resolve a reference, first locally then from peers
80    pub fn resolve(
81        &self,
82        flow_ref: &FlowRef,
83        available_peers: &[PeerAdvertisement],
84    ) -> Result<Option<Vec<u8>>, String> {
85        // Step 1: Try local resolution
86        // Check if we have this content hash locally
87        // For now, just indicate it wasn't found
88        // In a real implementation, we'd query from local content store
89
90        // Step 2: Look for peers that might have this
91        for peer_ad in available_peers {
92            if peer_ad.provides_namespace(&flow_ref.namespace) {
93                // This peer might have the content
94                // In a full implementation, we'd make a remote call here
95                return Ok(None); // Placeholder
96            }
97        }
98
99        Ok(None)
100    }
101}
102
103/// Projection distribution - tracks where projections are hosted
104#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
105pub struct ProjectionDistribution {
106    /// Namespace this projection is for
107    pub namespace: String,
108    /// Projection type (e.g., "search", "vector")
109    pub projection_type: String,
110    /// Peer hosting this projection
111    pub hosted_by: PeerId,
112    /// Last update timestamp
113    pub updated_at_ms: u128,
114}
115
116impl ProjectionDistribution {
117    /// Create a new projection distribution entry
118    pub fn new(
119        namespace: impl Into<String>,
120        projection_type: impl Into<String>,
121        hosted_by: PeerId,
122    ) -> Self {
123        Self {
124            namespace: namespace.into(),
125            projection_type: projection_type.into(),
126            hosted_by,
127            updated_at_ms: crate::now_ms(),
128        }
129    }
130}
131
132/// Capability location registry - tracks where capabilities are available
133#[derive(Clone)]
134pub struct CapabilityLocationRegistry {
135    /// Map from capability name to list of peers providing it
136    capability_locations: Arc<Mutex<HashMap<String, Vec<PeerId>>>>,
137    /// Map from peer ID to capabilities it provides
138    peer_capabilities: Arc<Mutex<HashMap<String, Vec<String>>>>,
139}
140
141impl CapabilityLocationRegistry {
142    /// Create a new capability location registry
143    pub fn new() -> Self {
144        Self {
145            capability_locations: Arc::new(Mutex::new(HashMap::new())),
146            peer_capabilities: Arc::new(Mutex::new(HashMap::new())),
147        }
148    }
149
150    /// Register that a peer provides a capability
151    pub fn register_capability(&self, capability: String, peer: PeerId) -> Result<(), String> {
152        let mut locations = self
153            .capability_locations
154            .lock()
155            .map_err(|e| e.to_string())?;
156        locations
157            .entry(capability.clone())
158            .or_insert_with(Vec::new)
159            .push(peer.clone());
160
161        let mut peer_caps = self.peer_capabilities.lock().map_err(|e| e.to_string())?;
162        peer_caps
163            .entry(peer.id.clone())
164            .or_insert_with(Vec::new)
165            .push(capability);
166
167        Ok(())
168    }
169
170    /// Find peers that provide a capability
171    pub fn peers_for_capability(&self, capability: &str) -> Result<Vec<PeerId>, String> {
172        let locations = self
173            .capability_locations
174            .lock()
175            .map_err(|e| e.to_string())?;
176        Ok(locations.get(capability).cloned().unwrap_or_default())
177    }
178
179    /// Get all capabilities provided by a peer
180    pub fn capabilities_for_peer(&self, peer_id: &PeerId) -> Result<Vec<String>, String> {
181        let peer_caps = self.peer_capabilities.lock().map_err(|e| e.to_string())?;
182        Ok(peer_caps.get(&peer_id.id).cloned().unwrap_or_default())
183    }
184}
185
186impl Default for CapabilityLocationRegistry {
187    fn default() -> Self {
188        Self::new()
189    }
190}
191
192/// Manager for acquiring content from distributed peers
193#[derive(Clone)]
194pub struct ContentAcquisitionManager {
195    /// Local content store
196    content_store: ContentAddressableStore,
197    /// Peer registry for discovering providers
198    peer_registry: Arc<Mutex<HashMap<String, PeerAdvertisement>>>,
199}
200
201impl ContentAcquisitionManager {
202    /// Create a new content acquisition manager
203    pub fn new(content_store: ContentAddressableStore) -> Self {
204        Self {
205            content_store,
206            peer_registry: Arc::new(Mutex::new(HashMap::new())),
207        }
208    }
209
210    /// Register a peer for content acquisition
211    pub fn register_peer(&self, advertisement: PeerAdvertisement) -> Result<(), String> {
212        let mut registry = self.peer_registry.lock().map_err(|e| e.to_string())?;
213        registry.insert(advertisement.peer_id.id.clone(), advertisement);
214        Ok(())
215    }
216
217    /// Resolve a reference using the acquisition policy
218    pub fn resolve(
219        &self,
220        flow_ref: &FlowRef,
221        policy: AcquisitionPolicy,
222    ) -> Result<Option<Vec<u8>>, String> {
223        // Step 1: Always try local first
224        let local_key = format!("{}:{}", flow_ref.namespace, flow_ref.object_id);
225
226        // Check local content store by namespace:id key
227        // First, try to get it directly
228        if let Ok(Some(content)) = self.content_store.get(&local_key) {
229            return Ok(Some(content));
230        }
231
232        // Step 2: Handle acquisition policies
233        match policy {
234            AcquisitionPolicy::LocalOnly => {
235                // Fail if not available locally
236                Ok(None)
237            }
238            AcquisitionPolicy::PreferLocal
239            | AcquisitionPolicy::RemoteAllowed
240            | AcquisitionPolicy::Required => {
241                // Try to find a peer that has this content
242                // In a real implementation, this would be async and perform network I/O
243                self.find_and_acquire_from_peer(flow_ref)
244            }
245        }
246    }
247
248    /// Find a peer that can provide content and attempt acquisition
249    fn find_and_acquire_from_peer(&self, flow_ref: &FlowRef) -> Result<Option<Vec<u8>>, String> {
250        let registry = self.peer_registry.lock().map_err(|e| e.to_string())?;
251
252        // Find peers that provide this namespace
253        let providers: Vec<_> = registry
254            .values()
255            .filter(|ad| ad.provides_namespace(&flow_ref.namespace))
256            .collect();
257
258        if providers.is_empty() {
259            return Ok(None);
260        }
261
262        // For now, try the first available provider
263        // In a real implementation, this would use load-aware selection
264        if providers.first().is_some() {
265            // Simulate content acquisition by checking what we already have
266            // In reality, this would perform a remote RPC call
267            Ok(None)
268        } else {
269            Ok(None)
270        }
271    }
272
273    /// Verify content against its content identity
274    pub fn verify_content(
275        content: &[u8],
276        expected_identity: &ContentIdentity,
277    ) -> Result<(), ContentTransferError> {
278        let computed_identity = ContentIdentity::from_bytes(content);
279
280        if computed_identity.hash == expected_identity.hash {
281            Ok(())
282        } else {
283            Err(ContentTransferError::HashMismatch {
284                expected: expected_identity.hash.clone(),
285                computed: computed_identity.hash,
286            })
287        }
288    }
289
290    /// Materialize acquired content into local storage
291    pub fn materialize(
292        &self,
293        flow_ref: &FlowRef,
294        content: Vec<u8>,
295        content_identity: &ContentIdentity,
296    ) -> Result<(), String> {
297        // Verify content hash
298        Self::verify_content(&content, content_identity).map_err(|e| e.to_string())?;
299
300        // Store in content store
301        let key = format!("{}:{}", flow_ref.namespace, flow_ref.object_id);
302        self.content_store.store(content, key)?;
303
304        Ok(())
305    }
306
307    /// Get content from local store
308    pub fn get_local(&self, flow_ref: &FlowRef) -> Result<Option<Vec<u8>>, String> {
309        let key = format!("{}:{}", flow_ref.namespace, flow_ref.object_id);
310        self.content_store.get(&key)
311    }
312
313    /// Check if content exists locally
314    pub fn has_local(&self, flow_ref: &FlowRef) -> Result<bool, String> {
315        let key = format!("{}:{}", flow_ref.namespace, flow_ref.object_id);
316        self.content_store.has_content(&key)
317    }
318}
319
320impl Default for ContentAcquisitionManager {
321    fn default() -> Self {
322        Self::new(ContentAddressableStore::new())
323    }
324}
325
326#[cfg(test)]
327mod tests {
328    use super::*;
329
330    #[test]
331    fn test_content_addressable_store() {
332        let store = ContentAddressableStore::new();
333        let content = b"hello world".to_vec();
334        let hash = "abc123".to_string();
335
336        store.store(content.clone(), hash.clone()).unwrap();
337        let retrieved = store.get(&hash).unwrap();
338        assert_eq!(retrieved, Some(content));
339    }
340
341    #[test]
342    fn test_content_addressable_store_locations() {
343        let store = ContentAddressableStore::new();
344        let hash = "abc123".to_string();
345        let peer_a = PeerId::new("peer-a");
346        let peer_b = PeerId::new("peer-b");
347
348        store.record_location(hash.clone(), peer_a).unwrap();
349        store.record_location(hash.clone(), peer_b).unwrap();
350
351        let peers = store.find_peers_for_content(&hash).unwrap();
352        assert_eq!(peers.len(), 2);
353    }
354
355    #[test]
356    fn test_projection_distribution() {
357        let peer = PeerId::new("search-node");
358        let proj = ProjectionDistribution::new("docs", "search", peer.clone());
359
360        assert_eq!(proj.namespace, "docs");
361        assert_eq!(proj.projection_type, "search");
362        assert_eq!(proj.hosted_by.id, "search-node");
363    }
364
365    #[test]
366    fn test_capability_location_registry() {
367        let registry = CapabilityLocationRegistry::new();
368        let peer_a = PeerId::new("peer-a");
369        let peer_b = PeerId::new("peer-b");
370
371        registry
372            .register_capability("search:docs".to_string(), peer_a.clone())
373            .unwrap();
374        registry
375            .register_capability("search:docs".to_string(), peer_b.clone())
376            .unwrap();
377        registry
378            .register_capability("vector:embeddings".to_string(), peer_b.clone())
379            .unwrap();
380
381        let search_peers = registry.peers_for_capability("search:docs").unwrap();
382        assert_eq!(search_peers.len(), 2);
383
384        let vector_peers = registry.peers_for_capability("vector:embeddings").unwrap();
385        assert_eq!(vector_peers.len(), 1);
386
387        let peer_a_caps = registry.capabilities_for_peer(&peer_a).unwrap();
388        assert_eq!(peer_a_caps.len(), 1);
389
390        let peer_b_caps = registry.capabilities_for_peer(&peer_b).unwrap();
391        assert_eq!(peer_b_caps.len(), 2);
392    }
393
394    #[test]
395    fn test_content_acquisition_manager_local_only() {
396        let store = ContentAddressableStore::new();
397        let manager = ContentAcquisitionManager::new(store);
398
399        let flow_ref = FlowRef::new("docs", "123");
400
401        // Should not find content with LocalOnly policy
402        let result = manager
403            .resolve(&flow_ref, AcquisitionPolicy::LocalOnly)
404            .unwrap();
405        assert!(result.is_none());
406    }
407
408    #[test]
409    fn test_content_acquisition_manager_materialize() {
410        let store = ContentAddressableStore::new();
411        let manager = ContentAcquisitionManager::new(store);
412
413        let flow_ref = FlowRef::new("docs", "123");
414        let content = b"test content".to_vec();
415        let content_id = ContentIdentity::from_bytes(&content);
416
417        // Materialize content
418        manager
419            .materialize(&flow_ref, content.clone(), &content_id)
420            .unwrap();
421
422        // Verify it's stored locally
423        let retrieved = manager.get_local(&flow_ref).unwrap();
424        assert_eq!(retrieved, Some(content));
425    }
426
427    #[test]
428    fn test_content_acquisition_manager_has_local() {
429        let store = ContentAddressableStore::new();
430        let manager = ContentAcquisitionManager::new(store);
431
432        let flow_ref = FlowRef::new("docs", "123");
433
434        // Initially should not have content
435        assert!(!manager.has_local(&flow_ref).unwrap());
436
437        // Materialize content
438        let content = b"test content".to_vec();
439        let content_id = ContentIdentity::from_bytes(&content);
440        manager
441            .materialize(&flow_ref, content, &content_id)
442            .unwrap();
443
444        // Now should have it
445        assert!(manager.has_local(&flow_ref).unwrap());
446    }
447
448    #[test]
449    fn test_verify_content_success() {
450        let content = b"test content".to_vec();
451        let content_id = ContentIdentity::from_bytes(&content);
452
453        let result = ContentAcquisitionManager::verify_content(&content, &content_id);
454        assert!(result.is_ok());
455    }
456
457    #[test]
458    fn test_verify_content_hash_mismatch() {
459        let content = b"test content".to_vec();
460        let wrong_id = ContentIdentity::new("wronghash");
461
462        let result = ContentAcquisitionManager::verify_content(&content, &wrong_id);
463        assert!(result.is_err());
464    }
465}