// Package asinfo provides information about Autonomous Systems (AS) package asinfo import ( _ "embed" "encoding/json" "sync" ) //go:embed asdata.json var asDataJSON []byte // Info represents information about an Autonomous System type Info struct { ASN int `json:"asn"` Handle string `json:"handle"` Description string `json:"description"` } // Registry provides access to AS information type Registry struct { mu sync.RWMutex byASN map[int]*Info } var ( defaultRegistry *Registry once sync.Once ) // initRegistry initializes the default registry with embedded data func initRegistry() { defaultRegistry = &Registry{ byASN: make(map[int]*Info), } var asInfos []Info if err := json.Unmarshal(asDataJSON, &asInfos); err != nil { panic("failed to unmarshal embedded AS data: " + err.Error()) } for i := range asInfos { info := &asInfos[i] defaultRegistry.byASN[info.ASN] = info } } // Get returns AS information for the given ASN func Get(asn int) (*Info, bool) { once.Do(initRegistry) defaultRegistry.mu.RLock() defer defaultRegistry.mu.RUnlock() info, ok := defaultRegistry.byASN[asn] if !ok { return nil, false } // Return a copy to prevent mutation copy := *info return ©, true } // GetDescription returns just the description for an ASN func GetDescription(asn int) string { info, ok := Get(asn) if !ok { return "" } return info.Description } // GetHandle returns just the handle for an ASN func GetHandle(asn int) string { info, ok := Get(asn) if !ok { return "" } return info.Handle } // Total returns the total number of AS entries in the registry func Total() int { once.Do(initRegistry) defaultRegistry.mu.RLock() defer defaultRegistry.mu.RUnlock() return len(defaultRegistry.byASN) } // All returns all AS information as a slice // Note: This creates a copy of all data, use sparingly func All() []Info { once.Do(initRegistry) defaultRegistry.mu.RLock() defer defaultRegistry.mu.RUnlock() result := make([]Info, 0, len(defaultRegistry.byASN)) for _, info := range defaultRegistry.byASN { result = append(result, *info) } return result } // Search searches for AS entries where the handle or description contains the query // This is a simple case-sensitive substring search func Search(query string) []Info { once.Do(initRegistry) defaultRegistry.mu.RLock() defer defaultRegistry.mu.RUnlock() var results []Info for _, info := range defaultRegistry.byASN { if contains(info.Handle, query) || contains(info.Description, query) { results = append(results, *info) } } return results } // contains is a simple substring search func contains(s, substr string) bool { return len(substr) > 0 && len(s) >= len(substr) && containsImpl(s, substr) } func containsImpl(s, substr string) bool { for i := 0; i <= len(s)-len(substr); i++ { if s[i:i+len(substr)] == substr { return true } } return false }