// Package transport - Multi-transport failover for war zones package transport import ( "fmt" "net" "sync" "time" ) // FailoverTransport implements automatic failover between multiple transports // Critical for war zones: Tor → Mesh → Bluetooth → Direct type FailoverTransport struct { transports []Transport // Ordered list of transports to try currentIndex int // Currently active transport mu sync.RWMutex healthCheck map[TransportType]time.Time // Last successful connection retryInterval time.Duration maxRetries int onFailover func(from, to TransportType) // Callback when failing over } // FailoverConfig holds failover configuration type FailoverConfig struct { RetryInterval time.Duration // Time between retry attempts MaxRetries int // Max retries per transport before failing over HealthCheckInterval time.Duration // How often to health check transports } // NewFailoverTransport creates a new failover transport func NewFailoverTransport(transports []Transport, config *FailoverConfig) *FailoverTransport { if config == nil { config = &FailoverConfig{ RetryInterval: 5 * time.Second, MaxRetries: 3, HealthCheckInterval: 30 * time.Second, } } ft := &FailoverTransport{ transports: transports, currentIndex: 0, healthCheck: make(map[TransportType]time.Time), retryInterval: config.RetryInterval, maxRetries: config.MaxRetries, } // Start health checking go ft.healthCheckLoop(config.HealthCheckInterval) return ft } // Connect attempts to connect using transports in priority order func (ft *FailoverTransport) Connect(address string) (net.Conn, error) { // Try each transport in order for attempt := 0; attempt < len(ft.transports)*ft.maxRetries; attempt++ { ft.mu.Lock() index := ft.currentIndex transport := ft.transports[index] ft.mu.Unlock() transportType := transport.Type() // Attempt connection conn, err := transport.Connect(address) if err == nil { // Success! ft.mu.Lock() ft.healthCheck[transportType] = time.Now() ft.mu.Unlock() fmt.Printf("✓ Connected via %s\n", transportType) return conn, nil } fmt.Printf("✗ %s connection failed: %v\n", transportType, err) // Move to next transport ft.mu.Lock() oldIndex := ft.currentIndex ft.currentIndex = (ft.currentIndex + 1) % len(ft.transports) newTransport := ft.transports[ft.currentIndex] ft.mu.Unlock() // Notify failover if ft.onFailover != nil && oldIndex != ft.currentIndex { go ft.onFailover(transport.Type(), newTransport.Type()) } // Wait before retry time.Sleep(ft.retryInterval) } return nil, fmt.Errorf("all transports failed after %d attempts", len(ft.transports)*ft.maxRetries) } // Listen starts listening on the highest priority available transport func (ft *FailoverTransport) Listen(address string) (net.Listener, error) { ft.mu.RLock() defer ft.mu.RUnlock() // Try each transport for _, transport := range ft.transports { listener, err := transport.Listen(address) if err == nil { fmt.Printf("✓ Listening on %s\n", transport.Type()) return listener, nil } fmt.Printf("✗ Listen failed on %s: %v\n", transport.Type(), err) } return nil, fmt.Errorf("all transports failed to listen") } // Type returns the currently active transport type func (ft *FailoverTransport) Type() TransportType { ft.mu.RLock() defer ft.mu.RUnlock() if ft.currentIndex < len(ft.transports) { return ft.transports[ft.currentIndex].Type() } return TransportType(-1) } // Close closes all transports func (ft *FailoverTransport) Close() error { ft.mu.Lock() defer ft.mu.Unlock() var lastErr error for _, transport := range ft.transports { if err := transport.Close(); err != nil { lastErr = err } } return lastErr } // GetCurrentTransport returns the currently active transport func (ft *FailoverTransport) GetCurrentTransport() Transport { ft.mu.RLock() defer ft.mu.RUnlock() if ft.currentIndex < len(ft.transports) { return ft.transports[ft.currentIndex] } return nil } // GetTransports returns all available transports func (ft *FailoverTransport) GetTransports() []Transport { ft.mu.RLock() defer ft.mu.RUnlock() return ft.transports } // SetTransportPriority reorders transports by priority func (ft *FailoverTransport) SetTransportPriority(types []TransportType) error { ft.mu.Lock() defer ft.mu.Unlock() if len(types) != len(ft.transports) { return fmt.Errorf("priority list length mismatch") } // Create new ordered list newTransports := make([]Transport, 0, len(ft.transports)) for _, targetType := range types { found := false for _, transport := range ft.transports { if transport.Type() == targetType { newTransports = append(newTransports, transport) found = true break } } if !found { return fmt.Errorf("transport type %s not found", targetType) } } ft.transports = newTransports ft.currentIndex = 0 return nil } // healthCheckLoop periodically checks transport health func (ft *FailoverTransport) healthCheckLoop(interval time.Duration) { ticker := time.NewTicker(interval) defer ticker.Stop() for range ticker.C { ft.performHealthCheck() } } // performHealthCheck checks if current transport is still healthy func (ft *FailoverTransport) performHealthCheck() { ft.mu.RLock() current := ft.transports[ft.currentIndex] lastSuccess, exists := ft.healthCheck[current.Type()] ft.mu.RUnlock() // If no successful connection in last 5 minutes, try next transport if !exists || time.Since(lastSuccess) > 5*time.Minute { ft.mu.Lock() oldIndex := ft.currentIndex ft.currentIndex = (ft.currentIndex + 1) % len(ft.transports) ft.mu.Unlock() if ft.onFailover != nil && oldIndex != ft.currentIndex { ft.mu.RLock() go ft.onFailover(ft.transports[oldIndex].Type(), ft.transports[ft.currentIndex].Type()) ft.mu.RUnlock() } } } // SetOnFailover sets callback for failover events func (ft *FailoverTransport) SetOnFailover(callback func(from, to TransportType)) { ft.mu.Lock() defer ft.mu.Unlock() ft.onFailover = callback } // GetHealthStatus returns health status of all transports func (ft *FailoverTransport) GetHealthStatus() map[TransportType]time.Time { ft.mu.RLock() defer ft.mu.RUnlock() status := make(map[TransportType]time.Time) for k, v := range ft.healthCheck { status[k] = v } return status } // String returns a string representation func (ft *FailoverTransport) String() string { ft.mu.RLock() defer ft.mu.RUnlock() currentType := "none" if ft.currentIndex < len(ft.transports) { currentType = ft.transports[ft.currentIndex].Type().String() } return fmt.Sprintf("FailoverTransport(current=%s, transports=%d)", currentType, len(ft.transports)) } // ConnectWithRetry attempts connection with exponential backoff func (ft *FailoverTransport) ConnectWithRetry(address string, maxAttempts int) (net.Conn, error) { var conn net.Conn var err error backoff := 1 * time.Second for attempt := 1; attempt <= maxAttempts; attempt++ { conn, err = ft.Connect(address) if err == nil { return conn, nil } fmt.Printf("Attempt %d/%d failed, retrying in %v...\n", attempt, maxAttempts, backoff) time.Sleep(backoff) // Exponential backoff with jitter backoff *= 2 if backoff > 60*time.Second { backoff = 60 * time.Second } } return nil, fmt.Errorf("failed after %d attempts: %w", maxAttempts, err) }