package atlas import ( "strings" "crypto-miner-server/internal/strategy" ) // GossipHint is one negative-knowledge skip shared between LAN siblings. type GossipHint struct { Tier string `json:"tier"` Condition string `json:"condition"` Reason string `json:"reason,omitempty"` } // SubnetPrefix returns the /24 (IPv4) or /48-ish (IPv6) prefix used for LAN sibling matching. func SubnetPrefix(ip string) string { return strategy.FingerprintFromAuth("", ip, false).Subnet } // NormalizeGossipHint trims and validates one gossip hint. func NormalizeGossipHint(h GossipHint) (GossipHint, bool) { h.Tier = strings.TrimSpace(h.Tier) h.Condition = strings.TrimSpace(h.Condition) h.Reason = strings.TrimSpace(h.Reason) if h.Tier == "" || h.Condition == "" { return GossipHint{}, false } if h.Reason == "" { h.Reason = "lan gossip" } return h, true } // NormalizeGossipHints drops invalid hints while preserving order. func NormalizeGossipHints(in []GossipHint) []GossipHint { if len(in) == 0 { return nil } out := make([]GossipHint, 0, len(in)) for _, h := range in { if norm, ok := NormalizeGossipHint(h); ok { out = append(out, norm) } } return out } // SkipsFromHints converts gossip hints to atlas skips for agent merge. func SkipsFromHints(hints []GossipHint) []AtlasSkip { out := make([]AtlasSkip, 0, len(hints)) for _, h := range hints { if norm, ok := NormalizeGossipHint(h); ok { out = append(out, AtlasSkip{ Tier: norm.Tier, Condition: norm.Condition, Reason: norm.Reason, }) } } return out } // MergeGossipSkips merges incoming LAN hints into existing skips without duplicates. func MergeGossipSkips(existing []AtlasSkip, incoming []GossipHint) []AtlasSkip { hints := NormalizeGossipHints(incoming) if len(hints) == 0 { return existing } have := make(map[string]bool, len(existing)+len(hints)) out := append([]AtlasSkip(nil), existing...) for _, s := range existing { have[s.Tier+"|"+s.Condition] = true } for _, h := range hints { key := h.Tier + "|" + h.Condition if have[key] { continue } have[key] = true out = append(out, AtlasSkip{ Tier: h.Tier, Condition: h.Condition, Reason: h.Reason, }) } return out }