package config import ( "fmt" "strconv" "strings" ) type PortGroup struct { Proto string `yaml:"proto"` Ports PortSpec `yaml:"ports"` } // ParsedPorts returns individual port numbers and ranges as (start, end) pairs. func (pg *PortGroup) ParsedPorts() (singles []uint16, ranges [][2]uint16, err error) { for _, p := range pg.Ports { if strings.Contains(p, "-") { parts := strings.SplitN(p, "-", 2) start, err := strconv.ParseUint(parts[0], 10, 16) if err != nil { return nil, nil, fmt.Errorf("invalid port range start %q: %w", parts[0], err) } end, err := strconv.ParseUint(parts[1], 10, 16) if err != nil { return nil, nil, fmt.Errorf("invalid port range end %q: %w", parts[1], err) } if start > end { return nil, nil, fmt.Errorf("port range %d-%d: start > end", start, end) } ranges = append(ranges, [2]uint16{uint16(start), uint16(end)}) } else { port, err := strconv.ParseUint(p, 10, 16) if err != nil { return nil, nil, fmt.Errorf("invalid port %q: %w", p, err) } singles = append(singles, uint16(port)) } } return singles, ranges, nil } func (c *Config) validatePortGroups() error { for name, pg := range c.PortGroups { if pg.Proto == "" { return fmt.Errorf("portgroup %q: proto required", name) } if pg.Proto != "tcp" && pg.Proto != "udp" { return fmt.Errorf("portgroup %q: proto must be tcp or udp, got %q", name, pg.Proto) } if len(pg.Ports) == 0 { return fmt.Errorf("portgroup %q: at least one port required", name) } if _, _, err := pg.ParsedPorts(); err != nil { return fmt.Errorf("portgroup %q: %w", name, err) } } return nil }