package dht import ( "context" "fmt" "math/rand" "sync" "time" ) // MockDHT implements the DHT interface for testing purposes // It provides the same interface as the real DHT but operates in-memory type MockDHT struct { storage map[string][]byte providers map[string][]string // key -> list of peer IDs peers map[string]*MockPeer latency time.Duration failureRate float64 mutex sync.RWMutex } type MockPeer struct { ID string Address string Online bool } // NewMockDHT creates a new mock DHT instance func NewMockDHT() *MockDHT { return &MockDHT{ storage: make(map[string][]byte), providers: make(map[string][]string), peers: make(map[string]*MockPeer), latency: 10 * time.Millisecond, // Default 10ms latency failureRate: 0.0, // No failures by default } } // SetLatency configures network latency simulation func (m *MockDHT) SetLatency(latency time.Duration) { m.latency = latency } // SetFailureRate configures failure simulation (0.0 = no failures, 1.0 = always fail) func (m *MockDHT) SetFailureRate(rate float64) { m.failureRate = rate } // simulateNetworkConditions applies latency and potential failures func (m *MockDHT) simulateNetworkConditions(ctx context.Context) error { // Check for context cancellation if ctx.Err() != nil { return ctx.Err() } // Simulate network latency if m.latency > 0 { select { case <-time.After(m.latency): case <-ctx.Done(): return ctx.Err() } } // Simulate network failures if m.failureRate > 0 && rand.Float64() < m.failureRate { return fmt.Errorf("mock network failure (simulated)") } return nil } // PutValue stores a key-value pair in the DHT func (m *MockDHT) PutValue(ctx context.Context, key string, value []byte) error { if err := m.simulateNetworkConditions(ctx); err != nil { return err } m.mutex.Lock() defer m.mutex.Unlock() m.storage[key] = make([]byte, len(value)) copy(m.storage[key], value) return nil } // GetValue retrieves a value from the DHT func (m *MockDHT) GetValue(ctx context.Context, key string) ([]byte, error) { if err := m.simulateNetworkConditions(ctx); err != nil { return nil, err } m.mutex.RLock() defer m.mutex.RUnlock() value, exists := m.storage[key] if !exists { return nil, fmt.Errorf("key not found: %s", key) } // Return a copy to prevent external modification result := make([]byte, len(value)) copy(result, value) return result, nil } // Provide announces that this node can provide the given key func (m *MockDHT) Provide(ctx context.Context, key string) error { if err := m.simulateNetworkConditions(ctx); err != nil { return err } m.mutex.Lock() defer m.mutex.Unlock() // Mock peer ID for this node peerID := "mock-peer-local" if _, exists := m.providers[key]; !exists { m.providers[key] = make([]string, 0) } // Add peer to providers list if not already present for _, existingPeer := range m.providers[key] { if existingPeer == peerID { return nil // Already providing } } m.providers[key] = append(m.providers[key], peerID) return nil } // FindProviders finds peers that can provide the given key func (m *MockDHT) FindProviders(ctx context.Context, key string, limit int) ([]string, error) { if err := m.simulateNetworkConditions(ctx); err != nil { return nil, err } m.mutex.RLock() defer m.mutex.RUnlock() providers, exists := m.providers[key] if !exists { return []string{}, nil } // Apply limit if specified if limit > 0 && len(providers) > limit { result := make([]string, limit) copy(result, providers[:limit]) return result, nil } // Return copy of providers result := make([]string, len(providers)) copy(result, providers) return result, nil } // AddPeer adds a mock peer to the network func (m *MockDHT) AddPeer(peerID, address string) { m.mutex.Lock() defer m.mutex.Unlock() m.peers[peerID] = &MockPeer{ ID: peerID, Address: address, Online: true, } } // RemovePeer removes a mock peer from the network func (m *MockDHT) RemovePeer(peerID string) { m.mutex.Lock() defer m.mutex.Unlock() delete(m.peers, peerID) // Remove from all provider lists for key, providers := range m.providers { filtered := make([]string, 0, len(providers)) for _, provider := range providers { if provider != peerID { filtered = append(filtered, provider) } } m.providers[key] = filtered } } // GetPeers returns all mock peers func (m *MockDHT) GetPeers() map[string]*MockPeer { m.mutex.RLock() defer m.mutex.RUnlock() result := make(map[string]*MockPeer) for id, peer := range m.peers { result[id] = &MockPeer{ ID: peer.ID, Address: peer.Address, Online: peer.Online, } } return result } // ListKeys returns all stored keys (for testing purposes) func (m *MockDHT) ListKeys() []string { m.mutex.RLock() defer m.mutex.RUnlock() keys := make([]string, 0, len(m.storage)) for key := range m.storage { keys = append(keys, key) } return keys } // Clear removes all data from the mock DHT func (m *MockDHT) Clear() { m.mutex.Lock() defer m.mutex.Unlock() m.storage = make(map[string][]byte) m.providers = make(map[string][]string) m.peers = make(map[string]*MockPeer) } // GetStats returns statistics about the mock DHT func (m *MockDHT) GetStats() MockDHTStats { m.mutex.RLock() defer m.mutex.RUnlock() return MockDHTStats{ TotalKeys: len(m.storage), TotalPeers: len(m.peers), TotalProviders: func() int { total := 0 for _, providers := range m.providers { total += len(providers) } return total }(), Latency: m.latency, FailureRate: m.failureRate, } } type MockDHTStats struct { TotalKeys int `json:"total_keys"` TotalPeers int `json:"total_peers"` TotalProviders int `json:"total_providers"` Latency time.Duration `json:"latency"` FailureRate float64 `json:"failure_rate"` }