package pipeline import ( "fmt" "net/netip" "sort" "strconv" "strings" "evobgp/internal/birdfmt" "evobgp/internal/store" ) type staticCommunityRoutes struct { CommunityID string RoutesV4 []birdfmt.StaticRoute RoutesV6 []birdfmt.StaticRoute } // materializeRowsForBird turns prefix rows into export-filter CIDR lists, optional AS_PATH rules, // and static routes grouped by community. The same prefix can appear in multiple community groups. func materializeRowsForBird(st store.Backend, tenantID string, rows []store.PrefixRow) ( filterV4, filterV6 []netip.Prefix, pathASNs []int64, staticByCommunity []staticCommunityRoutes, err error, ) { var pathSeen map[int64]struct{} addPath := func(n int64) { if pathSeen == nil { pathSeen = make(map[int64]struct{}) } if _, ok := pathSeen[n]; ok { return } pathSeen[n] = struct{}{} pathASNs = append(pathASNs, n) } filterSeen := make(map[string]struct{}) type staticGroup struct { v4 map[string]birdfmt.StaticRoute v6 map[string]birdfmt.StaticRoute } staticGroups := make(map[string]*staticGroup) routeBodyByCommunity := make(map[string]string) for _, pr := range rows { p := strings.TrimSpace(pr.Prefix) if strings.HasPrefix(p, "as:") { n, err := strconv.ParseInt(strings.TrimPrefix(p, "as:"), 10, 64) if err != nil || !store.ValidASN(n) { continue } addPath(n) continue } pfx, err := netip.ParsePrefix(p) if err != nil { continue } pfx = pfx.Masked() key := pfx.String() if _, ok := filterSeen[key]; !ok { filterSeen[key] = struct{}{} if pfx.Addr().Is4() { filterV4 = append(filterV4, pfx) } else { filterV6 = append(filterV6, pfx) } } communityID := "" if pr.CommunityID != nil { communityID = strings.TrimSpace(*pr.CommunityID) } body, ok := routeBodyByCommunity[communityID] if !ok { body, err = communityRouteBody(st, tenantID, pr.CommunityID) if err != nil { return nil, nil, nil, nil, err } routeBodyByCommunity[communityID] = body } grp, ok := staticGroups[communityID] if !ok { grp = &staticGroup{ v4: make(map[string]birdfmt.StaticRoute), v6: make(map[string]birdfmt.StaticRoute), } staticGroups[communityID] = grp } if pfx.Addr().Is4() { if _, exists := grp.v4[key]; !exists { grp.v4[key] = birdfmt.StaticRoute{Prefix: pfx, RouteBody: body} } } else if pfx.Addr().Is6() { if _, exists := grp.v6[key]; !exists { grp.v6[key] = birdfmt.StaticRoute{Prefix: pfx, RouteBody: body} } } } groupKeys := make([]string, 0, len(staticGroups)) for k := range staticGroups { groupKeys = append(groupKeys, k) } sort.Strings(groupKeys) for _, gid := range groupKeys { grp := staticGroups[gid] out := staticCommunityRoutes{CommunityID: gid} v4Keys := make([]string, 0, len(grp.v4)) for k := range grp.v4 { v4Keys = append(v4Keys, k) } sort.Strings(v4Keys) for _, k := range v4Keys { out.RoutesV4 = append(out.RoutesV4, grp.v4[k]) } v6Keys := make([]string, 0, len(grp.v6)) for k := range grp.v6 { v6Keys = append(v6Keys, k) } sort.Strings(v6Keys) for _, k := range v6Keys { out.RoutesV6 = append(out.RoutesV6, grp.v6[k]) } staticByCommunity = append(staticByCommunity, out) } return filterV4, filterV6, pathASNs, staticByCommunity, nil } func communityRouteBody(st store.Backend, tenantID string, cid *string) (string, error) { if cid == nil { return "", nil } id := strings.TrimSpace(*cid) if id == "" { return "", nil } c, err := st.GetCommunity(tenantID, id) if err != nil { return "", fmt.Errorf("community %s: %w", id, err) } return birdfmt.RouteCommunityAttrs("", c.Community, c.ValueJSON) }