From d562ad041e291583747c36bb275dbfc2c80896e7 Mon Sep 17 00:00:00 2001 From: r2dev2 Date: Sun, 10 May 2026 16:59:23 -0700 Subject: [PATCH 1/9] ci: build alo-latest without make examples --- .github/workflows/test.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bad4cfea..b6aa85bc 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -20,7 +20,8 @@ jobs: - name: Build run: | make - make examples + mkdir -p .bin + CGO_ENABLED=0 go build -o .bin/alo-latest std/examples/svs/alo-latest/main.go - name: Test run: make test From 18091b1b7d73311c5da7d9932952c3745cbf5af3 Mon Sep 17 00:00:00 2001 From: r2dev2 Date: Sun, 10 May 2026 17:05:29 -0700 Subject: [PATCH 2/9] lint: gofmt config and pet --- dv/config/config.go | 2 +- fw/mgmt/pet.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dv/config/config.go b/dv/config/config.go index 0de66d95..04575edc 100644 --- a/dv/config/config.go +++ b/dv/config/config.go @@ -115,7 +115,7 @@ func DefaultConfig() *Config { PrefixInsertionKeychainUri: "", TrustSchemaPath: "", PrefixInsertionTrustSchemaPath: "", - PrefixStateReplicate: true, + PrefixStateReplicate: true, } } diff --git a/fw/mgmt/pet.go b/fw/mgmt/pet.go index ed5ea6ed..8da29dc0 100644 --- a/fw/mgmt/pet.go +++ b/fw/mgmt/pet.go @@ -206,7 +206,7 @@ func (p *PETModule) list(interest *Interest) { Name: entry.Name, EgressRecords: make([]*mgmt.EgressRecord, 0, len(entry.EgressRouters)), NextHopRecords: make([]*mgmt.NextHopRecord, 0, len(entry.NextHops)), - Multicast: entry.Multicast, + Multicast: entry.Multicast, } for _, egress := range entry.EgressRouters { From f48bd8c7b5eeb05d0904c215833e23fb76b4e112 Mon Sep 17 00:00:00 2001 From: r2dev2 Date: Mon, 11 May 2026 10:08:22 -0700 Subject: [PATCH 3/9] ci: run ci on dv2 push --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b6aa85bc..0650eabb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,7 +1,7 @@ name: test on: push: - branches: ["main"] + branches: ["main", "dv2"] pull_request: jobs: From 97b3e0a780567e85c7f5cc17c06835e0200c5257 Mon Sep 17 00:00:00 2001 From: r2dev2 Date: Wed, 13 May 2026 11:58:29 -0700 Subject: [PATCH 4/9] e2e: re-generate trust schema --- e2e/client_lvs_minindn.tlv | Bin 297 -> 378 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/e2e/client_lvs_minindn.tlv b/e2e/client_lvs_minindn.tlv index 4f0e0e2fc9db00d3bcd8dc576c6b6cbb4ff9abc1..a948b439d545387470c8f994c84dcde67b5a837e 100644 GIT binary patch literal 378 zcmYk1%TB~F3`Kj}rb*u&egKMhW4k;Srv3KcXhnkoN}Em*K}PHdl>OlgEJ z@`x57+)5n_5A?<*PmeES<6-*T+4Hh4>)RTH!HABTM@)#7V+NEHLZX~7V&#;OhDs_U z8B!8k=*1@6gQ4rP&l;5d1?fUT@&XZL45MrbM%gklWk<-A9m6W;1RH9p3@6IVw(a3w zG_tPh@}IgTl)7>tRH`T$g>pr3$`$|iYeKGEGb-&jgd%iO8MQ=wU+2q7CJr907{=VA r>(jgV`61lMk?nl)XQrdNb4D}g4z9wc z%BUuwJV@?uGSHwbXk^bc@&Mjt2+Esw*;>*Yr;wufl(_*LaiJbK{*kEa$-c~h7ij}l98n1 z_h-Bu;LBY`D~5f_Saoh^UZ2XnjEuGqbRNq0cs-ygw^Ar)y`$yhOBP3|M<3NP52x`D DrEn_B From 308103bb41125e75a27feeb47432eed14c0d555f Mon Sep 17 00:00:00 2001 From: r2dev2 Date: Wed, 13 May 2026 14:14:08 -0700 Subject: [PATCH 5/9] dv: incremental psd egress update --- dv/dv/router.go | 55 ++++++++++++++++++++++++++++----------------- dv/dv/table_algo.go | 1 - 2 files changed, 35 insertions(+), 21 deletions(-) diff --git a/dv/dv/router.go b/dv/dv/router.go index 7599e0a0..67977db5 100644 --- a/dv/dv/router.go +++ b/dv/dv/router.go @@ -50,6 +50,8 @@ type Router struct { // prefix state daemon pfx *PrefixModule + // installed PSD sync/data PET egresses keyed by router TLV. + psdSyncInstalled map[string]enc.Name // neighbor table neighbors *table.NeighborTable // routing information base @@ -129,6 +131,8 @@ func NewRouter(config *config.Config, engine ndn.Engine) (*Router, error) { client: object.NewClient(engine, store, trust), nfdc: nfdc.NewNfdMgmtThread(engine), mutex: sync.Mutex{}, + + psdSyncInstalled: make(map[string]enc.Name), } // Initialize advertisement module @@ -290,19 +294,13 @@ func (dv *Router) register() (err error) { Name: prefix, }) } - // // Allow outgoing local-prefix-sync Interests to use two-phase forwarding. - // // Incoming Interests still terminate locally on the same prefix. - // dv.execMgmtRetry("pet", "add-egress", &mgmt.ControlArgs{ - // Name: dv.pfx.SyncPrefix(), - // Egress: &mgmt.EgressRecord{Name: neighborsPrefix.Clone()}, - // Multicast: true, - // }) // Set Advertisement Sync to localhop neighbors dv.execMgmtRetry("pet", "add-egress", &mgmt.ControlArgs{ Name: dv.config.AdvertisementSyncPrefix(), Egress: &mgmt.EgressRecord{Name: neighborsPrefix.Clone()}, }) - // Set broadcast strategy for Advertisement Sync prefix + + // Keep advertisement sync on the current working setup. dv.execMgmtRetry("strategy-choice", "set", &mgmt.ControlArgs{ Name: dv.config.AdvertisementSyncPrefix(), Strategy: &mgmt.Strategy{Name: defn.BROADCAST_STRATEGY}, @@ -323,33 +321,50 @@ func (dv *Router) execMgmtRetry(module, cmd string, args *mgmt.ControlArgs) { } } -// updatePsdSyncPrefix updates the PSD sync prefix PET entry with all routers as egress for BIER delivery. +// updatePsdPrefix incrementally updates PSD sync/data PET egresses. func (dv *Router) updatePsdPrefix() { synPfx := dv.pfx.SyncPrefix() grpPfx := dv.pfx.GroupPrefix() - // First, remove existing egress entries for this prefix + + dv.mutex.Lock() + desired := make(map[string]enc.Name) for _, router := range dv.rib.Entries() { + name := router.Name().Clone() + desired[name.TlvStr()] = name + } + installed := make(map[string]enc.Name, len(dv.psdSyncInstalled)) + for key, name := range dv.psdSyncInstalled { + installed[key] = name.Clone() + } + dv.psdSyncInstalled = desired + dv.mutex.Unlock() + + for key, router := range installed { + if _, ok := desired[key]; ok { + continue + } dv.execMgmtRetry("pet", "remove-egress", &mgmt.ControlArgs{ Name: synPfx, - Egress: &mgmt.EgressRecord{Name: router.Name().Clone()}, + Egress: &mgmt.EgressRecord{Name: router.Clone()}, }) - // Protocol naming convention dv.execMgmtRetry("pet", "remove-egress", &mgmt.ControlArgs{ - Name: grpPfx.Clone().Append(router.Name().Clone()...), - Egress: &mgmt.EgressRecord{Name: router.Name().Clone()}, + Name: grpPfx.Clone().Append(router.Clone()...), + Egress: &mgmt.EgressRecord{Name: router.Clone()}, }) } - // Then add all routers as egress - for _, router := range dv.rib.Entries() { + + for key, router := range desired { + if _, ok := installed[key]; ok { + continue + } dv.execMgmtRetry("pet", "add-egress", &mgmt.ControlArgs{ Name: synPfx, - Egress: &mgmt.EgressRecord{Name: router.Name().Clone()}, + Egress: &mgmt.EgressRecord{Name: router.Clone()}, Multicast: true, }) - // Protocol naming convention dv.execMgmtRetry("pet", "add-egress", &mgmt.ControlArgs{ - Name: grpPfx.Clone().Append(router.Name().Clone()...), - Egress: &mgmt.EgressRecord{Name: router.Name().Clone()}, + Name: grpPfx.Clone().Append(router.Clone()...), + Egress: &mgmt.EgressRecord{Name: router.Clone()}, }) } } diff --git a/dv/dv/table_algo.go b/dv/dv/table_algo.go index ced06a5f..8f7a3059 100644 --- a/dv/dv/table_algo.go +++ b/dv/dv/table_algo.go @@ -14,7 +14,6 @@ import ( func (dv *Router) postUpdateRib() { dv.updateFib() dv.advert.generate() - // Update PSD sync prefix with all routers as egress for BIER delivery. dv.updatePsdPrefix() } From e5040f882836f5bc528b298d21d8a4eb12d9221e Mon Sep 17 00:00:00 2001 From: r2dev2 Date: Wed, 13 May 2026 14:14:51 -0700 Subject: [PATCH 6/9] fw: petFound is optional and fix bugs in forgetting to look it up --- fw/fw/thread.go | 53 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 36 insertions(+), 17 deletions(-) diff --git a/fw/fw/thread.go b/fw/fw/thread.go index 719d81f7..ea77d2f9 100644 --- a/fw/fw/thread.go +++ b/fw/fw/thread.go @@ -20,6 +20,7 @@ import ( "github.com/named-data/ndnd/fw/dispatch" "github.com/named-data/ndnd/fw/table" enc "github.com/named-data/ndnd/std/encoding" + "github.com/named-data/ndnd/std/types/optional" "github.com/named-data/ndnd/std/utils" ) @@ -58,7 +59,7 @@ type pipelineContext struct { Pipeline forwardPipeline PetLocalHops []*table.PetNextHop PetEntry table.PetEntry - PetFound bool + PetFound optional.Optional[bool] LookupName enc.Name LocalFacesOnly bool } @@ -79,7 +80,7 @@ type petLocalHopsContext struct { LookupName enc.Name PitEntry table.PitEntry PetEntry table.PetEntry - PetFound bool + PetFound optional.Optional[bool] } // forwardInContext holds arguments for tryContentStoreHit @@ -417,10 +418,29 @@ func (t *Thread) validateNonce(interest *defn.FwInterest, packet *defn.Pkt) bool return true } -func (t *Thread) determinePipeline(packet *defn.Pkt, lookupName enc.Name) (forwardPipeline, table.PetEntry, bool) { +func petLookupState(found optional.Optional[bool]) string { + if !found.IsSet() { + return "none" + } + if found.Unwrap() { + return "true" + } + return "false" +} + +func (t *Thread) ensurePetLookup(petEntry *table.PetEntry, petFound *optional.Optional[bool], lookupName enc.Name) { + if petFound.IsSet() { + return + } + entry, found := table.Pet.FindLongestPrefixEnc(lookupName) + *petEntry = entry + *petFound = optional.Some(found) +} + +func (t *Thread) determinePipeline(packet *defn.Pkt, lookupName enc.Name) (forwardPipeline, table.PetEntry, optional.Optional[bool]) { var pipeline forwardPipeline var petEntry table.PetEntry - var petFound bool + petFound := optional.None[bool]() routerName, routerNameSet := CfgRouterName() @@ -438,8 +458,9 @@ func (t *Thread) determinePipeline(packet *defn.Pkt, lookupName enc.Name) (forwa pipeline = fwUnicastTransit } } else { - petEntry, petFound = table.Pet.FindLongestPrefixEnc(lookupName) - if petFound && petEntry.Multicast { + petEntry, found := table.Pet.FindLongestPrefixEnc(lookupName) + petFound = optional.Some(found) + if found && petEntry.Multicast { pipeline = fwMulticastIngress } else { pipeline = fwUnicastIngress @@ -451,7 +472,7 @@ func (t *Thread) determinePipeline(packet *defn.Pkt, lookupName enc.Name) (forwa "name", packet.Name, "lookup", lookupName, "pipeline", pipeline, - "petFound", petFound, + "petFound", petLookupState(petFound), "isLocalHop", isLocalHop, "egressRouter", len(packet.EgressRouter) > 0, "bier", len(packet.Bier) > 0, @@ -516,11 +537,8 @@ func (t *Thread) collectPetLocalHops(ctx petLocalHopsContext) []*table.PetNextHo return nil } - if !ctx.PetFound { - ctx.PetEntry, ctx.PetFound = table.Pet.FindLongestPrefixEnc(ctx.LookupName) - } - - if !ctx.PetFound { + t.ensurePetLookup(&ctx.PetEntry, &ctx.PetFound, ctx.LookupName) + if !ctx.PetFound.GetOr(false) { return nil } @@ -543,7 +561,7 @@ func (t *Thread) handleUnicastPipeline(ctx pipelineContext) { core.Log.Trace(t, "Unicast pipeline", "name", ctx.Pkt.Name, "lookup", ctx.LookupName, - "petFound", ctx.PetFound, + "petFound", petLookupState(ctx.PetFound), "localHop", isLocalHop, "localFacesOnly", ctx.LocalFacesOnly, ) @@ -586,7 +604,7 @@ func (t *Thread) handleUnicastPipeline(ctx pipelineContext) { } } -func (t *Thread) collectNetworkNextHops(packet *defn.Pkt, petEntry table.PetEntry, petFound bool) []StrategyCandidateHop { +func (t *Thread) collectNetworkNextHops(packet *defn.Pkt, petEntry table.PetEntry, petFound optional.Optional[bool]) []StrategyCandidateHop { var nextNet []StrategyCandidateHop if len(packet.EgressRouter) > 0 { @@ -596,7 +614,7 @@ func (t *Thread) collectNetworkNextHops(packet *defn.Pkt, petEntry table.PetEntr EgressRouter: packet.EgressRouter, }) } - } else if petFound { + } else if petFound.GetOr(false) { for _, er := range petEntry.EgressRouters { for _, nextHop := range table.FibStrategyTable.FindNextHopsEnc(er) { nextNet = append(nextNet, StrategyCandidateHop{ @@ -639,7 +657,7 @@ func (t *Thread) handleMulticastPipeline(ctx pipelineContext) { core.Log.Trace(t, "Multicast pipeline", "name", ctx.Pkt.Name, "lookup", ctx.LookupName, - "petFound", ctx.PetFound, + "petFound", petLookupState(ctx.PetFound), "localHop", isLocalHop, "localFacesOnly", ctx.LocalFacesOnly, "bier", len(ctx.Pkt.Bier), @@ -664,7 +682,8 @@ func (t *Thread) handleMulticastPipeline(ctx pipelineContext) { } // BIER forwarding for multicast - if !ctx.PetFound || len(ctx.PetEntry.EgressRouters) == 0 { + t.ensurePetLookup(&ctx.PetEntry, &ctx.PetFound, ctx.LookupName) + if !ctx.PetFound.GetOr(false) || len(ctx.PetEntry.EgressRouters) == 0 { return } From f22900b7527ad0cad8519981f881475f7d705f29 Mon Sep 17 00:00:00 2001 From: r2dev2 Date: Wed, 13 May 2026 14:20:56 -0700 Subject: [PATCH 7/9] fw: aggregate neighbors on same face in bier GetNeighborEntries --- fw/bier/bier.go | 21 ++++++++++++++++++++- fw/bier/bier_integration_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/fw/bier/bier.go b/fw/bier/bier.go index 05d3316e..df04da59 100644 --- a/fw/bier/bier.go +++ b/fw/bier/bier.go @@ -122,6 +122,20 @@ func BierAnd(a, b []byte) []byte { return result } +// BierOr returns bitwise OR of two bitstrings. Result length = max(len(a), len(b)). +func BierOr(a, b []byte) []byte { + maxLen := len(a) + if len(b) > maxLen { + maxLen = len(b) + } + result := make([]byte, maxLen) + copy(result, a) + for i := 0; i < len(b); i++ { + result[i] |= b[i] + } + return result +} + // BierAndNot returns a &^ b (a AND NOT b). Clears bits in a that are set in b. func BierAndNot(a, b []byte) []byte { result := make([]byte, len(a)) @@ -368,7 +382,9 @@ func (b *BiftState) GetNeighborEntries() []BiftNeighborEntry { if entry.NextHop == 0 || entry.Fbm == nil { continue } - if _, ok := faceMap[entry.NextHop]; !ok { + if fbm, ok := faceMap[entry.NextHop]; ok { + faceMap[entry.NextHop] = BierOr(fbm, entry.Fbm) + } else { faceMap[entry.NextHop] = BierClone(entry.Fbm) } } @@ -377,5 +393,8 @@ func (b *BiftState) GetNeighborEntries() []BiftNeighborEntry { for faceID, fbm := range faceMap { neighbors = append(neighbors, BiftNeighborEntry{FaceID: faceID, Fbm: fbm}) } + sort.Slice(neighbors, func(i, j int) bool { + return neighbors[i].FaceID < neighbors[j].FaceID + }) return neighbors } diff --git a/fw/bier/bier_integration_test.go b/fw/bier/bier_integration_test.go index 3b9674ee..8b5879e7 100644 --- a/fw/bier/bier_integration_test.go +++ b/fw/bier/bier_integration_test.go @@ -230,6 +230,32 @@ func TestBiftEdgeCases(t *testing.T) { } }) + t.Run("GetNeighborEntries aggregates all bits for same face", func(t *testing.T) { + b := &bier.BiftState{} + r0 := enc.Name{enc.NewGenericComponent("r0")} + r1 := enc.Name{enc.NewGenericComponent("r1")} + r9 := enc.Name{enc.NewGenericComponent("r9")} + + b.RegisterRouter(r0, 0) + b.RegisterRouter(r1, 1) + b.RegisterRouter(r9, 9) + + b.UpdateNextHop(0, 77) + b.UpdateNextHop(1, 77) + b.UpdateNextHop(9, 77) + b.RebuildFbm() + + neighbors := b.GetNeighborEntries() + if len(neighbors) != 1 { + t.Fatalf("expected 1 neighbor entry, got %d", len(neighbors)) + } + + fbm := neighbors[0].Fbm + if !bier.BierGetBit(fbm, 0) || !bier.BierGetBit(fbm, 1) || !bier.BierGetBit(fbm, 9) { + t.Fatalf("expected aggregated F-BM to contain bits 0, 1, and 9; got %08b %08b", fbm[0], fbm[1]) + } + }) + t.Run("RebuildFbm on empty BIFT does not panic", func(t *testing.T) { b := &bier.BiftState{} b.RebuildFbm() // must not panic From 26e6d50ba0306da3e1bf02148067412e75037b35 Mon Sep 17 00:00:00 2001 From: r2dev2 Date: Wed, 13 May 2026 14:22:11 -0700 Subject: [PATCH 8/9] fw: fix bier helpers to return the max length not the min length of the two operands --- fw/bier/bier.go | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/fw/bier/bier.go b/fw/bier/bier.go index df04da59..d2667d22 100644 --- a/fw/bier/bier.go +++ b/fw/bier/bier.go @@ -109,13 +109,17 @@ func BierClearBit(bs []byte, pos int) { bs[byteIdx] &^= (1 << bitIdx) } -// BierAnd returns bitwise AND of two bitstrings. Result length = min(len(a), len(b)). +// BierAnd returns bitwise AND of two bitstrings. Result length = max(len(a), len(b)). func BierAnd(a, b []byte) []byte { + maxLen := len(a) + if len(b) > maxLen { + maxLen = len(b) + } + result := make([]byte, maxLen) minLen := len(a) if len(b) < minLen { minLen = len(b) } - result := make([]byte, minLen) for i := 0; i < minLen; i++ { result[i] = a[i] & b[i] } @@ -136,9 +140,13 @@ func BierOr(a, b []byte) []byte { return result } -// BierAndNot returns a &^ b (a AND NOT b). Clears bits in a that are set in b. +// BierAndNot returns a &^ b (a AND NOT b). Result length = max(len(a), len(b)). func BierAndNot(a, b []byte) []byte { - result := make([]byte, len(a)) + maxLen := len(a) + if len(b) > maxLen { + maxLen = len(b) + } + result := make([]byte, maxLen) copy(result, a) minLen := len(a) if len(b) < minLen { From 230d825f05a7d3eafe56ac027d0a15146f69ac89 Mon Sep 17 00:00:00 2001 From: r2dev2 Date: Wed, 13 May 2026 14:39:25 -0700 Subject: [PATCH 9/9] fw: fix bier integration test for operators --- fw/bier/bier_integration_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fw/bier/bier_integration_test.go b/fw/bier/bier_integration_test.go index 8b5879e7..487374fd 100644 --- a/fw/bier/bier_integration_test.go +++ b/fw/bier/bier_integration_test.go @@ -86,10 +86,10 @@ func TestBierBitManipulationEdgeCases(t *testing.T) { a := []byte{0xFF, 0xFF, 0xFF} // 3 bytes b := []byte{0x0F, 0xF0} // 2 bytes — shorter res := bier.BierAnd(a, b) - if len(res) != 2 { - t.Errorf("result length should be min(3,2)=2, got %d", len(res)) + if len(res) != 3 { + t.Errorf("result length should be max(3,2)=3, got %d", len(res)) } - if res[0] != 0x0F || res[1] != 0xF0 { + if res[0] != 0x0F || res[1] != 0xF0 || res[2] != 0x00 { t.Errorf("unexpected result %v", res) } })