package flowclass import ( "bytes" "fmt" "strconv" "idio.link/go/netaddr/v2" "idio.link/go/parser" ) /************************** Pfxs **************************/ type fcPfxs struct{} func (t fcPfxs) Shift(sm *parser.SM, e rune) error { switch { case e == ' ': case e == '{': sm.Push(fcPfxSet{}) case isDigit(e): return sm.Push(fcPfxSet{}).Shift(sm, e) default: return parser.NewUnrecognized("prefixes", e) } return nil } func (t fcPfxs) Coalesce(nodes []*parser.CSTNode) any { return nodes[0].Coalesce() } func (t fcPfxs) Value() *bytes.Buffer { return nil } /************************* PfxSet *************************/ type fcPfxSet struct{} func (t fcPfxSet) Shift(sm *parser.SM, e rune) error { next := fcSet[fcPfx, *netaddr.NetAddr]{ newToken: func() fcPfx { return fcPfx{} }, } if len(sm.Top().Nodes) == 0 { return sm.Push(next).Shift(sm, e) } return parser.NewUnrecognized("prefix set", e) } func (t fcPfxSet) Coalesce(nodes []*parser.CSTNode) any { return nodes[0].Coalesce() } func (t fcPfxSet) Value() *bytes.Buffer { return nil } /*************************** Pfx **************************/ type fcPfx struct{} func (t fcPfx) Shift(sm *parser.SM, e rune) error { // TODO: handle IPv6 buf := new(bytes.Buffer) switch { case isDigit(e): buf.WriteRune(e) sm.Push(fcOctet{val: buf}) case e == '.': sm.Push(fcOctet{val: buf}) case e == '/': sm.Push(fcLength{val: buf}) default: return parser.NewUnrecognized("prefix", e) } return nil } func (t fcPfx) Coalesce(nodes []*parser.CSTNode) any { var len byte addr := make([]byte, 0, 16) for _, e := range nodes { switch e.ASTNode.(type) { case fcOctet: addr = append(addr, e.Coalesce().(byte)) case fcLength: len = e.Coalesce().(byte) } } return netaddr.NewNetAddr(addr, int(len)) } func (t fcPfx) Value() *bytes.Buffer { return nil } /************************* Length *************************/ type fcLength struct { val *bytes.Buffer } func (t fcLength) Shift(sm *parser.SM, e rune) error { return accNatural(e, "128", t.val, "length") } func (t fcLength) Coalesce(_ []*parser.CSTNode) any { l, _ := strconv.Atoi(string(t.val.Bytes())) return byte(l) } func (t fcLength) Value() *bytes.Buffer { return t.val } /************************* Octet **************************/ type fcOctet struct { val *bytes.Buffer } func (t fcOctet) Shift(sm *parser.SM, e rune) error { return accNatural(e, "255", t.val, "octet") } func (t fcOctet) Coalesce(_ []*parser.CSTNode) any { o, _ := strconv.Atoi(string(t.val.Bytes())) return byte(o) } func (t fcOctet) Value() *bytes.Buffer { return t.val } func accNatural( b rune, max string, buf *bytes.Buffer, msg string, ) error { switch { case isDigit(b): dropLeadingZeros(buf) buf.WriteRune(b) val := buf.Bytes() if natGreaterThan(val, []byte(max)) { return fmt.Errorf("%s: %s cannot be greater than %s", msg, val, max) } case buf.Len() == 0: return fmt.Errorf("%s: unexpected input: '%s'", msg, string(b)) default: return parser.NewUnrecognized(msg, b) } return nil } func dropLeadingZeros(buf *bytes.Buffer) { if buf == nil { return } if buf.Len() >= 1 && buf.Bytes()[0] == '0' { buf.Truncate(1) } return } func natGreaterThan(val []byte, max []byte) bool { switch { case len(val) < len(max): return false case len(val) > len(max): return true } for i := 0; i < len(val); i++ { if max[i] > val[i] { return false } } return true }