check / check (push) Canceled after 0s
Takes .golangci.yml, .dockerignore, .gitignore, .editorconfig, the workflow and REPO_POLICIES.md from sneak/prompts 0653579, with this repository's own entries after the shared content. Lint (golangci-lint v2.14.0) and test are Dockerfile phases that script/lint and script/test build alone with a new CACHEBUST value, keeping Go's and golangci-lint's caches; the image build depends on both. make fmt and make fmt-check also format the markdown with prettier, pinned in yarn.lock. The agent guidance moves to AGENTS.md. The Go code is fixed for what the new configuration finds, and the AS and prefix detail reads now return the rows' error after their loops. Model: opus-5-5
165 lines
3.3 KiB
Go
165 lines
3.3 KiB
Go
package asinfo
|
|
|
|
import (
|
|
"bytes"
|
|
"compress/gzip"
|
|
_ "embed"
|
|
"encoding/json"
|
|
"io"
|
|
"sync"
|
|
)
|
|
|
|
//go:embed asdata.json.gz
|
|
var asDataGZ []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 (
|
|
//nolint:gochecknoglobals // Singleton pattern for embedded data
|
|
defaultRegistry *Registry
|
|
//nolint:gochecknoglobals // Singleton pattern for embedded data
|
|
once sync.Once
|
|
)
|
|
|
|
// initRegistry initializes the default registry with embedded data
|
|
func initRegistry() {
|
|
defaultRegistry = &Registry{
|
|
byASN: make(map[int]*Info),
|
|
}
|
|
|
|
// Decompress the gzipped data
|
|
gzReader, err := gzip.NewReader(bytes.NewReader(asDataGZ))
|
|
if err != nil {
|
|
panic("failed to create gzip reader: " + err.Error())
|
|
}
|
|
defer func() {
|
|
_ = gzReader.Close()
|
|
}()
|
|
|
|
jsonData, err := io.ReadAll(gzReader)
|
|
if err != nil {
|
|
panic("failed to decompress AS data: " + err.Error())
|
|
}
|
|
|
|
var asInfos []Info
|
|
|
|
err = json.Unmarshal(jsonData, &asInfos)
|
|
if 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
|
|
infoCopy := *info
|
|
|
|
return &infoCopy, 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
|
|
}
|