feat: enhance community attribute handling in BIRD format. Add validation for community route bodies to ensure correct tuple structure and introduce tests for single-element standard arrays, improving error handling and robustness in community attribute processing.
This commit is contained in:
@@ -10,6 +10,10 @@ import (
|
|||||||
|
|
||||||
var standardPairRE = regexp.MustCompile(`^\s*(\d{1,5}):(\d{1,5})\s*$`)
|
var standardPairRE = regexp.MustCompile(`^\s*(\d{1,5}):(\d{1,5})\s*$`)
|
||||||
|
|
||||||
|
// badCommunityAddRE matches invalid BIRD filter calls like bgp_community.add(65000,1)
|
||||||
|
// (first argument is an int). Valid form is bgp_community.add((65000,1)); — note the pair tuple.
|
||||||
|
var badCommunityAddRE = regexp.MustCompile(`\b(?:bgp_community|bgp_large_community)\.add\(\s*\d`)
|
||||||
|
|
||||||
// RouteCommunityAttrs returns BIRD 2 lines (indented with 4 spaces) for use inside a static `route … { … }` block.
|
// RouteCommunityAttrs returns BIRD 2 lines (indented with 4 spaces) for use inside a static `route … { … }` block.
|
||||||
// Empty string means no community attributes.
|
// Empty string means no community attributes.
|
||||||
func RouteCommunityAttrs(kind, name, valueJSON string) (string, error) {
|
func RouteCommunityAttrs(kind, name, valueJSON string) (string, error) {
|
||||||
@@ -20,26 +24,46 @@ func RouteCommunityAttrs(kind, name, valueJSON string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if s, ok := parseLargeFromJSONLoose(raw); ok {
|
if s, ok := parseLargeFromJSONLoose(raw); ok {
|
||||||
|
if err := validateCommunityRouteBody(s); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
_ = kind // reserved for future kinds (e.g. extended communities)
|
_ = kind // reserved for future kinds (e.g. extended communities)
|
||||||
|
|
||||||
if m := standardPairRE.FindStringSubmatch(name); len(m) == 3 {
|
if m := standardPairRE.FindStringSubmatch(name); len(m) == 3 {
|
||||||
return standardAddLine(m[1], m[2])
|
s, err := standardAddLine(m[1], m[2])
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return s, validateCommunityRouteBody(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
var obj map[string]any
|
var obj map[string]any
|
||||||
if json.Unmarshal([]byte(raw), &obj) == nil {
|
if json.Unmarshal([]byte(raw), &obj) == nil {
|
||||||
if v, ok := obj["standard"].(string); ok {
|
if v, ok := obj["standard"].(string); ok {
|
||||||
if m := standardPairRE.FindStringSubmatch(v); len(m) == 3 {
|
if m := standardPairRE.FindStringSubmatch(v); len(m) == 3 {
|
||||||
return standardAddLine(m[1], m[2])
|
s, err := standardAddLine(m[1], m[2])
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return s, validateCommunityRouteBody(s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if arr, ok := obj["standard"].([]any); ok && len(arr) == 2 {
|
if arr, ok := obj["standard"].([]any); ok {
|
||||||
a, aok := numberToUint32String(arr[0])
|
if len(arr) == 1 {
|
||||||
b, bok := numberToUint32String(arr[1])
|
return "", fmt.Errorf(`birdfmt: value_json "standard" must be a pair [a,b] or "a:b" string, got single element`)
|
||||||
if aok && bok {
|
}
|
||||||
return standardAddLine(a, b)
|
if len(arr) == 2 {
|
||||||
|
a, aok := numberToUint32String(arr[0])
|
||||||
|
b, bok := numberToUint32String(arr[1])
|
||||||
|
if aok && bok {
|
||||||
|
s, err := standardAddLine(a, b)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return s, validateCommunityRouteBody(s)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -47,13 +71,36 @@ func RouteCommunityAttrs(kind, name, valueJSON string) (string, error) {
|
|||||||
var strVal string
|
var strVal string
|
||||||
if json.Unmarshal([]byte(raw), &strVal) == nil {
|
if json.Unmarshal([]byte(raw), &strVal) == nil {
|
||||||
if m := standardPairRE.FindStringSubmatch(strVal); len(m) == 3 {
|
if m := standardPairRE.FindStringSubmatch(strVal); len(m) == 3 {
|
||||||
return standardAddLine(m[1], m[2])
|
s, err := standardAddLine(m[1], m[2])
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return s, validateCommunityRouteBody(s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// validateCommunityRouteBody rejects lines that would make BIRD 2 fail parse-check with
|
||||||
|
// "Argument 1 of 'add' expected clist / quad / ip / pair, got int" (wrong add() shape).
|
||||||
|
func validateCommunityRouteBody(body string) error {
|
||||||
|
body = strings.TrimSpace(body)
|
||||||
|
if body == "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for _, line := range strings.Split(body, "\n") {
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
if line == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if badCommunityAddRE.MatchString(line) {
|
||||||
|
return fmt.Errorf("birdfmt: invalid community line (expected pair/triple tuple inside add(), e.g. bgp_community.add((65000,1));): %q", line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func standardAddLine(a, b string) (string, error) {
|
func standardAddLine(a, b string) (string, error) {
|
||||||
ai, err1 := strconv.ParseUint(strings.TrimSpace(a), 10, 16)
|
ai, err1 := strconv.ParseUint(strings.TrimSpace(a), 10, 16)
|
||||||
bi, err2 := strconv.ParseUint(strings.TrimSpace(b), 10, 16)
|
bi, err2 := strconv.ParseUint(strings.TrimSpace(b), 10, 16)
|
||||||
@@ -111,6 +158,11 @@ func numberToUint32String(v any) (string, bool) {
|
|||||||
return "", false
|
return "", false
|
||||||
}
|
}
|
||||||
return strconv.FormatUint(uint64(x), 10), true
|
return strconv.FormatUint(uint64(x), 10), true
|
||||||
|
case int64:
|
||||||
|
if x < 0 || x > 4294967295 {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
return strconv.FormatUint(uint64(x), 10), true
|
||||||
case string:
|
case string:
|
||||||
n, err := strconv.ParseUint(strings.TrimSpace(x), 10, 32)
|
n, err := strconv.ParseUint(strings.TrimSpace(x), 10, 32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -21,3 +21,10 @@ func TestRouteCommunityAttrs_LargeJSON(t *testing.T) {
|
|||||||
t.Fatalf("got %q", s)
|
t.Fatalf("got %q", s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRouteCommunityAttrs_StandardArrayOneElement(t *testing.T) {
|
||||||
|
_, err := RouteCommunityAttrs("", "x", `{"standard":[65000]}`)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error for single-element standard pair")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user