package ucxi import ( "context" "fmt" "sync" "time" "chorus.services/bzzz/pkg/ucxl" ) // BasicAddressResolver provides a basic implementation of AddressResolver type BasicAddressResolver struct { // In-memory registry for announced content registry map[string]*ResolvedContent mutex sync.RWMutex // P2P integration hooks (to be implemented later) announceHook func(ctx context.Context, addr *ucxl.Address, content *Content) error discoverHook func(ctx context.Context, pattern *ucxl.Address) ([]*ResolvedContent, error) // Configuration defaultTTL time.Duration nodeID string } // NewBasicAddressResolver creates a new basic address resolver func NewBasicAddressResolver(nodeID string) *BasicAddressResolver { return &BasicAddressResolver{ registry: make(map[string]*ResolvedContent), defaultTTL: 5 * time.Minute, nodeID: nodeID, } } // SetAnnounceHook sets a hook function for content announcements (for P2P integration) func (r *BasicAddressResolver) SetAnnounceHook(hook func(ctx context.Context, addr *ucxl.Address, content *Content) error) { r.announceHook = hook } // SetDiscoverHook sets a hook function for content discovery (for P2P integration) func (r *BasicAddressResolver) SetDiscoverHook(hook func(ctx context.Context, pattern *ucxl.Address) ([]*ResolvedContent, error)) { r.discoverHook = hook } // Resolve resolves a UCXL address to content func (r *BasicAddressResolver) Resolve(ctx context.Context, addr *ucxl.Address) (*ResolvedContent, error) { if addr == nil { return nil, fmt.Errorf("address cannot be nil") } key := r.generateRegistryKey(addr) r.mutex.RLock() resolved, exists := r.registry[key] r.mutex.RUnlock() if exists { // Check if content is still valid (TTL) if time.Now().Before(resolved.Resolved.Add(resolved.TTL)) { return resolved, nil } // Content expired, remove from registry r.mutex.Lock() delete(r.registry, key) r.mutex.Unlock() } // Try wildcard matching if exact match not found if !exists { if match := r.findWildcardMatch(addr); match != nil { return match, nil } } // If we have a discover hook, try P2P discovery if r.discoverHook != nil { results, err := r.discoverHook(ctx, addr) if err == nil && len(results) > 0 { // Cache the first result and return it result := results[0] r.cacheResolvedContent(key, result) return result, nil } } return nil, fmt.Errorf("address not found: %s", addr.String()) } // Announce announces content at a UCXL address func (r *BasicAddressResolver) Announce(ctx context.Context, addr *ucxl.Address, content *Content) error { if addr == nil { return fmt.Errorf("address cannot be nil") } if content == nil { return fmt.Errorf("content cannot be nil") } key := r.generateRegistryKey(addr) resolved := &ResolvedContent{ Address: addr, Content: content, Source: r.nodeID, Resolved: time.Now(), TTL: r.defaultTTL, } // Store in local registry r.mutex.Lock() r.registry[key] = resolved r.mutex.Unlock() // Call P2P announce hook if available if r.announceHook != nil { if err := r.announceHook(ctx, addr, content); err != nil { // Log but don't fail - local announcement succeeded // In a real implementation, this would be logged properly return nil } } return nil } // Discover discovers content matching a pattern func (r *BasicAddressResolver) Discover(ctx context.Context, pattern *ucxl.Address) ([]*ResolvedContent, error) { if pattern == nil { return nil, fmt.Errorf("pattern cannot be nil") } var results []*ResolvedContent // Search local registry r.mutex.RLock() for _, resolved := range r.registry { // Check if content is still valid (TTL) if time.Now().After(resolved.Resolved.Add(resolved.TTL)) { continue } // Check if address matches pattern if resolved.Address.Matches(pattern) { results = append(results, resolved) } } r.mutex.RUnlock() // Try P2P discovery if hook is available if r.discoverHook != nil { p2pResults, err := r.discoverHook(ctx, pattern) if err == nil { // Merge P2P results with local results // Cache P2P results for future use for _, result := range p2pResults { key := r.generateRegistryKey(result.Address) r.cacheResolvedContent(key, result) results = append(results, result) } } } return results, nil } // findWildcardMatch searches for wildcard matches in the registry func (r *BasicAddressResolver) findWildcardMatch(target *ucxl.Address) *ResolvedContent { r.mutex.RLock() defer r.mutex.RUnlock() for _, resolved := range r.registry { // Check if content is still valid (TTL) if time.Now().After(resolved.Resolved.Add(resolved.TTL)) { continue } // Check if target matches the registered address pattern if target.Matches(resolved.Address) { return resolved } } return nil } // generateRegistryKey generates a unique key for registry storage func (r *BasicAddressResolver) generateRegistryKey(addr *ucxl.Address) string { return fmt.Sprintf("%s:%s@%s:%s/%s", addr.Agent, addr.Role, addr.Project, addr.Task, addr.TemporalSegment.String()) } // cacheResolvedContent caches resolved content in the local registry func (r *BasicAddressResolver) cacheResolvedContent(key string, resolved *ResolvedContent) { r.mutex.Lock() defer r.mutex.Unlock() r.registry[key] = resolved } // GetRegistryStats returns statistics about the registry func (r *BasicAddressResolver) GetRegistryStats() map[string]interface{} { r.mutex.RLock() defer r.mutex.RUnlock() active := 0 expired := 0 now := time.Now() for _, resolved := range r.registry { if now.Before(resolved.Resolved.Add(resolved.TTL)) { active++ } else { expired++ } } return map[string]interface{}{ "total_entries": len(r.registry), "active_entries": active, "expired_entries": expired, "node_id": r.nodeID, } } // CleanupExpired removes expired entries from the registry func (r *BasicAddressResolver) CleanupExpired() int { r.mutex.Lock() defer r.mutex.Unlock() now := time.Now() removed := 0 for key, resolved := range r.registry { if now.After(resolved.Resolved.Add(resolved.TTL)) { delete(r.registry, key) removed++ } } return removed } // SetDefaultTTL sets the default TTL for cached content func (r *BasicAddressResolver) SetDefaultTTL(ttl time.Duration) { r.defaultTTL = ttl }