package scans import ( "crypto/rand" "encoding/hex" "errors" "net" "sync" "time" ) type CreateScanRequest struct { Name string `json:"name"` CIDRs []string `json:"cidrs"` ExcludeIPs []string `json:"exclude_ips"` SNMPCredentialsID string `json:"snmp_credentials_id"` Options ScanOptions `json:"options"` } type ScanOptions struct { PingTimeoutMS int `json:"ping_timeout_ms"` PingRetries int `json:"ping_retries"` MaxParallelHosts int `json:"max_parallel_hosts"` PortScanEnabled bool `json:"port_scan_enabled"` Ports []int `json:"ports"` } type ScanJob struct { ID string `json:"id"` Name string `json:"name"` Status string `json:"status"` CIDRs []string `json:"cidrs"` ExcludeIPs []string `json:"exclude_ips"` SNMPCredentialsID string `json:"snmp_credentials_id"` Options ScanOptions `json:"options"` CreatedAt time.Time `json:"created_at"` StartedAt time.Time `json:"started_at,omitempty"` FinishedAt time.Time `json:"finished_at,omitempty"` } type Store interface { CreateScan(CreateScanRequest) (ScanJob, error) GetScan(id string) (ScanJob, bool) } type MemoryStore struct { mu sync.RWMutex jobs map[string]ScanJob } func NewMemoryStore() *MemoryStore { return &MemoryStore{ jobs: make(map[string]ScanJob), } } func (s *MemoryStore) CreateScan(req CreateScanRequest) (ScanJob, error) { if len(req.CIDRs) == 0 { return ScanJob{}, errors.New("cidrs must not be empty") } for _, cidr := range req.CIDRs { if _, _, err := net.ParseCIDR(cidr); err != nil { return ScanJob{}, errors.New("invalid cidr: " + cidr) } } for _, ip := range req.ExcludeIPs { if net.ParseIP(ip) == nil { return ScanJob{}, errors.New("invalid exclude ip: " + ip) } } applyDefaultOptions(&req.Options) id, err := newID() if err != nil { return ScanJob{}, err } now := time.Now().UTC() job := ScanJob{ ID: id, Name: req.Name, Status: "queued", CIDRs: req.CIDRs, ExcludeIPs: req.ExcludeIPs, SNMPCredentialsID: req.SNMPCredentialsID, Options: req.Options, CreatedAt: now, } s.mu.Lock() s.jobs[id] = job s.mu.Unlock() return job, nil } func (s *MemoryStore) GetScan(id string) (ScanJob, bool) { s.mu.RLock() job, ok := s.jobs[id] s.mu.RUnlock() return job, ok } func applyDefaultOptions(opts *ScanOptions) { if opts.PingTimeoutMS <= 0 { opts.PingTimeoutMS = 700 } if opts.PingRetries < 0 { opts.PingRetries = 0 } if opts.MaxParallelHosts <= 0 { opts.MaxParallelHosts = 128 } if len(opts.Ports) == 0 { opts.Ports = []int{22, 80, 443, 161} } } func newID() (string, error) { var b [16]byte if _, err := rand.Read(b[:]); err != nil { return "", err } return hex.EncodeToString(b[:]), nil }