diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1b72cf43..bad4cfea 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -89,3 +89,10 @@ jobs: - name: Run e2e tests run: make e2e + + - name: Upload minindn logs on failure + if: failure() + uses: actions/upload-artifact@v4 + with: + name: minindn-logs + path: /tmp/minindn diff --git a/Makefile b/Makefile index c0fa9e17..5ec4c696 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,8 @@ ndnd: clean CGO_ENABLED=0 go build -o ndnd \ -ldflags "-X '${STD_PACKAGE}/utils.NDNdVersion=${VERSION}'" \ cmd/ndnd/main.go + mkdir -p .bin/ + cp ndnd .bin/ examples: .bin/alo-latest diff --git a/dv/config/config.go b/dv/config/config.go index da983d8a..21cba153 100644 --- a/dv/config/config.go +++ b/dv/config/config.go @@ -32,11 +32,6 @@ var BroadcastStrategy = enc.LOCALHOST. Append(enc.NewGenericComponent("strategy")). Append(enc.NewGenericComponent("broadcast")) -var ReplicastStrategy = enc.LOCALHOST. - Append(enc.NewGenericComponent("nfd")). - Append(enc.NewGenericComponent("strategy")). - Append(enc.NewGenericComponent("replicast")) - //go:embed schema.tlv var SchemaBytes []byte diff --git a/dv/dv/mgmt.go b/dv/dv/mgmt.go index 7eb2b9a0..b16b1b84 100644 --- a/dv/dv/mgmt.go +++ b/dv/dv/mgmt.go @@ -292,7 +292,7 @@ func (dv *Router) mgmtOnPrefix(args ndn.InterestHandlerArgs) { responseParams.ExpirationPeriod = optional.Some(expires) } - multicast := params.Val.Flags.GetOr(0)&1 != 0 + multicast := params.Val.Multicast dv.mutex.Lock() dv.pfx.Announce(name, faceID, cost, multicast, validity) dv.mutex.Unlock() diff --git a/dv/dv/prefix.go b/dv/dv/prefix.go index f4bcede0..d0b38c94 100644 --- a/dv/dv/prefix.go +++ b/dv/dv/prefix.go @@ -425,9 +425,8 @@ func (pfx *PrefixModule) applyPetOps(ops []petEgressOp) { } if op.add { cmd = "add-egress" - // Signal the Sync group (multicast) flag to the forwarder's PET via Flags bit 0. if op.multicast { - args.Flags = optional.Some(uint64(1)) + args.Multicast = true } } diff --git a/dv/dv/router.go b/dv/dv/router.go index 10663a8e..319b5bf2 100644 --- a/dv/dv/router.go +++ b/dv/dv/router.go @@ -8,6 +8,7 @@ import ( "github.com/named-data/ndnd/dv/config" "github.com/named-data/ndnd/dv/nfdc" "github.com/named-data/ndnd/dv/table" + "github.com/named-data/ndnd/fw/defn" enc "github.com/named-data/ndnd/std/encoding" "github.com/named-data/ndnd/std/log" "github.com/named-data/ndnd/std/ndn" @@ -285,64 +286,51 @@ func (dv *Router) register() (err error) { } for _, prefix := range pfxs { - dv.nfdc.Exec(nfdc.NfdMgmtCmd{ - Module: "pet", - Cmd: "add-nexthop", - Args: &mgmt.ControlArgs{ - Name: prefix, - }, - Retries: -1, + dv.execMgmtRetry("pet", "add-nexthop", &mgmt.ControlArgs{ + Name: prefix, }) } // Allow outgoing local-prefix-sync Interests to use two-phase forwarding. // Incoming Interests still terminate locally on the same prefix. - dv.nfdc.Exec(nfdc.NfdMgmtCmd{ - Module: "pet", - Cmd: "add-egress", - Args: &mgmt.ControlArgs{ - Name: dv.pfx.SyncPrefix(), - Egress: &mgmt.EgressRecord{Name: neighborsPrefix.Clone()}, - }, - Retries: -1, + 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.nfdc.Exec(nfdc.NfdMgmtCmd{ - Module: "pet", - Cmd: "add-egress", - Args: &mgmt.ControlArgs{ - Name: dv.config.AdvertisementSyncPrefix(), - Egress: &mgmt.EgressRecord{Name: neighborsPrefix.Clone()}, - }, - Retries: -1, - }) - // Set strategy to broadcast for advertisement sync Interests, so - // /localhop/.../DV/ADS traffic fan-outs to all neighbor nexthops. - dv.nfdc.Exec(nfdc.NfdMgmtCmd{ - Module: "strategy-choice", - Cmd: "set", - Args: &mgmt.ControlArgs{ - Name: dv.config.AdvertisementSyncPrefix(), - Strategy: &mgmt.Strategy{ - Name: config.BroadcastStrategy, - }, - }, - Retries: -1, - }) - dv.nfdc.Exec(nfdc.NfdMgmtCmd{ - Module: "strategy-choice", - Cmd: "set", - Args: &mgmt.ControlArgs{ - Name: dv.pfx.SyncPrefix(), - Strategy: &mgmt.Strategy{ - Name: config.BroadcastStrategy, - }, - }, - Retries: -1, + dv.execMgmtRetry("pet", "add-egress", &mgmt.ControlArgs{ + Name: dv.config.AdvertisementSyncPrefix(), + Egress: &mgmt.EgressRecord{Name: neighborsPrefix.Clone()}, + Multicast: true, }) + // Force multicast strategy for sync prefixes to broadcast. + broadcastPrefixes := []enc.Name{ + dv.pfx.SyncPrefix(), + dv.config.AdvertisementSyncPrefix(), + } + for _, prefix := range broadcastPrefixes { + dv.execMgmtRetry("multicast-strategy-choice", "set", &mgmt.ControlArgs{ + Name: prefix, + Strategy: &mgmt.Strategy{Name: defn.BROADCAST_STRATEGY}, + }) + } + return nil } +func (dv *Router) execMgmtRetry(module, cmd string, args *mgmt.ControlArgs) { + var err error + for i := 0; ; i++ { + if _, err = dv.engine.ExecMgmtCmd(module, cmd, args); err == nil { + break + } + log.Error(dv, "Forwarder command failed", "err", err, "attempt", i, + "module", module, "cmd", cmd, "args", args) + time.Sleep(100 * time.Millisecond) + } +} + // createFaces creates faces to all neighbors. func (dv *Router) createFaces() { neighborsPrefix := enc.LOCALHOP.Append(enc.NewGenericComponent("neighbors")) @@ -370,15 +358,10 @@ func (dv *Router) createFaces() { dv.mutex.Unlock() // Add neighbor to localhop neighbors - dv.nfdc.Exec(nfdc.NfdMgmtCmd{ - Module: "fib", - Cmd: "add-nexthop", - Args: &mgmt.ControlArgs{ - Name: neighborsPrefix.Clone(), - Cost: optional.Some(uint64(1)), - FaceId: optional.Some(faceId), - }, - Retries: 3, + dv.execMgmtRetry("fib", "add-nexthop", &mgmt.ControlArgs{ + Name: neighborsPrefix.Clone(), + Cost: optional.Some(uint64(1)), + FaceId: optional.Some(faceId), }) } } diff --git a/dv/dv/table_algo.go b/dv/dv/table_algo.go index 029efb81..c84ea5dc 100644 --- a/dv/dv/table_algo.go +++ b/dv/dv/table_algo.go @@ -3,7 +3,7 @@ package dv import ( "github.com/named-data/ndnd/dv/config" "github.com/named-data/ndnd/dv/table" - fw "github.com/named-data/ndnd/fw/fw" + "github.com/named-data/ndnd/fw/bier" enc "github.com/named-data/ndnd/std/encoding" "github.com/named-data/ndnd/std/log" ) @@ -138,7 +138,7 @@ func (dv *Router) updateFib() { // Rebuild the BIFT whenever the FIB changes so BIER forwarding paths // are always consistent with the routing table. - if fw.IsBierEnabled() { - fw.Bift.BuildFromFib() + if bier.IsBierEnabled() { + bier.Bift.BuildFromFib() } } diff --git a/e2e/dv_util.py b/e2e/dv_util.py index e70a15aa..4c1c4418 100644 --- a/e2e/dv_util.py +++ b/e2e/dv_util.py @@ -53,7 +53,7 @@ def is_converged(nodes: list[Node], network=DEFAULT_NETWORK, use_nfdc=False) -> # We don't support that. routes = node.cmd('nfdc route list') else: - routes = node.cmd('ndnd fw route-list') + routes = node.cmd('ndnd fw route-list 2>&1') for other in nodes: if other == node: continue diff --git a/e2e/test_001.py b/e2e/test_001.py index 569d5f74..944c2d45 100644 --- a/e2e/test_001.py +++ b/e2e/test_001.py @@ -63,16 +63,6 @@ def scenario(ndn: Minindn, network='/minindn'): strategy = node.cmd('ndnd fw strategy-list') if "multicast" in strategy: raise Exception(f'Multicast is to be retired, unexpectedly present in strategy on {node.name}') - expected_strategies = [ - # Localhop SVS sync interests should use broadcast (#174) - "prefix=/minindn/32=DV/32=PES/32=svs strategy=/localhost/nfd/strategy/broadcast/v=1", - - # Localhop advertisement sync interests should use broadcast strategy (#174) - "prefix=/localhop/minindn/32=DV/32=ADS strategy=/localhost/nfd/strategy/broadcast/v=1", - ] - for expected_strat in expected_strategies: - if expected_strat not in strategy: - raise Exception(f'Strategy {expected_strat!r} not in strategy on {node.name}') for node, put_node in cat_requests: cmd = f'ndnd cat "{network}/{put_node.name}/test" > recv.test.bin 2> cat.log' diff --git a/e2e/test_007.py b/e2e/test_007.py index 76150793..140e0c37 100644 --- a/e2e/test_007.py +++ b/e2e/test_007.py @@ -81,6 +81,7 @@ def scenario(ndn: Minindn, network='/minindn'): producer_prefixes = {sync_prefix, f'{network}/svs/{producer.name}'} dv_util.wait_prefix_pet_ready({node: producer_prefixes for node in consumers}, deadline=60) + beg = time.time() # Step 5: Wait for publish → BIER sync interest propagation → unicast data fetch. # 20s delay + 30s SVS sync cycle + 20s fetch buffer = 70s total. info(f'Waiting for message propagation from {producer.name} (70s)...\n') @@ -90,6 +91,7 @@ def scenario(ndn: Minindn, network='/minindn'): for node in chat_nodes: node.cmd("pkill -f 'ndnd svs-chat' 2>/dev/null; true") time.sleep(1) # let log buffers flush + print(time.time() - beg, "seconds elapsed") failures = [] for consumer in consumers: diff --git a/fw/bier/bier.go b/fw/bier/bier.go new file mode 100644 index 00000000..460ca533 --- /dev/null +++ b/fw/bier/bier.go @@ -0,0 +1,296 @@ +/* BIER - Bit Index Explicit Replication for ndnd + * + * Stateless multicast forwarding using bit-indexed replication. + * Each router is assigned a unique BFR-ID (bit index) by the operator. + * The BIFT maps each bit position to a next-hop face and forwarding bit mask. + */ + +package bier + +import ( + "sort" + "sync" + + "github.com/named-data/ndnd/fw/core" + "github.com/named-data/ndnd/fw/table" + enc "github.com/named-data/ndnd/std/encoding" +) + +// BiftEntry represents a single entry in the Bit Index Forwarding Table. +// Each entry maps a BFR-ID (bit position) to a next-hop face and +// the forwarding bit mask (F-BM) for that neighbor. +type BiftEntry struct { + BfrId int // Bit position this entry is for + RouterName enc.Name // Name of the destination router + NextHop uint64 // Face ID to reach this router's next hop + Fbm []byte // Forwarding Bit Mask for this neighbor +} + +// Bift is the global Bit Index Forwarding Table. +var Bift = &BiftState{} + +// BiftState holds the current BIFT and mapping state. +type BiftState struct { + mu sync.RWMutex + entries map[int]*BiftEntry // BFR-ID -> entry + + // routerBit maps router name hash -> BFR-ID for quick lookup + routerBit map[uint64]int +} + +func (b *BiftState) String() string { + return "bift" +} + +// CfgBierIndex returns the configured BIER index for this router. +// Returns -1 if BIER is not configured (disabled). +func CfgBierIndex() int { + return core.C.Fw.BierIndex +} + +// IsBierEnabled returns true if BIER forwarding is enabled on this router. +func IsBierEnabled() bool { + return CfgBierIndex() >= 0 +} + +// --- Bit-string manipulation helpers --- + +// BierGetBit returns true if bit position pos is set in the bitstring. +// Bit 0 is the LSB of the first byte. +func BierGetBit(bs []byte, pos int) bool { + byteIdx := pos / 8 + if byteIdx >= len(bs) { + return false + } + bitIdx := uint(pos % 8) + return (bs[byteIdx] & (1 << bitIdx)) != 0 +} + +// BierSetBit sets bit position pos in the bitstring. +// Grows the slice if needed. +func BierSetBit(bs []byte, pos int) []byte { + byteIdx := pos / 8 + for byteIdx >= len(bs) { + bs = append(bs, 0) + } + bitIdx := uint(pos % 8) + bs[byteIdx] |= (1 << bitIdx) + return bs +} + +// BierClearBit clears bit position pos in the bitstring. +func BierClearBit(bs []byte, pos int) { + byteIdx := pos / 8 + if byteIdx >= len(bs) { + return + } + bitIdx := uint(pos % 8) + bs[byteIdx] &^= (1 << bitIdx) +} + +// BierAnd returns bitwise AND of two bitstrings. Result length = min(len(a), len(b)). +func BierAnd(a, b []byte) []byte { + 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] + } + 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)) + copy(result, a) + minLen := len(a) + if len(b) < minLen { + minLen = len(b) + } + for i := 0; i < minLen; i++ { + result[i] = a[i] &^ b[i] + } + return result +} + +// BierIsZero returns true if all bits in the bitstring are zero. +func BierIsZero(bs []byte) bool { + for _, b := range bs { + if b != 0 { + return false + } + } + return true +} + +// BierClone returns a copy of the bitstring. +func BierClone(bs []byte) []byte { + if bs == nil { + return nil + } + c := make([]byte, len(bs)) + copy(c, bs) + return c +} + +// --- BIFT Construction --- + +// RegisterBierRouter registers a router name with its operator-assigned BFR-ID. +// This is called when learning about a router's BIER index (e.g., from DV). +func (b *BiftState) RegisterRouter(routerName enc.Name, bfrId int) { + b.mu.Lock() + defer b.mu.Unlock() + + if b.entries == nil { + b.entries = make(map[int]*BiftEntry) + b.routerBit = make(map[uint64]int) + } + + b.entries[bfrId] = &BiftEntry{ + BfrId: bfrId, + RouterName: routerName.Clone(), + } + b.routerBit[routerName.Hash()] = bfrId + + core.Log.Info(b, "Registered BIER router", "name", routerName, "bfr-id", bfrId) +} + +// UpdateNextHop updates the next-hop face for reaching a given BFR-ID. +// Called when FIB changes. +func (b *BiftState) UpdateNextHop(bfrId int, nextHop uint64) { + b.mu.Lock() + defer b.mu.Unlock() + + if entry, ok := b.entries[bfrId]; ok { + entry.NextHop = nextHop + } +} + +// RebuildFbm rebuilds forwarding bit masks for all BIFT entries. +// Groups BFR-IDs by their next-hop face and computes the F-BM for each group. +func (b *BiftState) RebuildFbm() { + b.mu.Lock() + defer b.mu.Unlock() + + // Find maximum BFR-ID to size bitstrings + maxBit := 0 + for bfrId := range b.entries { + if bfrId > maxBit { + maxBit = bfrId + } + } + bsLen := (maxBit / 8) + 1 + + // Group by next-hop face + faceGroups := make(map[uint64][]int) // faceID -> list of BFR-IDs + for bfrId, entry := range b.entries { + if entry.NextHop > 0 { + faceGroups[entry.NextHop] = append(faceGroups[entry.NextHop], bfrId) + } + } + + // Build F-BM for each face group + faceFbm := make(map[uint64][]byte) + for faceID, bfrIds := range faceGroups { + fbm := make([]byte, bsLen) + for _, id := range bfrIds { + fbm = BierSetBit(fbm, id) + } + faceFbm[faceID] = fbm + } + + // Assign F-BM to each entry based on its next-hop face + for _, entry := range b.entries { + if fbm, ok := faceFbm[entry.NextHop]; ok { + entry.Fbm = fbm + } + } + + core.Log.Info(b, "Rebuilt BIFT forwarding bit masks", + "entries", len(b.entries), "faces", len(faceGroups)) +} + +// BuildFromFib rebuilds the BIFT from the current FIB state. +// For each known router (registered via DV), looks up the FIB to find next hops. +func (b *BiftState) BuildFromFib() { + b.mu.RLock() + entries := make(map[int]*BiftEntry, len(b.entries)) + for k, v := range b.entries { + entries[k] = v + } + b.mu.RUnlock() + + // Update next hops from FIB for each registered router + for _, entry := range entries { + nexthops := table.FibStrategyTable.FindNextHopsEnc(entry.RouterName) + if len(nexthops) > 0 { + // Sort by cost and pick best + sort.Slice(nexthops, func(i, j int) bool { + return nexthops[i].Cost < nexthops[j].Cost + }) + b.UpdateNextHop(entry.BfrId, nexthops[0].Nexthop) + } + } + + b.RebuildFbm() +} + +// GetRouterBfrId returns the BFR-ID for a given router name. +func (b *BiftState) GetRouterBfrId(routerName enc.Name) (int, bool) { + b.mu.RLock() + defer b.mu.RUnlock() + + if id, ok := b.routerBit[routerName.Hash()]; ok { + return id, true + } + return -1, false +} + +// --- BFIR Functions --- + +// BuildBierBitString builds a BIER bit-string from a list of egress router names. +// Returns nil if no egress routers have known BFR-IDs. +func (b *BiftState) BuildBierBitString(egressRouters []enc.Name) []byte { + b.mu.RLock() + defer b.mu.RUnlock() + + var bs []byte + for _, er := range egressRouters { + if id, ok := b.routerBit[er.Hash()]; ok { + bs = BierSetBit(bs, id) + } + } + return bs +} + +// --- Replication (BFR/BFER) --- + +// BiftNeighborEntry represents a unique next-hop face and its aggregated F-BM. +type BiftNeighborEntry struct { + FaceID uint64 + Fbm []byte +} + +// GetNeighborEntries returns the unique neighbor entries (grouped by face) from the BIFT. +func (b *BiftState) GetNeighborEntries() []BiftNeighborEntry { + b.mu.RLock() + defer b.mu.RUnlock() + + faceMap := make(map[uint64][]byte) + for _, entry := range b.entries { + if entry.NextHop == 0 || entry.Fbm == nil { + continue + } + if _, ok := faceMap[entry.NextHop]; !ok { + faceMap[entry.NextHop] = BierClone(entry.Fbm) + } + } + + neighbors := make([]BiftNeighborEntry, 0, len(faceMap)) + for faceID, fbm := range faceMap { + neighbors = append(neighbors, BiftNeighborEntry{FaceID: faceID, Fbm: fbm}) + } + return neighbors +} diff --git a/fw/bier_tests/bier_conceptual_test.go b/fw/bier/bier_conceptual_test.go similarity index 96% rename from fw/bier_tests/bier_conceptual_test.go rename to fw/bier/bier_conceptual_test.go index db3e1f60..d3eaa3e1 100644 --- a/fw/bier_tests/bier_conceptual_test.go +++ b/fw/bier/bier_conceptual_test.go @@ -1,4 +1,4 @@ -package bier_tests +package bier_test // Tests for BIER forwarding in NDN: Sync Interest multicast delivery, // prefix-to-routing mapping, BFIR encoding, and BFR/BFER per-hop handling. @@ -7,7 +7,7 @@ import ( "fmt" "testing" - fw "github.com/named-data/ndnd/fw/fw" + bier "github.com/named-data/ndnd/fw/bier" enc "github.com/named-data/ndnd/std/encoding" ) @@ -76,7 +76,7 @@ func TestSyncInterestStatelessForwarding(t *testing.T) { t.Run("Same Sync Interest sent twice produces identical results", func(t *testing.T) { bs := g.buildBitstring(1, 3, 4) res1 := g.simulate(0, bs) - res2 := g.simulate(0, fw.BierClone(bs)) + res2 := g.simulate(0, bier.BierClone(bs)) // Both runs must deliver to exactly the same set for id := range res1.delivered { @@ -98,7 +98,7 @@ func TestPrefixToRoutingMapping(t *testing.T) { // list that BuildBierBitString transforms into a BIER bit-string. t.Run("Group members map to correct bit positions", func(t *testing.T) { - bift := &fw.BiftState{} + bift := &bier.BiftState{} // Mapping protocol populates router-to-BFR-ID assignments // (In production this comes from DV advertisements) @@ -119,22 +119,22 @@ func TestPrefixToRoutingMapping(t *testing.T) { bs := bift.BuildBierBitString(egressRouters) // Verify: bits 0, 2, 3 set (routerA, routerC, routerD) - if !fw.BierGetBit(bs, 0) { + if !bier.BierGetBit(bs, 0) { t.Error("routerA (bit 0) should be set") } - if fw.BierGetBit(bs, 1) { + if bier.BierGetBit(bs, 1) { t.Error("routerB (bit 1) should NOT be set — not in egress list") } - if !fw.BierGetBit(bs, 2) { + if !bier.BierGetBit(bs, 2) { t.Error("routerC (bit 2) should be set") } - if !fw.BierGetBit(bs, 3) { + if !bier.BierGetBit(bs, 3) { t.Error("routerD (bit 3) should be set") } }) t.Run("Unknown routers in egress list are safely ignored", func(t *testing.T) { - bift := &fw.BiftState{} + bift := &bier.BiftState{} known := enc.Name{enc.NewGenericComponent("known-router")} unknown := enc.Name{enc.NewGenericComponent("unknown-router")} @@ -146,24 +146,24 @@ func TestPrefixToRoutingMapping(t *testing.T) { bs := bift.BuildBierBitString(egressRouters) // Only the known router should appear in the bit-string - if !fw.BierGetBit(bs, 5) { + if !bier.BierGetBit(bs, 5) { t.Error("known router (bit 5) should be set") } // Bit-string should be minimal — no stray bits for i := 0; i < 5; i++ { - if fw.BierGetBit(bs, i) { + if bier.BierGetBit(bs, i) { t.Errorf("bit %d should not be set", i) } } }) t.Run("Single egress router produces single-bit string", func(t *testing.T) { - bift := &fw.BiftState{} + bift := &bier.BiftState{} r := enc.Name{enc.NewGenericComponent("solo")} bift.RegisterRouter(r, 7) bs := bift.BuildBierBitString([]enc.Name{r}) - if !fw.BierGetBit(bs, 7) { + if !bier.BierGetBit(bs, 7) { t.Error("single-router bit-string should have bit 7 set") } // All other bits must be clear @@ -183,7 +183,7 @@ func TestPrefixToRoutingMapping(t *testing.T) { t.Run("Producer multihoming — producer reachable via multiple egress routers", func(t *testing.T) { // A producer announces the same prefix to two different routers. // The mapping protocol creates egress entries for both. - bift := &fw.BiftState{} + bift := &bier.BiftState{} router1 := enc.Name{enc.NewGenericComponent("edge-1")} router2 := enc.Name{enc.NewGenericComponent("edge-2")} @@ -197,13 +197,13 @@ func TestPrefixToRoutingMapping(t *testing.T) { egressRouters := []enc.Name{router1, router2} bs := bift.BuildBierBitString(egressRouters) - if !fw.BierGetBit(bs, 0) { + if !bier.BierGetBit(bs, 0) { t.Error("multihomed router1 (bit 0) should be set") } - if !fw.BierGetBit(bs, 1) { + if !bier.BierGetBit(bs, 1) { t.Error("multihomed router2 (bit 1) should be set") } - if fw.BierGetBit(bs, 2) { + if bier.BierGetBit(bs, 2) { t.Error("router3 (bit 2) should NOT be set — producer not attached there") } }) @@ -229,7 +229,7 @@ func TestBfirEncodingFromEgressRouters(t *testing.T) { }) t.Run("BFIR encoding preserves per-router bit accuracy", func(t *testing.T) { - bift := &fw.BiftState{} + bift := &bier.BiftState{} // Register routers at specific BFR-IDs (operator-assigned) names := make([]enc.Name, 10) @@ -246,11 +246,11 @@ func TestBfirEncodingFromEgressRouters(t *testing.T) { expected := map[int]bool{0: true, 3: true, 7: true, 9: true} for i := 0; i < 10; i++ { if expected[i] { - if !fw.BierGetBit(bs, i) { + if !bier.BierGetBit(bs, i) { t.Errorf("BFIR encoding: bit %d should be set (egress router present)", i) } } else { - if fw.BierGetBit(bs, i) { + if bier.BierGetBit(bs, i) { t.Errorf("BFIR encoding: bit %d should NOT be set (no egress router)", i) } } @@ -258,7 +258,7 @@ func TestBfirEncodingFromEgressRouters(t *testing.T) { }) t.Run("BFIR with no valid egress routers returns nil", func(t *testing.T) { - bift := &fw.BiftState{} + bift := &bier.BiftState{} // No routers registered — empty mapping bs := bift.BuildBierBitString(nil) if bs != nil { diff --git a/fw/bier_tests/bier_fib_rebuild_test.go b/fw/bier/bier_fib_rebuild_test.go similarity index 97% rename from fw/bier_tests/bier_fib_rebuild_test.go rename to fw/bier/bier_fib_rebuild_test.go index 09e4c682..f011281f 100644 --- a/fw/bier_tests/bier_fib_rebuild_test.go +++ b/fw/bier/bier_fib_rebuild_test.go @@ -1,4 +1,4 @@ -package bier_tests +package bier_test // TestBiftRebuildOnFibChange verifies that the BIFT is properly rebuilt when // the FIB changes. This tests the hook added in table_algo.go:updateFib(). @@ -12,7 +12,7 @@ package bier_tests import ( "testing" - fw "github.com/named-data/ndnd/fw/fw" + bier "github.com/named-data/ndnd/fw/bier" "github.com/named-data/ndnd/fw/table" enc "github.com/named-data/ndnd/std/encoding" ) @@ -24,7 +24,7 @@ func TestBiftRebuildOnFibChange(t *testing.T) { // Use the global FibStrategyTable (requires initialization). table.Initialize() - b := &fw.BiftState{} + b := &bier.BiftState{} rA := enc.Name{enc.NewGenericComponent("routerA")} rB := enc.Name{enc.NewGenericComponent("routerB")} @@ -96,10 +96,10 @@ func TestBiftRebuildOnFibChange(t *testing.T) { // After the "FIB convergence" above, the bit-string built for routerA // should still result in bit 1 being set (BFR-ID is stable). bs := b.BuildBierBitString([]enc.Name{rA}) - if !fw.BierGetBit(bs, 1) { + if !bier.BierGetBit(bs, 1) { t.Error("bit 1 (routerA BFR-ID) should be set after FIB-driven rebuild") } - if fw.BierGetBit(bs, 2) { + if bier.BierGetBit(bs, 2) { t.Error("bit 2 (routerB BFR-ID) should NOT be set when only routerA is egress") } }) @@ -111,7 +111,7 @@ func TestBiftRebuildOnFibChange(t *testing.T) { func TestBiftBuildFromFibMultipleRebuildsSafe(t *testing.T) { table.Initialize() - b := &fw.BiftState{} + b := &bier.BiftState{} rX := enc.Name{enc.NewGenericComponent("routerX")} b.RegisterRouter(rX, 5) b.UpdateNextHop(5, 77) @@ -128,7 +128,7 @@ func TestBiftBuildFromFibMultipleRebuildsSafe(t *testing.T) { for _, n := range neighbors { if n.FaceID == 77 { found = true - if !fw.BierGetBit(n.Fbm, 5) { + if !bier.BierGetBit(n.Fbm, 5) { t.Error("F-BM for face 77 should have bit 5 set after repeated rebuilds") } } diff --git a/fw/bier_tests/bier_integration_test.go b/fw/bier/bier_integration_test.go similarity index 79% rename from fw/bier_tests/bier_integration_test.go rename to fw/bier/bier_integration_test.go index cd4d0577..3b9674ee 100644 --- a/fw/bier_tests/bier_integration_test.go +++ b/fw/bier/bier_integration_test.go @@ -1,11 +1,11 @@ -package bier_tests +package bier_test import ( "sync" "testing" + bier "github.com/named-data/ndnd/fw/bier" "github.com/named-data/ndnd/fw/core" - fw "github.com/named-data/ndnd/fw/fw" "github.com/named-data/ndnd/fw/table" enc "github.com/named-data/ndnd/std/encoding" ) @@ -21,31 +21,31 @@ func setBierIndex(idx int) func() { func TestBierBitManipulationEdgeCases(t *testing.T) { t.Run("GetBit on empty slice returns false", func(t *testing.T) { - if fw.BierGetBit(nil, 0) { + if bier.BierGetBit(nil, 0) { t.Error("GetBit on nil should be false") } - if fw.BierGetBit([]byte{}, 7) { + if bier.BierGetBit([]byte{}, 7) { t.Error("GetBit on empty slice should be false") } }) t.Run("GetBit out-of-bounds returns false", func(t *testing.T) { bs := []byte{0xFF} // only byte 0 - if fw.BierGetBit(bs, 8) { + if bier.BierGetBit(bs, 8) { t.Error("GetBit at byte 1 of 1-byte slice should be false") } - if fw.BierGetBit(bs, 100) { + if bier.BierGetBit(bs, 100) { t.Error("GetBit far out of bounds should be false") } }) t.Run("SetBit auto-extends slice", func(t *testing.T) { var bs []byte - bs = fw.BierSetBit(bs, 23) // byte index 2 + bs = bier.BierSetBit(bs, 23) // byte index 2 if len(bs) < 3 { t.Errorf("slice should be at least 3 bytes, got %d", len(bs)) } - if !fw.BierGetBit(bs, 23) { + if !bier.BierGetBit(bs, 23) { t.Error("bit 23 should be set") } // Preceding bytes should be zero @@ -56,7 +56,7 @@ func TestBierBitManipulationEdgeCases(t *testing.T) { t.Run("SetBit boundary — bit 7 is MSB of first byte", func(t *testing.T) { var bs []byte - bs = fw.BierSetBit(bs, 7) + bs = bier.BierSetBit(bs, 7) if bs[0] != 0x80 { t.Errorf("expected 0x80, got %02x", bs[0]) } @@ -64,7 +64,7 @@ func TestBierBitManipulationEdgeCases(t *testing.T) { t.Run("SetBit boundary — bit 8 is LSB of second byte", func(t *testing.T) { var bs []byte - bs = fw.BierSetBit(bs, 8) + bs = bier.BierSetBit(bs, 8) if len(bs) < 2 || bs[1] != 0x01 { t.Errorf("expected second byte 0x01, got %v", bs) } @@ -72,20 +72,20 @@ func TestBierBitManipulationEdgeCases(t *testing.T) { t.Run("ClearBit out-of-bounds is a no-op", func(t *testing.T) { bs := []byte{0xFF} - fw.BierClearBit(bs, 100) // should not panic + bier.BierClearBit(bs, 100) // should not panic if bs[0] != 0xFF { t.Error("ClearBit out-of-bounds should not modify the slice") } }) t.Run("ClearBit on nil is a no-op", func(t *testing.T) { - fw.BierClearBit(nil, 0) // must not panic + bier.BierClearBit(nil, 0) // must not panic }) - t.Run("fw.BierAnd with different length slices", func(t *testing.T) { + t.Run("bier.BierAnd with different length slices", func(t *testing.T) { a := []byte{0xFF, 0xFF, 0xFF} // 3 bytes b := []byte{0x0F, 0xF0} // 2 bytes — shorter - res := fw.BierAnd(a, b) + res := bier.BierAnd(a, b) if len(res) != 2 { t.Errorf("result length should be min(3,2)=2, got %d", len(res)) } @@ -94,17 +94,17 @@ func TestBierBitManipulationEdgeCases(t *testing.T) { } }) - t.Run("fw.BierAnd both nil/empty returns empty", func(t *testing.T) { - res := fw.BierAnd(nil, nil) + t.Run("bier.BierAnd both nil/empty returns empty", func(t *testing.T) { + res := bier.BierAnd(nil, nil) if len(res) != 0 { t.Errorf("AND of two nils should be empty") } }) - t.Run("fw.BierAndNot shorter mask", func(t *testing.T) { + t.Run("bier.BierAndNot shorter mask", func(t *testing.T) { a := []byte{0xFF, 0xFF} // 2 bytes b := []byte{0x0F} // 1 byte — shorter - res := fw.BierAndNot(a, b) + res := bier.BierAndNot(a, b) // Only first byte gets bits cleared if res[0] != 0xF0 { t.Errorf("byte 0: expected 0xF0, got %02x", res[0]) @@ -114,26 +114,26 @@ func TestBierBitManipulationEdgeCases(t *testing.T) { } }) - t.Run("fw.BierIsZero on nil is true", func(t *testing.T) { - if !fw.BierIsZero(nil) { + t.Run("bier.BierIsZero on nil is true", func(t *testing.T) { + if !bier.BierIsZero(nil) { t.Error("nil bitstring should be zero") } }) - t.Run("fw.BierIsZero on empty slice is true", func(t *testing.T) { - if !fw.BierIsZero([]byte{}) { + t.Run("bier.BierIsZero on empty slice is true", func(t *testing.T) { + if !bier.BierIsZero([]byte{}) { t.Error("empty bitstring should be zero") } }) - t.Run("fw.BierClone of nil returns nil", func(t *testing.T) { - if fw.BierClone(nil) != nil { + t.Run("bier.BierClone of nil returns nil", func(t *testing.T) { + if bier.BierClone(nil) != nil { t.Error("clone of nil should be nil") } }) - t.Run("fw.BierClone of empty slice returns empty (not nil)", func(t *testing.T) { - c := fw.BierClone([]byte{}) + t.Run("bier.BierClone of empty slice returns empty (not nil)", func(t *testing.T) { + c := bier.BierClone([]byte{}) if c == nil { t.Error("clone of empty slice should be non-nil") } @@ -146,41 +146,41 @@ func TestBierBitManipulationEdgeCases(t *testing.T) { var bs []byte positions := []int{0, 63, 64, 127, 255} for _, pos := range positions { - bs = fw.BierSetBit(bs, pos) + bs = bier.BierSetBit(bs, pos) } for _, pos := range positions { - if !fw.BierGetBit(bs, pos) { + if !bier.BierGetBit(bs, pos) { t.Errorf("bit %d should be set", pos) } } // Adjacent bits should be clear - if fw.BierGetBit(bs, 1) { + if bier.BierGetBit(bs, 1) { t.Error("bit 1 should not be set") } - if fw.BierGetBit(bs, 62) { + if bier.BierGetBit(bs, 62) { t.Error("bit 62 should not be set") } }) } -// --- fw.IsBierEnabled / fw.CfgBierIndex --- +// --- bier.IsBierEnabled / bier.CfgBierIndex --- func TestBierEnabledConfig(t *testing.T) { t.Run("disabled when BierIndex is -1 (default)", func(t *testing.T) { restore := setBierIndex(-1) defer restore() - if fw.IsBierEnabled() { + if bier.IsBierEnabled() { t.Error("BIER should be disabled when BierIndex=-1") } - if fw.CfgBierIndex() != -1 { - t.Error("fw.CfgBierIndex should return -1") + if bier.CfgBierIndex() != -1 { + t.Error("bier.CfgBierIndex should return -1") } }) t.Run("enabled when BierIndex is 0", func(t *testing.T) { restore := setBierIndex(0) defer restore() - if !fw.IsBierEnabled() { + if !bier.IsBierEnabled() { t.Error("BIER should be enabled when BierIndex=0") } }) @@ -188,7 +188,7 @@ func TestBierEnabledConfig(t *testing.T) { t.Run("enabled for large index", func(t *testing.T) { restore := setBierIndex(255) defer restore() - if !fw.IsBierEnabled() { + if !bier.IsBierEnabled() { t.Error("BIER should be enabled when BierIndex=255") } }) @@ -198,7 +198,7 @@ func TestBierEnabledConfig(t *testing.T) { func TestBiftEdgeCases(t *testing.T) { t.Run("GetNeighborEntries on empty BIFT", func(t *testing.T) { - b := &fw.BiftState{} + b := &bier.BiftState{} neighbors := b.GetNeighborEntries() if len(neighbors) != 0 { t.Errorf("empty BIFT should have 0 neighbors, got %d", len(neighbors)) @@ -206,7 +206,7 @@ func TestBiftEdgeCases(t *testing.T) { }) t.Run("GetNeighborEntries skips entries with no next hop", func(t *testing.T) { - b := &fw.BiftState{} + b := &bier.BiftState{} r := enc.Name{enc.NewGenericComponent("r")} b.RegisterRouter(r, 0) // No UpdateNextHop call — NextHop is 0 @@ -218,7 +218,7 @@ func TestBiftEdgeCases(t *testing.T) { }) t.Run("GetNeighborEntries skips entries with nil F-BM", func(t *testing.T) { - b := &fw.BiftState{} + b := &bier.BiftState{} r := enc.Name{enc.NewGenericComponent("r")} b.RegisterRouter(r, 1) b.UpdateNextHop(1, 99) @@ -231,12 +231,12 @@ func TestBiftEdgeCases(t *testing.T) { }) t.Run("RebuildFbm on empty BIFT does not panic", func(t *testing.T) { - b := &fw.BiftState{} + b := &bier.BiftState{} b.RebuildFbm() // must not panic }) t.Run("BuildBierBitString with empty egress list returns nil", func(t *testing.T) { - b := &fw.BiftState{} + b := &bier.BiftState{} bs := b.BuildBierBitString(nil) if bs != nil { t.Errorf("empty egress list should return nil, got %v", bs) @@ -244,7 +244,7 @@ func TestBiftEdgeCases(t *testing.T) { }) t.Run("BuildBierBitString with all unknown routers returns nil", func(t *testing.T) { - b := &fw.BiftState{} + b := &bier.BiftState{} unknown := enc.Name{enc.NewGenericComponent("unknown")} bs := b.BuildBierBitString([]enc.Name{unknown}) if bs != nil { @@ -253,7 +253,7 @@ func TestBiftEdgeCases(t *testing.T) { }) t.Run("RegisterRouter overwrites existing BFR-ID", func(t *testing.T) { - b := &fw.BiftState{} + b := &bier.BiftState{} r := enc.Name{enc.NewGenericComponent("router")} b.RegisterRouter(r, 3) b.RegisterRouter(r, 7) // re-register same name, different bit @@ -268,12 +268,12 @@ func TestBiftEdgeCases(t *testing.T) { }) t.Run("UpdateNextHop on non-existent BFR-ID is a no-op", func(t *testing.T) { - b := &fw.BiftState{} + b := &bier.BiftState{} b.UpdateNextHop(99, 100) // must not panic }) t.Run("RebuildFbm groups multiple BFR-IDs per face", func(t *testing.T) { - b := &fw.BiftState{} + b := &bier.BiftState{} for i := 0; i < 8; i++ { name := enc.Name{enc.NewGenericComponent("r" + string(rune('0'+i)))} b.RegisterRouter(name, i) @@ -287,7 +287,7 @@ func TestBiftEdgeCases(t *testing.T) { } fbm := neighbors[0].Fbm for i := 0; i < 8; i++ { - if !fw.BierGetBit(fbm, i) { + if !bier.BierGetBit(fbm, i) { t.Errorf("F-BM for face 555 should have bit %d set", i) } } @@ -298,7 +298,7 @@ func TestBiftEdgeCases(t *testing.T) { // Use table.Initialize() to set it up with the default config. table.Initialize() - b := &fw.BiftState{} + b := &bier.BiftState{} r := enc.Name{enc.NewGenericComponent("r")} b.RegisterRouter(r, 0) b.BuildFromFib() // FIB empty → no next hops resolved, no panic @@ -309,7 +309,7 @@ func TestBiftEdgeCases(t *testing.T) { // --- Concurrent access --- func TestBiftConcurrency(t *testing.T) { - b := &fw.BiftState{} + b := &bier.BiftState{} var wg sync.WaitGroup const goroutines = 20 @@ -355,7 +355,7 @@ func TestBiftConcurrency(t *testing.T) { } func TestBiftBuildBierBitStringMixed(t *testing.T) { - b := &fw.BiftState{} + b := &bier.BiftState{} known := enc.Name{enc.NewGenericComponent("known")} unknown := enc.Name{enc.NewGenericComponent("unknown")} @@ -364,10 +364,10 @@ func TestBiftBuildBierBitStringMixed(t *testing.T) { bs := b.BuildBierBitString(egressRouters) // Only bit 3 should be set (unknown skipped) - if !fw.BierGetBit(bs, 3) { + if !bier.BierGetBit(bs, 3) { t.Error("bit 3 should be set for known router") } - if fw.BierGetBit(bs, 0) || fw.BierGetBit(bs, 1) || fw.BierGetBit(bs, 2) { + if bier.BierGetBit(bs, 0) || bier.BierGetBit(bs, 1) || bier.BierGetBit(bs, 2) { t.Error("only bit 3 should be set") } } @@ -375,19 +375,19 @@ func TestBiftBuildBierBitStringMixed(t *testing.T) { func TestBierAndNotDoesNotModifyInputs(t *testing.T) { a := []byte{0xFF, 0xFF} b := []byte{0x0F, 0xF0} - aCopy := fw.BierClone(a) - bCopy := fw.BierClone(b) + aCopy := bier.BierClone(a) + bCopy := bier.BierClone(b) - fw.BierAndNot(a, b) + bier.BierAndNot(a, b) for i := range a { if a[i] != aCopy[i] { - t.Errorf("fw.BierAndNot mutated a at byte %d", i) + t.Errorf("bier.BierAndNot mutated a at byte %d", i) } } for i := range b { if b[i] != bCopy[i] { - t.Errorf("fw.BierAndNot mutated b at byte %d", i) + t.Errorf("bier.BierAndNot mutated b at byte %d", i) } } } @@ -395,19 +395,19 @@ func TestBierAndNotDoesNotModifyInputs(t *testing.T) { func TestBierAndDoesNotModifyInputs(t *testing.T) { a := []byte{0xAA, 0xBB} b := []byte{0xCC, 0xDD} - aCopy := fw.BierClone(a) - bCopy := fw.BierClone(b) + aCopy := bier.BierClone(a) + bCopy := bier.BierClone(b) - fw.BierAnd(a, b) + bier.BierAnd(a, b) for i := range a { if a[i] != aCopy[i] { - t.Errorf("fw.BierAnd mutated a at byte %d", i) + t.Errorf("bier.BierAnd mutated a at byte %d", i) } } for i := range b { if b[i] != bCopy[i] { - t.Errorf("fw.BierAnd mutated b at byte %d", i) + t.Errorf("bier.BierAnd mutated b at byte %d", i) } } } diff --git a/fw/bier_tests/bier_test.go b/fw/bier/bier_test.go similarity index 77% rename from fw/bier_tests/bier_test.go rename to fw/bier/bier_test.go index 4d039897..28e314cd 100644 --- a/fw/bier_tests/bier_test.go +++ b/fw/bier/bier_test.go @@ -1,9 +1,9 @@ -package bier_tests +package bier_test import ( "testing" - fw "github.com/named-data/ndnd/fw/fw" + bier "github.com/named-data/ndnd/fw/bier" enc "github.com/named-data/ndnd/std/encoding" ) @@ -13,26 +13,26 @@ func TestBierBitManipulation(t *testing.T) { var bs []byte // Set bits 0, 5, 15 - bs = fw.BierSetBit(bs, 0) - bs = fw.BierSetBit(bs, 5) - bs = fw.BierSetBit(bs, 15) + bs = bier.BierSetBit(bs, 0) + bs = bier.BierSetBit(bs, 5) + bs = bier.BierSetBit(bs, 15) // Check that bits are set correctly - if !fw.BierGetBit(bs, 0) { + if !bier.BierGetBit(bs, 0) { t.Error("Bit 0 should be set") } - if !fw.BierGetBit(bs, 5) { + if !bier.BierGetBit(bs, 5) { t.Error("Bit 5 should be set") } - if !fw.BierGetBit(bs, 15) { + if !bier.BierGetBit(bs, 15) { t.Error("Bit 15 should be set") } // Check that other bits are not set - if fw.BierGetBit(bs, 1) { + if bier.BierGetBit(bs, 1) { t.Error("Bit 1 should not be set") } - if fw.BierGetBit(bs, 7) { + if bier.BierGetBit(bs, 7) { t.Error("Bit 7 should not be set") } }) @@ -40,28 +40,28 @@ func TestBierBitManipulation(t *testing.T) { t.Run("ClearBit", func(t *testing.T) { bs := []byte{0xFF, 0xFF} // All bits set in first two bytes - fw.BierClearBit(bs, 3) - fw.BierClearBit(bs, 10) + bier.BierClearBit(bs, 3) + bier.BierClearBit(bs, 10) - if fw.BierGetBit(bs, 3) { + if bier.BierGetBit(bs, 3) { t.Error("Bit 3 should be cleared") } - if fw.BierGetBit(bs, 10) { + if bier.BierGetBit(bs, 10) { t.Error("Bit 10 should be cleared") } - if !fw.BierGetBit(bs, 2) { + if !bier.BierGetBit(bs, 2) { t.Error("Bit 2 should still be set") } - if !fw.BierGetBit(bs, 9) { + if !bier.BierGetBit(bs, 9) { t.Error("Bit 9 should still be set") } }) - t.Run("fw.BierAnd", func(t *testing.T) { + t.Run("bier.BierAnd", func(t *testing.T) { a := []byte{0b11110000, 0b10101010} b := []byte{0b11001100, 0b11110000} - result := fw.BierAnd(a, b) + result := bier.BierAnd(a, b) expected := []byte{0b11000000, 0b10100000} if len(result) != len(expected) { @@ -74,11 +74,11 @@ func TestBierBitManipulation(t *testing.T) { } }) - t.Run("fw.BierAndNot", func(t *testing.T) { + t.Run("bier.BierAndNot", func(t *testing.T) { a := []byte{0b11111111, 0b11111111} b := []byte{0b00001111, 0b11110000} - result := fw.BierAndNot(a, b) + result := bier.BierAndNot(a, b) expected := []byte{0b11110000, 0b00001111} if len(result) != len(expected) { @@ -91,21 +91,21 @@ func TestBierBitManipulation(t *testing.T) { } }) - t.Run("fw.BierIsZero", func(t *testing.T) { + t.Run("bier.BierIsZero", func(t *testing.T) { zero := []byte{0, 0, 0} nonZero := []byte{0, 0, 1} - if !fw.BierIsZero(zero) { + if !bier.BierIsZero(zero) { t.Error("Zero bitstring should return true") } - if fw.BierIsZero(nonZero) { + if bier.BierIsZero(nonZero) { t.Error("Non-zero bitstring should return false") } }) - t.Run("fw.BierClone", func(t *testing.T) { + t.Run("bier.BierClone", func(t *testing.T) { original := []byte{1, 2, 3, 4} - cloned := fw.BierClone(original) + cloned := bier.BierClone(original) if len(cloned) != len(original) { t.Errorf("Clone length mismatch: got %d, want %d", len(cloned), len(original)) @@ -128,7 +128,7 @@ func TestBierBitManipulation(t *testing.T) { // TestBiftConstruction tests the BIFT construction and lookup func TestBiftConstruction(t *testing.T) { - bift := &fw.BiftState{} + bift := &bier.BiftState{} t.Run("RegisterRouter", func(t *testing.T) { r1 := enc.Name{enc.NewGenericComponent("router1")} @@ -177,13 +177,13 @@ func TestBiftConstruction(t *testing.T) { bs := bift.BuildBierBitString(egressRouters) // Check that bits 0 and 5 are set - if !fw.BierGetBit(bs, 0) { + if !bier.BierGetBit(bs, 0) { t.Error("Bit 0 (router1) should be set") } - if !fw.BierGetBit(bs, 5) { + if !bier.BierGetBit(bs, 5) { t.Error("Bit 5 (router3) should be set") } - if fw.BierGetBit(bs, 1) { + if bier.BierGetBit(bs, 1) { t.Error("Bit 1 (router2) should not be set") } }) @@ -226,13 +226,13 @@ func TestBiftConstruction(t *testing.T) { if face100Fbm == nil { t.Error("Face 100 neighbor entry not found") } else { - if !fw.BierGetBit(face100Fbm, 0) { + if !bier.BierGetBit(face100Fbm, 0) { t.Error("Face 100 F-BM should have bit 0 set") } - if !fw.BierGetBit(face100Fbm, 1) { + if !bier.BierGetBit(face100Fbm, 1) { t.Error("Face 100 F-BM should have bit 1 set") } - if fw.BierGetBit(face100Fbm, 5) { + if bier.BierGetBit(face100Fbm, 5) { t.Error("Face 100 F-BM should not have bit 5 set") } } @@ -240,10 +240,10 @@ func TestBiftConstruction(t *testing.T) { if face200Fbm == nil { t.Error("Face 200 neighbor entry not found") } else { - if !fw.BierGetBit(face200Fbm, 5) { + if !bier.BierGetBit(face200Fbm, 5) { t.Error("Face 200 F-BM should have bit 5 set") } - if fw.BierGetBit(face200Fbm, 0) { + if bier.BierGetBit(face200Fbm, 0) { t.Error("Face 200 F-BM should not have bit 0 set") } } @@ -258,48 +258,48 @@ func TestBierReplicationMask(t *testing.T) { // Neighbor 3 can reach routers at bits 5, 6 var fbm1, fbm2, fbm3 []byte - fbm1 = fw.BierSetBit(fbm1, 0) - fbm1 = fw.BierSetBit(fbm1, 1) - fbm1 = fw.BierSetBit(fbm1, 2) + fbm1 = bier.BierSetBit(fbm1, 0) + fbm1 = bier.BierSetBit(fbm1, 1) + fbm1 = bier.BierSetBit(fbm1, 2) - fbm2 = fw.BierSetBit(fbm2, 3) - fbm2 = fw.BierSetBit(fbm2, 4) + fbm2 = bier.BierSetBit(fbm2, 3) + fbm2 = bier.BierSetBit(fbm2, 4) - fbm3 = fw.BierSetBit(fbm3, 5) - fbm3 = fw.BierSetBit(fbm3, 6) + fbm3 = bier.BierSetBit(fbm3, 5) + fbm3 = bier.BierSetBit(fbm3, 6) // Incoming BIER bit-string has bits 0, 3, 5 set (3 egress routers) var incoming []byte - incoming = fw.BierSetBit(incoming, 0) - incoming = fw.BierSetBit(incoming, 3) - incoming = fw.BierSetBit(incoming, 5) + incoming = bier.BierSetBit(incoming, 0) + incoming = bier.BierSetBit(incoming, 3) + incoming = bier.BierSetBit(incoming, 5) // Compute replication masks - rep1 := fw.BierAnd(incoming, fbm1) - rep2 := fw.BierAnd(incoming, fbm2) - rep3 := fw.BierAnd(incoming, fbm3) + rep1 := bier.BierAnd(incoming, fbm1) + rep2 := bier.BierAnd(incoming, fbm2) + rep3 := bier.BierAnd(incoming, fbm3) // Neighbor 1 should replicate for bit 0 only - if !fw.BierGetBit(rep1, 0) { + if !bier.BierGetBit(rep1, 0) { t.Error("Neighbor 1 should replicate for bit 0") } - if fw.BierGetBit(rep1, 1) || fw.BierGetBit(rep1, 2) { + if bier.BierGetBit(rep1, 1) || bier.BierGetBit(rep1, 2) { t.Error("Neighbor 1 should not replicate for bits 1 or 2") } // Neighbor 2 should replicate for bit 3 only - if !fw.BierGetBit(rep2, 3) { + if !bier.BierGetBit(rep2, 3) { t.Error("Neighbor 2 should replicate for bit 3") } - if fw.BierGetBit(rep2, 4) { + if bier.BierGetBit(rep2, 4) { t.Error("Neighbor 2 should not replicate for bit 4") } // Neighbor 3 should replicate for bit 5 only - if !fw.BierGetBit(rep3, 5) { + if !bier.BierGetBit(rep3, 5) { t.Error("Neighbor 3 should replicate for bit 5") } - if fw.BierGetBit(rep3, 6) { + if bier.BierGetBit(rep3, 6) { t.Error("Neighbor 3 should not replicate for bit 6") } } diff --git a/fw/bier_tests/bier_topo_test.go b/fw/bier/bier_topo_test.go similarity index 98% rename from fw/bier_tests/bier_topo_test.go rename to fw/bier/bier_topo_test.go index 6e87f45a..8d62c2ac 100644 --- a/fw/bier_tests/bier_topo_test.go +++ b/fw/bier/bier_topo_test.go @@ -1,4 +1,4 @@ -package bier_tests +package bier_test // Topology-level BIER simulation tests. // @@ -9,7 +9,7 @@ package bier_tests // - Loop suppression prevents infinite forwarding // - Scale: up to 256 routers // -// Each test creates per-router fw.BiftState objects and runs the BIER algorithm +// Each test creates per-router bier.BiftState objects and runs the BIER algorithm // directly (without the global Bift or processBierInterest), allowing full // multi-router simulation in a single test process. @@ -19,7 +19,7 @@ import ( "sort" "testing" - fw "github.com/named-data/ndnd/fw/fw" + bier "github.com/named-data/ndnd/fw/bier" enc "github.com/named-data/ndnd/std/encoding" ) @@ -30,18 +30,18 @@ import ( // topo represents a simulated BIER network with N routers (IDs 0..N-1). type topo struct { n int - adj [][]int // undirected adjacency list - bift []*fw.BiftState // per-router BIFT (index == router ID == BFR-ID) + adj [][]int // undirected adjacency list + bift []*bier.BiftState // per-router BIFT (index == router ID == BFR-ID) } func newTopo(n int) *topo { t := &topo{ n: n, adj: make([][]int, n), - bift: make([]*fw.BiftState, n), + bift: make([]*bier.BiftState, n), } for i := range t.bift { - t.bift[i] = &fw.BiftState{} + t.bift[i] = &bier.BiftState{} } return t } @@ -89,7 +89,7 @@ func (t *topo) buildBifts() { b.RegisterRouter(name, dst) if nh[dst] >= 0 { // Offset face IDs by 1: NextHop=0 is the "unset" sentinel in - // fw.BiftState, so router 0 as a direct neighbour must map to face 1. + // bier.BiftState, so router 0 as a direct neighbour must map to face 1. b.UpdateNextHop(dst, uint64(nh[dst]+1)) } } @@ -101,7 +101,7 @@ func (t *topo) buildBifts() { func (t *topo) buildBitstring(ids ...int) []byte { var bs []byte for _, id := range ids { - bs = fw.BierSetBit(bs, id) + bs = bier.BierSetBit(bs, id) } return bs } @@ -128,7 +128,7 @@ func (t *topo) simulate(srcRouter int, bs []byte) topoSimResult { hopCount int } - queue := []item{{srcRouter, fw.BierClone(bs), -1, 0}} + queue := []item{{srcRouter, bier.BierClone(bs), -1, 0}} // visited prevents processing the same (router, bitstring) state twice. visited := make(map[string]bool) @@ -142,18 +142,18 @@ func (t *topo) simulate(srcRouter int, bs []byte) topoSimResult { } visited[key] = true - remaining := fw.BierClone(cur.bs) + remaining := bier.BierClone(cur.bs) // BFER check: local bit - if fw.BierGetBit(remaining, cur.router) { + if bier.BierGetBit(remaining, cur.router) { res.delivered[cur.router] = true if _, seen := res.hopCounts[cur.router]; !seen { res.hopCounts[cur.router] = cur.hopCount } - fw.BierClearBit(remaining, cur.router) + bier.BierClearBit(remaining, cur.router) } - if fw.BierIsZero(remaining) { + if bier.BierIsZero(remaining) { continue } @@ -164,13 +164,13 @@ func (t *topo) simulate(srcRouter int, bs []byte) topoSimResult { if nbID == cur.inFace { continue } - mask := fw.BierAnd(remaining, nb.Fbm) - if fw.BierIsZero(mask) { + mask := bier.BierAnd(remaining, nb.Fbm) + if bier.BierIsZero(mask) { continue } res.packetsSent++ queue = append(queue, item{nbID, mask, cur.router, cur.hopCount + 1}) - remaining = fw.BierAndNot(remaining, nb.Fbm) + remaining = bier.BierAndNot(remaining, nb.Fbm) } } return res diff --git a/fw/defn/name.go b/fw/defn/name.go index 4c4aeaca..a1190702 100644 --- a/fw/defn/name.go +++ b/fw/defn/name.go @@ -15,5 +15,13 @@ var BEST_ROUTE_STRATEGY = STRATEGY_PREFIX. Append(enc.NewGenericComponent("best-route")). Append(enc.NewVersionComponent(1)) +var BROADCAST_STRATEGY = STRATEGY_PREFIX. + Append(enc.NewGenericComponent("broadcast")). + Append(enc.NewVersionComponent(1)) + +var BIER_STRATEGY = STRATEGY_PREFIX. + Append(enc.NewGenericComponent("bier")). + Append(enc.NewVersionComponent(1)) + // Default forwarding strategy name var DEFAULT_STRATEGY = BEST_ROUTE_STRATEGY diff --git a/fw/fw/bestroute.go b/fw/fw/bestroute.go index 03b697be..03c44989 100644 --- a/fw/fw/bestroute.go +++ b/fw/fw/bestroute.go @@ -117,6 +117,22 @@ func (s *BestRoute) AfterReceiveInterest( core.Log.Debug(s, "No usable nexthop for Interest - DROP", "name", packet.Name) } +func (s *BestRoute) AfterReceiveMulticastInterest( + packet *defn.Pkt, + pitEntry table.PitEntry, + inFace uint64, + petEntry table.PetEntry, + deliveredToLocal bool, +) { + core.Log.Error(s, "BestRoute does not support AfterReceiveMulticastInterest", + "name", packet.Name, + "inFace", inFace, + "petNextHops", len(petEntry.NextHops), + "petEgress", len(petEntry.EgressRouters), + "deliveredToLocal", deliveredToLocal, + ) +} + // (AI GENERATED DESCRIPTION): No‑op; the BestRoute strategy performs no action before satisfying an Interest. func (s *BestRoute) BeforeSatisfyInterest(pitEntry table.PitEntry, inFace uint64) { // This does nothing in BestRoute diff --git a/fw/fw/bier.go b/fw/fw/bier.go index 4ab8cf90..059cd713 100644 --- a/fw/fw/bier.go +++ b/fw/fw/bier.go @@ -1,296 +1,177 @@ -/* BIER - Bit Index Explicit Replication for ndnd +/* BIER Strategy for ndnd * - * Stateless multicast forwarding using bit-indexed replication. - * Each router is assigned a unique BFR-ID (bit index) by the operator. - * The BIFT maps each bit position to a next-hop face and forwarding bit mask. + * Implements BIER (Bit Index Explicit Replication) multicast forwarding + * as a proper NDN forwarding strategy, working in tandem with the PIT. + * + * Roles: + * BFIR: thread.go detects multiple egress routers, pre-encodes bit-string, + * then calls this strategy. Strategy replicates to BIFT neighbors via + * SendInterest (creating PIT out-records for tracked Data return). + * + * BFR: Transit router receives Interest with BIER header. Goes through full + * PIT pipeline (no bypass). Strategy replicates to neighbors via BIFT + * F-BM AND operations, using SendInterest for PIT tracking. + * + * BFER: Local bit is set. thread.go delivers to local app via allowedLocalNexthops + * (PET-based). Strategy clears local bit then replicates remaining bits. */ package fw import ( - "sort" - "sync" - + "github.com/named-data/ndnd/fw/bier" "github.com/named-data/ndnd/fw/core" + "github.com/named-data/ndnd/fw/defn" "github.com/named-data/ndnd/fw/table" enc "github.com/named-data/ndnd/std/encoding" + ndnlog "github.com/named-data/ndnd/std/log" ) -// BiftEntry represents a single entry in the Bit Index Forwarding Table. -// Each entry maps a BFR-ID (bit position) to a next-hop face and -// the forwarding bit mask (F-BM) for that neighbor. -type BiftEntry struct { - BfrId int // Bit position this entry is for - RouterName enc.Name // Name of the destination router - NextHop uint64 // Face ID to reach this router's next hop - Fbm []byte // Forwarding Bit Mask for this neighbor -} - -// Bift is the global Bit Index Forwarding Table. -var Bift = &BiftState{} - -// BiftState holds the current BIFT and mapping state. -type BiftState struct { - mu sync.RWMutex - entries map[int]*BiftEntry // BFR-ID -> entry - - // routerBit maps router name hash -> BFR-ID for quick lookup - routerBit map[uint64]int -} - -func (b *BiftState) String() string { - return "bift" -} - -// CfgBierIndex returns the configured BIER index for this router. -// Returns -1 if BIER is not configured (disabled). -func CfgBierIndex() int { - return core.C.Fw.BierIndex -} - -// IsBierEnabled returns true if BIER forwarding is enabled on this router. -func IsBierEnabled() bool { - return CfgBierIndex() >= 0 -} - -// --- Bit-string manipulation helpers --- - -// BierGetBit returns true if bit position pos is set in the bitstring. -// Bit 0 is the LSB of the first byte. -func BierGetBit(bs []byte, pos int) bool { - byteIdx := pos / 8 - if byteIdx >= len(bs) { - return false - } - bitIdx := uint(pos % 8) - return (bs[byteIdx] & (1 << bitIdx)) != 0 -} - -// BierSetBit sets bit position pos in the bitstring. -// Grows the slice if needed. -func BierSetBit(bs []byte, pos int) []byte { - byteIdx := pos / 8 - for byteIdx >= len(bs) { - bs = append(bs, 0) - } - bitIdx := uint(pos % 8) - bs[byteIdx] |= (1 << bitIdx) - return bs -} - -// BierClearBit clears bit position pos in the bitstring. -func BierClearBit(bs []byte, pos int) { - byteIdx := pos / 8 - if byteIdx >= len(bs) { - return - } - bitIdx := uint(pos % 8) - bs[byteIdx] &^= (1 << bitIdx) -} - -// BierAnd returns bitwise AND of two bitstrings. Result length = min(len(a), len(b)). -func BierAnd(a, b []byte) []byte { - 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] - } - 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)) - copy(result, a) - minLen := len(a) - if len(b) < minLen { - minLen = len(b) - } - for i := 0; i < minLen; i++ { - result[i] = a[i] &^ b[i] - } - return result -} - -// BierIsZero returns true if all bits in the bitstring are zero. -func BierIsZero(bs []byte) bool { - for _, b := range bs { - if b != 0 { - return false +// BierStrategyName is the canonical name of the BIER strategy. +// Exported so thread.go can use it for auto-override when packet.Bier is set. +var BierStrategyName enc.Name + +// BierStrategy implements BIER multicast forwarding via the NDN strategy interface. +// It works in tandem with the PIT: replication uses SendInterest (PIT out-records) +// rather than raw face sends, enabling Data return path tracking and loop suppression. +type BierStrategy struct { + StrategyBase +} + +func init() { + strategyInit = append(strategyInit, func() Strategy { return &BierStrategy{} }) + StrategyVersions["bier"] = []uint64{1} +} + +func (s *BierStrategy) Instantiate(fwThread *Thread) { + s.NewStrategyBase(fwThread, "bier", 1) + BierStrategyName = s.GetName() +} + +func (s *BierStrategy) AfterContentStoreHit( + packet *defn.Pkt, + pitEntry table.PitEntry, + inFace uint64, +) { + core.Log.Trace(s, "AfterContentStoreHit", "name", packet.Name, "faceid", inFace) + s.SendData(packet, pitEntry, inFace, 0) +} + +func (s *BierStrategy) AfterReceiveData( + packet *defn.Pkt, + pitEntry table.PitEntry, + inFace uint64, +) { + core.Log.Trace(s, "AfterReceiveData", "name", packet.Name, "inrecords", len(pitEntry.InRecords())) + for faceID := range pitEntry.InRecords() { + core.Log.Trace(s, "Forwarding Data", "name", packet.Name, "faceid", faceID) + s.SendData(packet, pitEntry, faceID, inFace) + } +} + +func (s *BierStrategy) AfterReceiveInterest( + packet *defn.Pkt, + pitEntry table.PitEntry, + inFace uint64, + nexthops []StrategyCandidateHop, +) { + core.Log.Error(s, "BierStrategy does not support AfterReceiveInterest (unicast)", + "name", packet.Name, + "inFace", inFace, + "nexthops", len(nexthops), + ) +} + +func (s *BierStrategy) AfterReceiveMulticastInterest( + packet *defn.Pkt, + pitEntry table.PitEntry, + inFace uint64, + petEntry table.PetEntry, + deliveredToLocal bool, +) { + if len(packet.Bier) == 0 { + core.Log.Trace(s, "BFIR: encoding BIER bit-string", "name", packet.Name, "egress-count", len(petEntry.EgressRouters)) + packet.Bier = bier.Bift.BuildBierBitString(petEntry.EgressRouters) + } + + if deliveredToLocal && len(packet.Bier) > 0 && bier.IsBierEnabled() { + bs := bier.BierClone(packet.Bier) + bier.BierClearBit(bs, bier.CfgBierIndex()) + packet.Bier = bs + if bier.BierIsZero(bs) { + return } } - return true -} - -// BierClone returns a copy of the bitstring. -func BierClone(bs []byte) []byte { - if bs == nil { - return nil - } - c := make([]byte, len(bs)) - copy(c, bs) - return c -} - -// --- BIFT Construction --- - -// RegisterBierRouter registers a router name with its operator-assigned BFR-ID. -// This is called when learning about a router's BIER index (e.g., from DV). -func (b *BiftState) RegisterRouter(routerName enc.Name, bfrId int) { - b.mu.Lock() - defer b.mu.Unlock() - - if b.entries == nil { - b.entries = make(map[int]*BiftEntry) - b.routerBit = make(map[uint64]int) - } - - b.entries[bfrId] = &BiftEntry{ - BfrId: bfrId, - RouterName: routerName.Clone(), - } - b.routerBit[routerName.Hash()] = bfrId - - core.Log.Info(b, "Registered BIER router", "name", routerName, "bfr-id", bfrId) -} - -// UpdateNextHop updates the next-hop face for reaching a given BFR-ID. -// Called when FIB changes. -func (b *BiftState) UpdateNextHop(bfrId int, nextHop uint64) { - b.mu.Lock() - defer b.mu.Unlock() - - if entry, ok := b.entries[bfrId]; ok { - entry.NextHop = nextHop - } -} - -// RebuildFbm rebuilds forwarding bit masks for all BIFT entries. -// Groups BFR-IDs by their next-hop face and computes the F-BM for each group. -func (b *BiftState) RebuildFbm() { - b.mu.Lock() - defer b.mu.Unlock() - // Find maximum BFR-ID to size bitstrings - maxBit := 0 - for bfrId := range b.entries { - if bfrId > maxBit { - maxBit = bfrId + s.replicateBier(packet, pitEntry, inFace) +} + +// bierReplicate performs BIFT-based BIER replication through the PIT. +// Local bit is cleared first (local delivery already done by thread.go's +// allowedLocalNexthops block). Remaining bits are replicated to BIFT +// neighbors using sendInterest, which creates PIT out-records. +// Shared by BierStrategy and Multicast so both strategies are BIER-aware. +func bierReplicate( + logCtx ndnlog.Tag, + packet *defn.Pkt, + pitEntry table.PitEntry, + inFace uint64, + sendInterest func(*defn.Pkt, table.PitEntry, uint64, uint64) bool, +) { + incomingBs := bier.BierClone(packet.Bier) + + // Clear local bit — local delivery was already handled by thread.go. + // Also clear it if no local app is registered, to avoid forwarding our + // own bit position to downstream neighbors. + if bier.IsBierEnabled() { + localId := bier.CfgBierIndex() + if localId >= 0 && bier.BierGetBit(incomingBs, localId) { + bier.BierClearBit(incomingBs, localId) } } - bsLen := (maxBit / 8) + 1 - // Group by next-hop face - faceGroups := make(map[uint64][]int) // faceID -> list of BFR-IDs - for bfrId, entry := range b.entries { - if entry.NextHop > 0 { - faceGroups[entry.NextHop] = append(faceGroups[entry.NextHop], bfrId) - } + if bier.BierIsZero(incomingBs) { + return } - // Build F-BM for each face group - faceFbm := make(map[uint64][]byte) - for faceID, bfrIds := range faceGroups { - fbm := make([]byte, bsLen) - for _, id := range bfrIds { - fbm = BierSetBit(fbm, id) - } - faceFbm[faceID] = fbm - } + core.Log.Trace(logCtx, "BIER replication", "name", packet.Name, "bs-len", len(incomingBs)) - // Assign F-BM to each entry based on its next-hop face - for _, entry := range b.entries { - if fbm, ok := faceFbm[entry.NextHop]; ok { - entry.Fbm = fbm + neighbors := bier.Bift.GetNeighborEntries() + for _, neighbor := range neighbors { + if neighbor.FaceID == inFace { + continue // Never send back on incoming face } - } - - core.Log.Info(b, "Rebuilt BIFT forwarding bit masks", - "entries", len(b.entries), "faces", len(faceGroups)) -} -// BuildFromFib rebuilds the BIFT from the current FIB state. -// For each known router (registered via DV), looks up the FIB to find next hops. -func (b *BiftState) BuildFromFib() { - b.mu.RLock() - entries := make(map[int]*BiftEntry, len(b.entries)) - for k, v := range b.entries { - entries[k] = v - } - b.mu.RUnlock() - - // Update next hops from FIB for each registered router - for _, entry := range entries { - nexthops := table.FibStrategyTable.FindNextHopsEnc(entry.RouterName) - if len(nexthops) > 0 { - // Sort by cost and pick best - sort.Slice(nexthops, func(i, j int) bool { - return nexthops[i].Cost < nexthops[j].Cost - }) - b.UpdateNextHop(entry.BfrId, nexthops[0].Nexthop) + replicationMask := bier.BierAnd(incomingBs, neighbor.Fbm) + if bier.BierIsZero(replicationMask) { + continue } - } - - b.RebuildFbm() -} - -// GetRouterBfrId returns the BFR-ID for a given router name. -func (b *BiftState) GetRouterBfrId(routerName enc.Name) (int, bool) { - b.mu.RLock() - defer b.mu.RUnlock() - if id, ok := b.routerBit[routerName.Hash()]; ok { - return id, true - } - return -1, false -} + // Clone packet with per-neighbor replication mask + clonePkt := &defn.Pkt{ + Name: packet.Name, + L3: packet.L3, + Raw: packet.Raw, + IncomingFaceID: packet.IncomingFaceID, + CongestionMark: packet.CongestionMark, + Bier: replicationMask, + } -// --- BFIR Functions --- + core.Log.Trace(logCtx, "BIER: replicating to neighbor", "name", packet.Name, "faceid", neighbor.FaceID) -// BuildBierBitString builds a BIER bit-string from a list of egress router names. -// Returns nil if no egress routers have known BFR-IDs. -func (b *BiftState) BuildBierBitString(egressRouters []enc.Name) []byte { - b.mu.RLock() - defer b.mu.RUnlock() + // KEY: SendInterest creates PIT out-record — Data return path is tracked + sendInterest(clonePkt, pitEntry, neighbor.FaceID, inFace) - var bs []byte - for _, er := range egressRouters { - if id, ok := b.routerBit[er.Hash()]; ok { - bs = BierSetBit(bs, id) + // Loop suppression: clear forwarded bits from working mask + incomingBs = bier.BierAndNot(incomingBs, neighbor.Fbm) + if bier.BierIsZero(incomingBs) { + break } } - return bs } -// --- Replication (BFR/BFER) --- - -// BiftNeighborEntry represents a unique next-hop face and its aggregated F-BM. -type BiftNeighborEntry struct { - FaceID uint64 - Fbm []byte +func (s *BierStrategy) replicateBier(packet *defn.Pkt, pitEntry table.PitEntry, inFace uint64) { + bierReplicate(s, packet, pitEntry, inFace, s.SendInterest) } -// GetNeighborEntries returns the unique neighbor entries (grouped by face) from the BIFT. -func (b *BiftState) GetNeighborEntries() []BiftNeighborEntry { - b.mu.RLock() - defer b.mu.RUnlock() - - faceMap := make(map[uint64][]byte) - for _, entry := range b.entries { - if entry.NextHop == 0 || entry.Fbm == nil { - continue - } - if _, ok := faceMap[entry.NextHop]; !ok { - faceMap[entry.NextHop] = BierClone(entry.Fbm) - } - } - - neighbors := make([]BiftNeighborEntry, 0, len(faceMap)) - for faceID, fbm := range faceMap { - neighbors = append(neighbors, BiftNeighborEntry{FaceID: faceID, Fbm: fbm}) - } - return neighbors -} +func (s *BierStrategy) BeforeSatisfyInterest(pitEntry table.PitEntry, inFace uint64) {} diff --git a/fw/fw/bier_strategy.go b/fw/fw/bier_strategy.go deleted file mode 100644 index 6b9bc831..00000000 --- a/fw/fw/bier_strategy.go +++ /dev/null @@ -1,154 +0,0 @@ -/* BIER Strategy for ndnd - * - * Implements BIER (Bit Index Explicit Replication) multicast forwarding - * as a proper NDN forwarding strategy, working in tandem with the PIT. - * - * Roles: - * BFIR: thread.go detects multiple egress routers, pre-encodes bit-string, - * then calls this strategy. Strategy replicates to BIFT neighbors via - * SendInterest (creating PIT out-records for tracked Data return). - * - * BFR: Transit router receives Interest with BIER header. Goes through full - * PIT pipeline (no bypass). Strategy replicates to neighbors via BIFT - * F-BM AND operations, using SendInterest for PIT tracking. - * - * BFER: Local bit is set. thread.go delivers to local app via allowedLocalNexthops - * (PET-based). Strategy clears local bit then replicates remaining bits. - */ - -package fw - -import ( - "github.com/named-data/ndnd/fw/core" - "github.com/named-data/ndnd/fw/defn" - "github.com/named-data/ndnd/fw/table" - enc "github.com/named-data/ndnd/std/encoding" - ndnlog "github.com/named-data/ndnd/std/log" -) - -// BierStrategyName is the canonical name of the BIER strategy. -// Exported so thread.go can use it for auto-override when packet.Bier is set. -var BierStrategyName enc.Name - -// BierStrategy implements BIER multicast forwarding via the NDN strategy interface. -// It works in tandem with the PIT: replication uses SendInterest (PIT out-records) -// rather than raw face sends, enabling Data return path tracking and loop suppression. -type BierStrategy struct { - StrategyBase -} - -func init() { - strategyInit = append(strategyInit, func() Strategy { return &BierStrategy{} }) - StrategyVersions["bier"] = []uint64{1} -} - -func (s *BierStrategy) Instantiate(fwThread *Thread) { - s.NewStrategyBase(fwThread, "bier", 1) - BierStrategyName = s.GetName() -} - -func (s *BierStrategy) AfterContentStoreHit( - packet *defn.Pkt, - pitEntry table.PitEntry, - inFace uint64, -) { - core.Log.Trace(s, "AfterContentStoreHit", "name", packet.Name, "faceid", inFace) - s.SendData(packet, pitEntry, inFace, 0) -} - -func (s *BierStrategy) AfterReceiveData( - packet *defn.Pkt, - pitEntry table.PitEntry, - inFace uint64, -) { - core.Log.Trace(s, "AfterReceiveData", "name", packet.Name, "inrecords", len(pitEntry.InRecords())) - for faceID := range pitEntry.InRecords() { - core.Log.Trace(s, "Forwarding Data", "name", packet.Name, "faceid", faceID) - s.SendData(packet, pitEntry, faceID, inFace) - } -} - -func (s *BierStrategy) AfterReceiveInterest( - packet *defn.Pkt, - pitEntry table.PitEntry, - inFace uint64, - nexthops []*table.FibNextHopEntry, - nextER []enc.Name, -) { - if len(packet.Bier) == 0 { - // BierStrategy only handles BIER Interests. No broadcast fallback. - core.Log.Debug(s, "BierStrategy: no bit-string, dropping", "name", packet.Name) - return - } - s.replicateBier(packet, pitEntry, inFace) -} - -// bierReplicate performs BIFT-based BIER replication through the PIT. -// Local bit is cleared first (local delivery already done by thread.go's -// allowedLocalNexthops block). Remaining bits are replicated to BIFT -// neighbors using sendInterest, which creates PIT out-records. -// Shared by BierStrategy and Multicast so both strategies are BIER-aware. -func bierReplicate( - logCtx ndnlog.Tag, - packet *defn.Pkt, - pitEntry table.PitEntry, - inFace uint64, - sendInterest func(*defn.Pkt, table.PitEntry, uint64, uint64) bool, -) { - incomingBs := BierClone(packet.Bier) - - // Clear local bit — local delivery was already handled by thread.go. - // Also clear it if no local app is registered, to avoid forwarding our - // own bit position to downstream neighbors. - if IsBierEnabled() { - localId := CfgBierIndex() - if localId >= 0 && BierGetBit(incomingBs, localId) { - BierClearBit(incomingBs, localId) - } - } - - if BierIsZero(incomingBs) { - return - } - - core.Log.Trace(logCtx, "BIER replication", "name", packet.Name, "bs-len", len(incomingBs)) - - neighbors := Bift.GetNeighborEntries() - for _, neighbor := range neighbors { - if neighbor.FaceID == inFace { - continue // Never send back on incoming face - } - - replicationMask := BierAnd(incomingBs, neighbor.Fbm) - if BierIsZero(replicationMask) { - continue - } - - // Clone packet with per-neighbor replication mask - clonePkt := &defn.Pkt{ - Name: packet.Name, - L3: packet.L3, - Raw: packet.Raw, - IncomingFaceID: packet.IncomingFaceID, - CongestionMark: packet.CongestionMark, - Bier: replicationMask, - } - - core.Log.Trace(logCtx, "BIER: replicating to neighbor", "name", packet.Name, "faceid", neighbor.FaceID) - - // KEY: SendInterest creates PIT out-record — Data return path is tracked - sendInterest(clonePkt, pitEntry, neighbor.FaceID, inFace) - - // Loop suppression: clear forwarded bits from working mask - incomingBs = BierAndNot(incomingBs, neighbor.Fbm) - if BierIsZero(incomingBs) { - break - } - } -} - -func (s *BierStrategy) replicateBier(packet *defn.Pkt, pitEntry table.PitEntry, inFace uint64) { - bierReplicate(s, packet, pitEntry, inFace, s.SendInterest) -} - -func (s *BierStrategy) BeforeSatisfyInterest(pitEntry table.PitEntry, inFace uint64) {} diff --git a/fw/fw/broadcast.go b/fw/fw/broadcast.go index 338ee270..1c5ccc8e 100644 --- a/fw/fw/broadcast.go +++ b/fw/fw/broadcast.go @@ -1,7 +1,6 @@ -/** - * Broadcast forwarding strategy. +/* Broadcast Strategy for ndnd * - * Using this strategy, the interest packet will be forwarded to all nexthops. + * Implements multicast broadcast forwarding as a proper NDN forwarding strategy. */ package fw @@ -12,34 +11,30 @@ import ( "github.com/named-data/ndnd/fw/table" ) -// The strategy to forward interests to all nexthops. -type Broadcast struct { +// BroadcastStrategy implements broadcast multicast forwarding. +type BroadcastStrategy struct { StrategyBase } -// (AI GENERATED DESCRIPTION): Registers the Broadcast strategy with version 1 in the strategy registry by appending its constructor to the init list. func init() { - strategyInit = append(strategyInit, func() Strategy { return &Broadcast{} }) + strategyInit = append(strategyInit, func() Strategy { return &BroadcastStrategy{} }) StrategyVersions["broadcast"] = []uint64{1} } -// (AI GENERATED DESCRIPTION): Initializes the *Broadcast strategy by setting up its base with the name “broadcast” and priority 1 on the provided forwarding thread. -func (s *Broadcast) Instantiate(fwThread *Thread) { +func (s *BroadcastStrategy) Instantiate(fwThread *Thread) { s.NewStrategyBase(fwThread, "broadcast", 1) } -// (AI GENERATED DESCRIPTION): Sends a cached Data packet (retrieved from the Content Store) back to the requester via the specified PIT entry, using the requesting face and indicating the Content Store as the data source. -func (s *Broadcast) AfterContentStoreHit( +func (s *BroadcastStrategy) AfterContentStoreHit( packet *defn.Pkt, pitEntry table.PitEntry, inFace uint64, ) { core.Log.Trace(s, "AfterContentStoreHit", "name", packet.Name, "faceid", inFace) - s.SendData(packet, pitEntry, inFace, 0) // 0 indicates ContentStore is source + s.SendData(packet, pitEntry, inFace, 0) } -// (AI GENERATED DESCRIPTION): Forwards a received Data packet to every face recorded in the PIT entry, logging each forwarding step and invoking SendData for each destination. -func (s *Broadcast) AfterReceiveData( +func (s *BroadcastStrategy) AfterReceiveData( packet *defn.Pkt, pitEntry table.PitEntry, inFace uint64, @@ -51,35 +46,67 @@ func (s *Broadcast) AfterReceiveData( } } -// Forwards an incoming Interest to all next-hops -func (s *Broadcast) AfterReceiveInterest( +func (s *BroadcastStrategy) AfterReceiveInterest( packet *defn.Pkt, pitEntry table.PitEntry, inFace uint64, nexthops []StrategyCandidateHop, ) { - if len(nexthops) == 0 { - core.Log.Debug(s, "No nexthop found - DROP", "name", packet.Name) - return - } + core.Log.Error(s, "BroadcastStrategy does not support AfterReceiveInterest (unicast)", + "name", packet.Name, + "inFace", inFace, + "nexthops", len(nexthops), + ) +} + +func (s *BroadcastStrategy) AfterReceiveMulticastInterest( + packet *defn.Pkt, + pitEntry table.PitEntry, + inFace uint64, + petEntry table.PetEntry, + deliveredToLocal bool, +) { + core.Log.Trace(s, "Broadcast multicast dispatch", + "name", packet.Name, + "deliveredToLocal", deliveredToLocal, + "petEgress", len(petEntry.EgressRouters), + "petNextHops", len(petEntry.NextHops), + ) + + seen := make(map[uint64]bool) + sent := 0 + + for _, egress := range petEntry.EgressRouters { + nextHops := table.FibStrategyTable.FindNextHopsEnc(egress) - successfulForward := false + for _, nextHop := range nextHops { + faceID := nextHop.Nexthop + if _, ok := seen[faceID]; ok { + continue + } + seen[faceID] = true - for _, nh := range nexthops { - if sent := s.SendInterest(packet, pitEntry, nh.HopEntry.Nexthop, inFace); sent { - core.Log.Trace(s, "Forwarded Interest", "name", packet.Name, "faceid", nh.HopEntry.Nexthop) - successfulForward = true - } else { - core.Log.Trace(s, "Error forwarding interest", "name", packet.Name, "faceid", nh.HopEntry.Nexthop) + if faceID == packet.IncomingFaceID { + continue + } + + if pitEntry.InRecords()[faceID] != nil { + continue + } + + if s.SendInterest(packet, pitEntry, faceID, inFace) { + sent++ + } } } - if !successfulForward { - core.Log.Debug(s, "No usable nexthop for Interest - DROP", "name", packet.Name) + if sent == 0 { + core.Log.Warn(s, "Broadcast multicast had no eligible PET-scoped nexthops", + "name", packet.Name, + "deliveredToLocal", deliveredToLocal, + "petEgress", len(petEntry.EgressRouters), + ) } } -// (AI GENERATED DESCRIPTION): No‑op hook invoked before satisfying an Interest in the Broadcast strategy – it performs no action. -func (s *Broadcast) BeforeSatisfyInterest(pitEntry table.PitEntry, inFace uint64) { - // This does nothing in Broadcast -} +func (s *BroadcastStrategy) BeforeSatisfyInterest(pitEntry table.PitEntry, inFace uint64) {} diff --git a/fw/fw/multicast.go b/fw/fw/multicast.go index 8030ff2f..864ad50e 100644 --- a/fw/fw/multicast.go +++ b/fw/fw/multicast.go @@ -90,6 +90,22 @@ func (s *Multicast) AfterReceiveInterest( } } +func (s *Multicast) AfterReceiveMulticastInterest( + packet *defn.Pkt, + pitEntry table.PitEntry, + inFace uint64, + petEntry table.PetEntry, + deliveredToLocal bool, +) { + core.Log.Error(s, "Multicast does not support AfterReceiveMulticastInterest", + "name", packet.Name, + "inFace", inFace, + "petNextHops", len(petEntry.NextHops), + "petEgress", len(petEntry.EgressRouters), + "deliveredToLocal", deliveredToLocal, + ) +} + // (AI GENERATED DESCRIPTION): No‑op hook invoked before satisfying an Interest in the Multicast strategy – it performs no action. func (s *Multicast) BeforeSatisfyInterest(pitEntry table.PitEntry, inFace uint64) { // This does nothing in Multicast diff --git a/fw/fw/replicast.go b/fw/fw/replicast.go deleted file mode 100644 index 6c3d6414..00000000 --- a/fw/fw/replicast.go +++ /dev/null @@ -1,108 +0,0 @@ -/** - * Replicast forwarding strategy - * - * Using this strategy, there will be exactly one request sent per unique ER. - * - * This will eventually be replaced by BIER multicast - */ - -package fw - -import ( - "sort" - - "github.com/named-data/ndnd/fw/core" - "github.com/named-data/ndnd/fw/defn" - "github.com/named-data/ndnd/fw/table" -) - -// The strategy to forward interests to all nexthops. -type Replicast struct { - StrategyBase -} - -// (AI GENERATED DESCRIPTION): Registers the Replicast strategy with version 1 in the strategy registry by appending its constructor to the init list. -func init() { - strategyInit = append(strategyInit, func() Strategy { return &Replicast{} }) - StrategyVersions["replicast"] = []uint64{1} -} - -// (AI GENERATED DESCRIPTION): Initializes the *Replicast strategy by setting up its base with the name “replicast” and priority 1 on the provided forwarding thread. -func (s *Replicast) Instantiate(fwThread *Thread) { - s.NewStrategyBase(fwThread, "replicast", 1) -} - -// (AI GENERATED DESCRIPTION): Sends a cached Data packet (retrieved from the Content Store) back to the requester via the specified PIT entry, using the requesting face and indicating the Content Store as the data source. -func (s *Replicast) AfterContentStoreHit( - packet *defn.Pkt, - pitEntry table.PitEntry, - inFace uint64, -) { - core.Log.Trace(s, "AfterContentStoreHit", "name", packet.Name, "faceid", inFace) - s.SendData(packet, pitEntry, inFace, 0) // 0 indicates ContentStore is source -} - -// (AI GENERATED DESCRIPTION): Forwards a received Data packet to every face recorded in the PIT entry, logging each forwarding step and invoking SendData for each destination. -func (s *Replicast) AfterReceiveData( - packet *defn.Pkt, - pitEntry table.PitEntry, - inFace uint64, -) { - core.Log.Trace(s, "AfterReceiveData", "name", packet.Name, "inrecords", len(pitEntry.InRecords())) - for faceID := range pitEntry.InRecords() { - core.Log.Trace(s, "Forwarding Data", "name", packet.Name, "faceid", faceID) - s.SendData(packet, pitEntry, faceID, inFace) - } -} - -// Forwards n interest packets to n unique egress routers -func (s *Replicast) AfterReceiveInterest( - packet *defn.Pkt, - pitEntry table.PitEntry, - inFace uint64, - nexthops []StrategyCandidateHop, -) { - if len(nexthops) == 0 { - core.Log.Debug(s, "No nexthop found - DROP", "name", packet.Name) - return - } - - // interest with ER tag should only be unicast as dest router is known - if len(packet.EgressRouter) > 0 { - core.Log.Debug(s, "Interest with ER tag passed to replicast - DROP", "name", packet.Name) - return - } - - // sort nexthops by cost and send to best-possible nexthop for each unique ER - sort.Slice(nexthops, func(i, j int) bool { - return nexthops[i].HopEntry.Cost < nexthops[j].HopEntry.Cost - }) - - sentER := make(map[uint64]bool) - - for _, nh := range nexthops { - if sentER[nh.EgressRouter.Hash()] { - core.Log.Trace(s, "Avoiding duplicate interest", "name", packet.Name, "sentER", sentER, "faceid", nh.HopEntry.Nexthop, "er", nh.EgressRouter, "inFace", inFace) - continue - } - - outgoingPkt := *packet - outgoingPkt.EgressRouter = nh.EgressRouter - if sent := s.SendInterest(&outgoingPkt, pitEntry, nh.HopEntry.Nexthop, inFace); sent { - core.Log.Trace(s, "Forwarded Interest", "name", outgoingPkt.Name, "faceid", nh.HopEntry.Nexthop, "er", nh.EgressRouter, "inFace", inFace) - sentER[nh.EgressRouter.Hash()] = true - } - } - - for _, nh := range nexthops { - er := nh.EgressRouter - if !sentER[er.Hash()] { - core.Log.Debug(s, "No usable replicast nexthop for Interest specific ER tag - DROP", "name", packet.Name, "ER", er) - } - } -} - -// (AI GENERATED DESCRIPTION): No‑op hook invoked before satisfying an Interest in the Replicast strategy – it performs no action. -func (s *Replicast) BeforeSatisfyInterest(pitEntry table.PitEntry, inFace uint64) { - // This does nothing in Replicast -} diff --git a/fw/fw/strategy.go b/fw/fw/strategy.go index 1546539b..1da34330 100644 --- a/fw/fw/strategy.go +++ b/fw/fw/strategy.go @@ -42,6 +42,12 @@ type Strategy interface { pitEntry table.PitEntry, inFace uint64, nexthops []StrategyCandidateHop) + AfterReceiveMulticastInterest( + packet *defn.Pkt, + pitEntry table.PitEntry, + inFace uint64, + petEntry table.PetEntry, + deliveredToLocal bool) BeforeSatisfyInterest( pitEntry table.PitEntry, inFace uint64) diff --git a/fw/fw/thread.go b/fw/fw/thread.go index fccd2e8f..4d98422c 100644 --- a/fw/fw/thread.go +++ b/fw/fw/thread.go @@ -14,6 +14,7 @@ import ( "sync/atomic" "time" + "github.com/named-data/ndnd/fw/bier" "github.com/named-data/ndnd/fw/core" "github.com/named-data/ndnd/fw/defn" "github.com/named-data/ndnd/fw/dispatch" @@ -22,6 +23,49 @@ import ( "github.com/named-data/ndnd/std/utils" ) +type forwardPipeline int + +const ( + fwUnicastTransit forwardPipeline = iota + fwUnicastIngress + fwUnicastEgress + fwMulticastIngress + fwMulticastEgress + fwMulticastTransit +) + +var forwardPipelineStrings = map[forwardPipeline]string{ + fwUnicastTransit: "unicast-transit", + fwUnicastIngress: "unicast-ingress", + fwUnicastEgress: "unicast-egress", + fwMulticastIngress: "multicast-ingress", + fwMulticastEgress: "multicast-egress", + fwMulticastTransit: "multicast-transit", +} + +func (p forwardPipeline) String() string { + if s, ok := forwardPipelineStrings[p]; ok { + return s + } + return fmt.Sprintf("forwardPipeline(%d)", int(p)) +} + +func (p forwardPipeline) isUnicast() bool { + return p == fwUnicastTransit || p == fwUnicastIngress || p == fwUnicastEgress +} + +func (p forwardPipeline) isMulticast() bool { + return !p.isUnicast() +} + +func (p forwardPipeline) isTransit() bool { + return p == fwUnicastTransit || p == fwMulticastTransit +} + +func (p forwardPipeline) isIngressEgress() bool { + return !p.isTransit() +} + // MaxFwThreads Maximum number of forwarding threads const MaxFwThreads = 32 @@ -246,58 +290,97 @@ func (t *Thread) processIncomingInterest(packet *defn.Pkt) { return } - // Get strategy for name - strategyName := table.FibStrategyTable.FindStrategyEnc(interest.Name()) - strategy := t.strategies[strategyName.Hash()] - bestRouteStrategy := t.strategies[defn.BEST_ROUTE_STRATEGY.Hash()] + // Use forwarding hint if present + lookupName := interest.Name() + if fhName != nil { + lookupName = fhName + } + + routerName, routerNameSet := CfgRouterName() + + isLocalHop := lookupName.At(0).Equal(enc.LOCALHOP) + var pipeline forwardPipeline + var petEntry table.PetEntry + var petFound bool + petLookup := false + if bier.IsBierEnabled() && len(packet.Bier) > 0 { + if bier.BierGetBit(bier.BierClone(packet.Bier), bier.CfgBierIndex()) { + pipeline = fwMulticastEgress + } else { + pipeline = fwMulticastTransit + } + } else if len(packet.EgressRouter) > 0 { + if routerNameSet && packet.EgressRouter.Equal(routerName) { + pipeline = fwUnicastEgress + } else { + pipeline = fwUnicastTransit + } + } else { + petLookup = true + petEntry, petFound = table.Pet.FindLongestPrefixEnc(lookupName) + if petFound && petEntry.Multicast { + pipeline = fwMulticastIngress + } else { + pipeline = fwUnicastIngress + } + } + core.Log.Trace(t, "Interest pipeline decision", + "name", packet.Name, + "lookup", lookupName, + "pipeline", pipeline, + "petFound", petFound, + "isLocalHop", isLocalHop, + "egressRouter", len(packet.EgressRouter) > 0, + "bier", len(packet.Bier) > 0, + ) // Add in-record and determine if already pending // this looks like custom interest again, but again can be changed without much issue? inRecord, isAlreadyPending, prevNonce := pitEntry.InsertInRecord( interest, incomingFace.FaceID(), packet.PitToken) - if !isAlreadyPending { - core.Log.Trace(t, "Interest is not pending", "name", packet.Name) - - // Check CS for matching entry - if t.pitCS.IsCsServing() { - csEntry := t.pitCS.FindMatchingDataFromCS(interest) - if csEntry != nil { - // Update counters - t.nCsHits.Add(1) - - // Parse the cached data packet and replace in the pending one - // This is not the fastest way to do it, but simplifies everything - // significantly. We can optimize this later. - csData, csWire, err := csEntry.Copy() - if csData != nil && csWire != nil { - // Mark PIT entry as expired - table.UpdateExpirationTimer(pitEntry, time.Now()) - - // Create the pending packet structure - packet.EgressRouter = nil - packet.L3.Data = csData - packet.L3.Interest = nil - packet.Raw = enc.Wire{csWire} - packet.Name = csData.NameV - strategy.AfterContentStoreHit(packet, pitEntry, incomingFace.FaceID()) - return - } else if err != nil { - core.Log.Error(t, "Error copying CS entry", "err", err) - } else { - core.Log.Error(t, "Error copying CS entry", "err", "csData is nil") - } - } else { - // Update counters - t.nCsMisses.Add(1) - } - } - } else { + if isAlreadyPending { core.Log.Trace(t, "Interest is already pending", "name", packet.Name) // Add the previous nonce to the dead nonce list to prevent further looping // TODO: review this design, not specified in NFD dev guide t.deadNonceList.Insert(interest.Name(), prevNonce) + } else { + core.Log.Trace(t, "Interest is not pending", "name", packet.Name) + } + + // Check CS for matching entry + if pipeline.isUnicast() && !isAlreadyPending && t.pitCS.IsCsServing() { + csEntry := t.pitCS.FindMatchingDataFromCS(interest) + if csEntry != nil { + // Update counters + t.nCsHits.Add(1) + + // Parse the cached data packet and replace in the pending one + // This is not the fastest way to do it, but simplifies everything + // significantly. We can optimize this later. + csData, csWire, err := csEntry.Copy() + if csData != nil && csWire != nil { + // Mark PIT entry as expired + table.UpdateExpirationTimer(pitEntry, time.Now()) + + // Create the pending packet structure + packet.EgressRouter = nil + packet.L3.Data = csData + packet.L3.Interest = nil + packet.Raw = enc.Wire{csWire} + packet.Name = csData.NameV + t.afterContentStoreHit(packet, pitEntry, incomingFace.FaceID()) + return + } else if err != nil { + core.Log.Error(t, "Error copying CS entry", "err", err) + } else { + core.Log.Error(t, "Error copying CS entry", "err", "csData is nil") + } + } else { + // Update counters + t.nCsMisses.Add(1) + } } // Update PIT entry expiration timer @@ -317,175 +400,168 @@ func (t *Thread) processIncomingInterest(packet *defn.Pkt) { return } - // Use forwarding hint if present - lookupName := interest.Name() - if fhName != nil { - lookupName = fhName - } + var petLocalHops []*table.PetNextHop - routerName, routerNameSet := CfgRouterName() - - nextLocal, nextNet, nextER, isMulticast := t.twoPhaseLookup(lookupName, packet.EgressRouter, routerName, routerNameSet, len(packet.Bier) > 0) + if pipeline.isIngressEgress() { + // perform cached pet lookup + if !petLookup { + petEntry, petFound = table.Pet.FindLongestPrefixEnc(lookupName) + petLookup = true + } - // If the first component is /localhop, we do not forward interests received - // on non-local faces to non-local faces - localFacesOnly := incomingFace.Scope() != defn.Local && packet.Name.At(0).Equal(enc.LOCALHOP) + if petFound { + petLocalHops = make([]*table.PetNextHop, 0, len(petEntry.NextHops)) + for i := range petEntry.NextHops { + nexthop := &petEntry.NextHops[i] + // Exclude incoming face + if nexthop.FaceID == packet.IncomingFaceID { + continue + } + // Exclude faces that have an in-record for this interest + // TODO: unclear where NFD dev guide specifies such behavior (if any) + if pitEntry.InRecords()[nexthop.FaceID] != nil { + continue + } + petLocalHops = append(petLocalHops, nexthop) + } + } + } - // Filter local PET nexthops. - allowedLocalNexthops := make([]*table.PetNextHop, 0, len(nextLocal)) - for _, nexthop := range nextLocal { - // Exclude incoming face - if nexthop.FaceID == packet.IncomingFaceID { - continue + localFacesOnly := incomingFace.Scope() != defn.Local && isLocalHop + + // Unicast Delivery making use of FIB -> FIBStrategy -> unicast delivery + if pipeline.isUnicast() { + core.Log.Trace(t, "Unicast pipeline", + "name", packet.Name, + "lookup", lookupName, + "petFound", petFound, + "localHop", isLocalHop, + "localFacesOnly", localFacesOnly, + ) + // Deliver to local faces if there exist such legal local faces + if len(petLocalHops) > 0 { + core.Log.Trace(t, "Local Egress captures the Interest", "name", packet.Name) + packet.EgressRouter = nil + // TODO: micro-strategy dispatch per PIT in-record + t.processOutgoingInterest(packet, pitEntry, petLocalHops[0].FaceID, incomingFace.FaceID()) } - // Exclude faces that have an in-record for this interest - // TODO: unclear where NFD dev guide specifies such behavior (if any) - if pitEntry.InRecords()[nexthop.FaceID] == nil { - allowedLocalNexthops = append(allowedLocalNexthops, nexthop) + + // FIB lookup to get the next network hops + nextNet := make([]StrategyCandidateHop, 0) + if len(packet.EgressRouter) > 0 { + for _, nextHop := range table.FibStrategyTable.FindNextHopsEnc(packet.EgressRouter) { + nextNet = append(nextNet, StrategyCandidateHop{ + HopEntry: nextHop, + EgressRouter: packet.EgressRouter, + }) + } + } else if petFound { + for _, er := range petEntry.EgressRouters { + for _, nextHop := range table.FibStrategyTable.FindNextHopsEnc(er) { + nextNet = append(nextNet, StrategyCandidateHop{ + HopEntry: nextHop, + EgressRouter: er, + }) + } + } } - } - // Filter network FIB nexthops. - allowedNetNexthops := make([]StrategyCandidateHop, 0, len(nextNet)) - for _, nexthop := range nextNet { - // Exclude incoming face - if nexthop.HopEntry.Nexthop == packet.IncomingFaceID { - continue + if len(nextNet) == 0 { + core.Log.Trace(t, "Unicast forwarding: no PET and no FIB nexthops", + "name", packet.Name, + "lookup", lookupName, + "pipeline", pipeline, + "isLocalHop", isLocalHop, + ) } - // Exclude non-local faces for localhop enforcement - if localFacesOnly { - if face := dispatch.GetFace(nexthop.HopEntry.Nexthop); face != nil && face.Scope() != defn.Local { + // Filter network FIB nexthops. + allowedNetNexthops := make([]StrategyCandidateHop, 0, len(nextNet)) + + for _, nexthop := range nextNet { + // Exclude incoming face + if nexthop.HopEntry.Nexthop == packet.IncomingFaceID { + continue + } + + // Exclude non-local faces for localhop enforcement + if localFacesOnly { + if face := dispatch.GetFace(nexthop.HopEntry.Nexthop); face != nil && face.Scope() != defn.Local { + continue + } + } + + // Exclude faces that have an in-record for this interest + // TODO: unclear where NFD dev guide specifies such behavior (if any) + if pitEntry.InRecords()[nexthop.HopEntry.Nexthop] != nil { continue } - } - // Exclude faces that have an in-record for this interest - // TODO: unclear where NFD dev guide specifies such behavior (if any) - if pitEntry.InRecords()[nexthop.HopEntry.Nexthop] == nil { allowedNetNexthops = append(allowedNetNexthops, nexthop) } - } - // If local choices exist, forward to local app face(s). - // For BIER interests, after local delivery clear the local bit and continue - // to network replication so the router can act as both BFER and BFR. - if len(allowedLocalNexthops) > 0 { - core.Log.Trace(t, "Local Egress captures the Interest", "name", packet.Name) - packet.EgressRouter = nil - // TODO: micro-strategy dispatch per PIT in-record - t.processOutgoingInterest(packet, pitEntry, allowedLocalNexthops[0].FaceID, incomingFace.FaceID()) - // If this is a BIER interest, clear our local bit and continue to network replication - if len(packet.Bier) > 0 && IsBierEnabled() { - bs := BierClone(packet.Bier) - BierClearBit(bs, CfgBierIndex()) - packet.Bier = bs - if BierIsZero(packet.Bier) { - return - } - // Fall through to network BIER replication below + if len(allowedNetNexthops) > 0 { + core.Log.Trace(t, "Unicast forward", + "name", packet.Name, + "allowedNet", len(allowedNetNexthops), + "localFacesOnly", localFacesOnly, + ) + // Forward using a unicast strategy from FibStrategyTable + // Below does a FibStrategyTable lookup, but we only have one strategy rn + // so we manually use bestRouteStrategy for testing + // strategyName := table.FibStrategyTable.FindStrategyEnc(interest.Name()) + // strategy := t.strategies[strategyName.Hash()] + bestRouteStrategy := t.strategies[defn.BEST_ROUTE_STRATEGY.Hash()] + strategy := bestRouteStrategy + strategy.AfterReceiveInterest(packet, pitEntry, incomingFace.FaceID(), allowedNetNexthops) } else { + // NACK? return } } - // Transit BFR: packet arrived with a BIER bit-string already set. - // Dispatch directly to the multicast pipeline — BIFT determines next hops, - // independent of the PET/FIB allowedNetNexthops list (which was intentionally - // left empty by twoPhaseLookup when hasBier=true). - if len(packet.Bier) > 0 { - t.processBierInterest(packet, pitEntry, incomingFace.FaceID()) - return - } - - if len(allowedNetNexthops) > 0 { - // BFIR: when this router is the ingress and sees a Sync group prefix - // (isMulticast=true) with >1 egress routers and BIER enabled, encode a - // bit-string. After this point packet.Bier is set for all BIER traffic. - if IsBierEnabled() && len(allowedNetER) > 1 && isMulticast { - bierBs := Bift.BuildBierBitString(allowedNetER) - if len(bierBs) > 0 { - core.Log.Trace(t, "BFIR: encoding BIER bit-string", "name", packet.Name, "egress-count", len(allowedNetER)) - packet.Bier = bierBs - t.processBierInterest(packet, pitEntry, incomingFace.FaceID()) - return + // Multicast delivery making use of BIFT -> MulticastStrategyTable -> multicast delivery + // Currently just overrides /localhop to a broadcast override and manual BIER code + if pipeline.isMulticast() { + multicastStrategyName := table.MulticastStrategyTable.FindStrategyEnc(lookupName) + core.Log.Trace(t, "Multicast pipeline", + "name", packet.Name, + "lookup", lookupName, + "petFound", petFound, + "localHop", isLocalHop, + "bier", len(packet.Bier), + "mcastStrategy", multicastStrategyName, + ) + deliveredToLocal := false + if len(petLocalHops) > 0 { + core.Log.Trace(t, "Local Egress captures the Interest", "name", packet.Name) + // In multicast, we deliver to all local faces unlike in unicast + for _, localHop := range petLocalHops { + packet.EgressRouter = nil + t.processOutgoingInterest(packet, pitEntry, localHop.FaceID, incomingFace.FaceID()) + deliveredToLocal = true } } - // Unicast pipeline: delegate to strategy. - strategy.AfterReceiveInterest(packet, pitEntry, incomingFace.FaceID(), allowedNetNexthops, allowedNetER) - } else { - // NACK? + strategy := t.strategies[multicastStrategyName.Hash()] + strategy.AfterReceiveMulticastInterest(packet, pitEntry, incomingFace.FaceID(), petEntry, deliveredToLocal) + return } } -// processBierInterest is the multicast forwarding pipeline for BIER Interests. -// It is called directly by processInterest (pipeline-level dispatch) rather than -// delegating to a forwarding strategy. This reflects the conceptual distinction -// between unicast and multicast delivery models. -func (t *Thread) processBierInterest(packet *defn.Pkt, pitEntry table.PitEntry, inFace uint64) { - bierReplicate(t, packet, pitEntry, inFace, func(pkt *defn.Pkt, pit table.PitEntry, nexthop, in uint64) bool { - return t.processOutgoingInterest(pkt, pit, nexthop, in) - }) -} - -// twoPhaseLookup resolves next-hops for an Interest. -// hasBier indicates that a BIER bit-string is already encoded on the packet; -// in that case the PET→egress mapping is skipped so a transit router does not -// re-query and potentially re-encode a new bit-string. Only the ingress BFIR -// performs the PET egress lookup. -func (t *Thread) twoPhaseLookup(lookupName enc.Name, egressRouter enc.Name, routerName enc.Name, routerNameSet bool, hasBier bool) ( - nextLocal []*table.PetNextHop, - nextNet []*table.FibNextHopEntry, - nextER []enc.Name, - isMulticast bool, +func (t *Thread) afterContentStoreHit( + packet *defn.Pkt, + pitEntry table.PitEntry, + inFace uint64, ) { - // If EgressRouter is set and matches this router, forward to local app faces via PET. - if len(egressRouter) > 0 { - if routerNameSet && egressRouter.Equal(routerName) { - petEntry, petFound := table.Pet.FindLongestPrefixEnc(lookupName) - if petFound { - for i := range petEntry.NextHops { - nextLocal = append(nextLocal, &petEntry.NextHops[i]) - } - } - return nextLocal, nextNet, nextER, false - } else { - for _, nextHop := range table.FibStrategyTable.FindNextHopsEnc(egressRouter) { - nextNet = append(nextNet, StrategyCandidateHop{ - HopEntry: nextHop, - EgressRouter: egressRouter, - }) - } - return nextLocal, nextNet, nextER, false - } - } + core.Log.Trace(t, "AfterContentStoreHit", "name", packet.Name, "faceid", inFace) - // No EgressRouter: try local PET first. - petEntry, petFound := table.Pet.FindLongestPrefixEnc(lookupName) - if !petFound { - return nextLocal, nextNet, nextER, false - } - for i := range petEntry.NextHops { - nextLocal = append(nextLocal, &petEntry.NextHops[i]) - } - - // When a BIER bit-string is already present the packet is on a transit router; - // the BIFT + bit-string fully determines forwarding — no PET egress needed. - if hasBier { - return nextLocal, nextNet, nextER, false - } - - // Ingress (BFIR) path: map PET egress routers to network next-hops. - for _, er := range petEntry.EgressRouters { - for _, nextHop := range table.FibStrategyTable.FindNextHopsEnc(er) { - nextNet = append(nextNet, StrategyCandidateHop{ - HopEntry: nextHop, - EgressRouter: er, - }) - } + nexthop := inFace + var pitToken []byte + if inRecord, ok := pitEntry.InRecords()[nexthop]; ok { + pitToken = inRecord.PitToken + pitEntry.RemoveInRecord(nexthop) } - isMulticast = petEntry.Multicast - return nextLocal, nextNet, nextER, isMulticast + t.processOutgoingData(packet, nexthop, pitToken, 0) } func (t *Thread) processOutgoingInterest( diff --git a/fw/mgmt/bift.go b/fw/mgmt/bift.go index 3b6bdb14..25af711a 100644 --- a/fw/mgmt/bift.go +++ b/fw/mgmt/bift.go @@ -8,8 +8,8 @@ package mgmt import ( + "github.com/named-data/ndnd/fw/bier" "github.com/named-data/ndnd/fw/core" - "github.com/named-data/ndnd/fw/fw" mgmt "github.com/named-data/ndnd/std/ndn/mgmt_2022" "github.com/named-data/ndnd/std/types/optional" ) @@ -73,7 +73,7 @@ func (b *BiftModule) registerRouter(interest *Interest) { } bfrId := params.Cost.GetOr(0) - fw.Bift.RegisterRouter(params.Name, int(bfrId)) + bier.Bift.RegisterRouter(params.Name, int(bfrId)) core.Log.Info(b, "Registered BIFT router", "name", params.Name, "bfrId", bfrId) b.manager.sendCtrlResp(interest, 200, "OK", &mgmt.ControlArgs{ @@ -88,7 +88,7 @@ func (b *BiftModule) rebuild(interest *Interest) { return } - fw.Bift.BuildFromFib() + bier.Bift.BuildFromFib() core.Log.Info(b, "Rebuilt BIFT from FIB") b.manager.sendCtrlResp(interest, 200, "OK", &mgmt.ControlArgs{}) diff --git a/fw/mgmt/pet.go b/fw/mgmt/pet.go index d9533907..d646785c 100644 --- a/fw/mgmt/pet.go +++ b/fw/mgmt/pet.go @@ -84,8 +84,7 @@ func (p *PETModule) addEgress(interest *Interest) { return } - // Flags bit 0 signals a Sync group (multicast) prefix announcement from ndn-dv. - multicast := params.Flags.GetOr(0)&1 != 0 + multicast := params.Multicast table.Pet.AddEgressEnc(params.Name, params.Egress.Name, multicast) core.Log.Info(p, "Added PET egress", "name", params.Name, "egress", params.Egress.Name, "multicast", multicast) diff --git a/fw/mgmt/strategy-choice.go b/fw/mgmt/strategy-choice.go index 492a6026..9b68d7ad 100644 --- a/fw/mgmt/strategy-choice.go +++ b/fw/mgmt/strategy-choice.go @@ -19,11 +19,28 @@ import ( // StrategyChoiceModule is the module that handles Strategy Choice Management. type StrategyChoiceModule struct { manager *Thread + kind strategyChoiceKind +} + +type strategyChoiceKind int + +const ( + strategyChoiceUnicast strategyChoiceKind = iota + strategyChoiceMulticast +) + +func NewStrategyChoiceModule(kind strategyChoiceKind) *StrategyChoiceModule { + return &StrategyChoiceModule{kind: kind} } // (AI GENERATED DESCRIPTION): Returns the identifier string `"mgmt-strategy"` for the StrategyChoiceModule. func (s *StrategyChoiceModule) String() string { - return "mgmt-strategy" + switch s.kind { + case strategyChoiceMulticast: + return "mgmt-multicast-strategy" + default: + return "mgmt-strategy" + } } // (AI GENERATED DESCRIPTION): Registers the specified manager by assigning it to the StrategyChoiceModule’s manager field. @@ -131,14 +148,26 @@ func (s *StrategyChoiceModule) set(interest *Interest) { params.Strategy.Name = params.Strategy.Name. Append(enc.NewVersionComponent(strategyVersion)) } - table.FibStrategyTable.SetStrategyEnc(params.Name, params.Strategy.Name) + + switch s.kind { + case strategyChoiceMulticast: + table.MulticastStrategyTable.SetStrategyEnc(params.Name, params.Strategy.Name) + core.Log.Info(s, "Set multicast strategy", "name", params.Name, "strategy", params.Strategy.Name) + + case strategyChoiceUnicast: + table.FibStrategyTable.SetStrategyEnc(params.Name, params.Strategy.Name) + core.Log.Info(s, "Set strategy", "name", params.Name, "strategy", params.Strategy.Name) + + default: + core.Log.Warn(s, "Unknown strategy choice kind", "kind", s.kind) + s.manager.sendCtrlResp(interest, 500, "Internal error", nil) + return + } s.manager.sendCtrlResp(interest, 200, "OK", &mgmt.ControlArgs{ Name: params.Name, Strategy: params.Strategy, }) - - core.Log.Info(s, "Set strategy", "name", params.Name, "strategy", params.Strategy.Name) } // (AI GENERATED DESCRIPTION): Unsets a strategy encoding for a given name by handling a control interest, validating its parameters, removing the strategy from the FIB strategy table, and replying with a 200 OK response. @@ -164,8 +193,18 @@ func (s *StrategyChoiceModule) unset(interest *Interest) { return } - table.FibStrategyTable.UnSetStrategyEnc(params.Name) - core.Log.Info(s, "Unset Strategy", "name", params.Name) + switch s.kind { + case strategyChoiceMulticast: + table.MulticastStrategyTable.UnSetStrategyEnc(params.Name) + core.Log.Info(s, "Unset multicast strategy", "name", params.Name) + case strategyChoiceUnicast: + table.FibStrategyTable.UnSetStrategyEnc(params.Name) + core.Log.Info(s, "Unset Strategy", "name", params.Name) + default: + core.Log.Warn(s, "Unknown strategy choice kind", "kind", s.kind) + s.manager.sendCtrlResp(interest, 500, "Internal error", nil) + return + } s.manager.sendCtrlResp(interest, 200, "OK", &mgmt.ControlArgs{Name: params.Name}) } @@ -179,7 +218,24 @@ func (s *StrategyChoiceModule) list(interest *Interest) { // Generate new dataset // TODO: For thread safety, we should lock the Strategy table from writes until we are done - entries := table.FibStrategyTable.GetAllForwardingStrategies() + var entries []table.FibStrategyEntry + name := LOCAL_PREFIX.Append( + enc.NewGenericComponent("strategy-choice"), + enc.NewGenericComponent("list"), + ) + switch s.kind { + case strategyChoiceMulticast: + entries = table.MulticastStrategyTable.GetAllForwardingStrategies() + name = LOCAL_PREFIX.Append( + enc.NewGenericComponent("multicast-strategy-choice"), + enc.NewGenericComponent("list"), + ) + case strategyChoiceUnicast: + entries = table.FibStrategyTable.GetAllForwardingStrategies() + default: + core.Log.Warn(s, "Unknown strategy choice kind", "kind", s.kind) + return + } choices := []*mgmt.StrategyChoice{} for _, fsEntry := range entries { choices = append(choices, &mgmt.StrategyChoice{ @@ -189,9 +245,5 @@ func (s *StrategyChoiceModule) list(interest *Interest) { } dataset := &mgmt.StrategyChoiceMsg{StrategyChoices: choices} - name := LOCAL_PREFIX.Append( - enc.NewGenericComponent("strategy-choice"), - enc.NewGenericComponent("list"), - ) s.manager.sendStatusDataset(interest, name, dataset.Encode()) } diff --git a/fw/mgmt/thread.go b/fw/mgmt/thread.go index 1f27f91c..6a444929 100644 --- a/fw/mgmt/thread.go +++ b/fw/mgmt/thread.go @@ -63,7 +63,8 @@ func MakeMgmtThread() *Thread { m.registerModule("bift", newBiftModule()) m.registerModule("rib", new(RIBModule)) m.registerModule("status", new(ForwarderStatusModule)) - m.registerModule("strategy-choice", new(StrategyChoiceModule)) + m.registerModule("strategy-choice", NewStrategyChoiceModule(strategyChoiceUnicast)) + m.registerModule("multicast-strategy-choice", NewStrategyChoiceModule(strategyChoiceMulticast)) // readvertisers run in the management thread for ease of // implementation, since they use the internal transport diff --git a/fw/table/config.go b/fw/table/config.go index 92b683c6..e2d7fda7 100644 --- a/fw/table/config.go +++ b/fw/table/config.go @@ -12,6 +12,7 @@ import ( "time" "github.com/named-data/ndnd/fw/core" + "github.com/named-data/ndnd/fw/defn" enc "github.com/named-data/ndnd/std/encoding" ) @@ -39,6 +40,20 @@ func Initialize() { core.Log.Fatal(nil, "Unknown FIB table algorithm", "algo", core.C.Tables.Fib.Algorithm) } + // Create Multicast strategy table + defaultMulticastStrategy := defn.BROADCAST_STRATEGY + if core.C.Fw.BierIndex >= 0 { + defaultMulticastStrategy = defn.BIER_STRATEGY + } + switch core.C.Tables.Fib.Algorithm { + case "hashtable": + newMulticastStrategyTableHashTable(core.C.Tables.Fib.Hashtable.M, defaultMulticastStrategy) + case "nametree": + newMulticastStrategyTableTree(defaultMulticastStrategy) + default: + core.Log.Fatal(nil, "Unknown FIB table algorithm", "algo", core.C.Tables.Fib.Algorithm) + } + // Create Network Region Table for _, region := range core.C.Tables.NetworkRegion.Regions { name, err := enc.NameFromStr(region) diff --git a/fw/table/fib-strategy-hashtable.go b/fw/table/fib-strategy-hashtable.go index afadb021..4f6a4f35 100644 --- a/fw/table/fib-strategy-hashtable.go +++ b/fw/table/fib-strategy-hashtable.go @@ -50,21 +50,34 @@ type FibStrategyHashTable struct { fibStrategyRWMutex sync.RWMutex } -// newFibStrategyTableHashTable creates a new FIB with the hash table algorithm. +// newStrategyTableHashTable creates a new strategy table with the hash table algorithm. // The argument m determines the virtual name length. -func newFibStrategyTableHashTable(m uint16) { - FibStrategyTable = new(FibStrategyHashTable) - fibStrategyTableHashTable := FibStrategyTable.(*FibStrategyHashTable) +func newStrategyTableHashTable(m uint16, defaultStrategy enc.Name) *FibStrategyHashTable { + table := new(FibStrategyHashTable) - fibStrategyTableHashTable.m = int(m) // Cast to int so that it's easy to pass to name.Prefix - fibStrategyTableHashTable.realTable = make(map[uint64]*baseFibStrategyEntry) - fibStrategyTableHashTable.virtTable = make(map[uint64]*virtualDetails) - fibStrategyTableHashTable.virtTableNames = make(map[uint64]map[string]int) + table.m = int(m) // Cast to int so that it's easy to pass to name.Prefix + table.realTable = make(map[uint64]*baseFibStrategyEntry) + table.virtTable = make(map[uint64]*virtualDetails) + table.virtTableNames = make(map[uint64]map[string]int) rtEntry := new(baseFibStrategyEntry) rtEntry.name = enc.Name{} - rtEntry.strategy = defn.DEFAULT_STRATEGY - fibStrategyTableHashTable.realTable[enc.Name{}.Hash()] = rtEntry + rtEntry.strategy = defaultStrategy + table.realTable[enc.Name{}.Hash()] = rtEntry + + return table +} + +// newFibStrategyTableHashTable creates a new FIB with the hash table algorithm. +// The argument m determines the virtual name length. +func newFibStrategyTableHashTable(m uint16) { + FibStrategyTable = newStrategyTableHashTable(m, defn.DEFAULT_STRATEGY) +} + +// newMulticastStrategyTableHashTable creates a new multicast strategy table +// with the hash table algorithm. The argument m determines the virtual name length. +func newMulticastStrategyTableHashTable(m uint16, defaultStrategy enc.Name) { + MulticastStrategyTable = newStrategyTableHashTable(m, defaultStrategy) } // findLongestPrefixMatch returns the entry corresponding to the longest diff --git a/fw/table/fib-strategy-tree.go b/fw/table/fib-strategy-tree.go index 6bc01411..7752c045 100644 --- a/fw/table/fib-strategy-tree.go +++ b/fw/table/fib-strategy-tree.go @@ -32,16 +32,29 @@ type FibStrategyTree struct { mutex sync.RWMutex } -// (AI GENERATED DESCRIPTION): Initializes the FibStrategyTable with a new FibStrategyTree whose root entry has an empty component, default strategy, and empty name. -func newFibStrategyTableTree() { - FibStrategyTable = new(FibStrategyTree) - tree := FibStrategyTable.(*FibStrategyTree) +// newStrategyTableTree initializes a new strategy table tree whose root entry +// has an empty component, default strategy, and empty name. +func newStrategyTableTree(defaultStrategy enc.Name) *FibStrategyTree { + tree := new(FibStrategyTree) // Root component will be empty tree.root = new(fibStrategyTreeEntry) tree.root.component = enc.Component{} - tree.root.strategy = defn.DEFAULT_STRATEGY + tree.root.strategy = defaultStrategy tree.root.name = enc.Name{} + + return tree +} + +// (AI GENERATED DESCRIPTION): Initializes the FibStrategyTable with a new FibStrategyTree whose root entry has an empty component, default strategy, and empty name. +func newFibStrategyTableTree() { + FibStrategyTable = newStrategyTableTree(defn.DEFAULT_STRATEGY) +} + +// newMulticastStrategyTableTree initializes the MulticastStrategyTable with +// a new FibStrategyTree whose root entry has an empty component and default strategy. +func newMulticastStrategyTableTree(defaultStrategy enc.Name) { + MulticastStrategyTable = newStrategyTableTree(defaultStrategy) } // findExactMatchEntry returns the entry corresponding to the exact match of diff --git a/fw/table/fib-strategy.go b/fw/table/fib-strategy.go index f80db8f1..61ba6260 100644 --- a/fw/table/fib-strategy.go +++ b/fw/table/fib-strategy.go @@ -50,6 +50,10 @@ type FibStrategy interface { // FibStrategy is a table containing FIB and Strategy entries for given prefixes. var FibStrategyTable FibStrategy +// MulticastStrategyTable is a table containing multicast strategy entries for given prefixes. +// It uses the same API as FibStrategyTable but only stores multicast strategies. +var MulticastStrategyTable FibStrategy + // Name returns the name associated with the baseFibStrategyEntry. func (e *baseFibStrategyEntry) Name() enc.Name { return e.name diff --git a/repo/tlv/definitions.go b/repo/tlv/definitions.go index 5ecc7e41..9285c98f 100644 --- a/repo/tlv/definitions.go +++ b/repo/tlv/definitions.go @@ -61,7 +61,7 @@ type BlobFetch struct { } type SecurityConfigObject struct { - //+field:sequence:[]byte:binary:[]byte + //+field:binary Schema []byte `tlv:"0x1A5"` //+field:sequence:[]byte:binary:[]byte Anchors [][]byte `tlv:"0x1BA"` diff --git a/repo/tlv/zz_generated.go b/repo/tlv/zz_generated.go index b94da29f..eed98344 100644 --- a/repo/tlv/zz_generated.go +++ b/repo/tlv/zz_generated.go @@ -1158,8 +1158,6 @@ func ParseBlobFetch(reader enc.WireView, ignoreCritical bool) (*BlobFetch, error type SecurityConfigObjectEncoder struct { Length uint - Schema_encoder struct { - } Anchors_subencoder []struct { } } @@ -1168,14 +1166,7 @@ type SecurityConfigObjectParsingContext struct { } func (encoder *SecurityConfigObjectEncoder) Init(value *SecurityConfigObject) { - { - encoder := &encoder.Schema_encoder - value := struct { - }{} - _ = encoder - _ = value - } { Anchors_l := len(value.Anchors) encoder.Anchors_subencoder = make([]struct { @@ -1198,22 +1189,10 @@ func (encoder *SecurityConfigObjectEncoder) Init(value *SecurityConfigObject) { } l := uint(0) - { - encoder := &encoder.Schema_encoder - value := struct { - Schema []byte - }{ - Schema: value.Schema, - } - { - if value.Schema != nil { - l += 3 - l += uint(enc.TLNum(len(value.Schema)).EncodingLength()) - l += uint(len(value.Schema)) - } - _ = encoder - _ = value - } + if value.Schema != nil { + l += 3 + l += uint(enc.TLNum(len(value.Schema)).EncodingLength()) + l += uint(len(value.Schema)) } if value.Anchors != nil { for seq_i, seq_v := range value.Anchors { @@ -1248,25 +1227,13 @@ func (encoder *SecurityConfigObjectEncoder) EncodeInto(value *SecurityConfigObje pos := uint(0) - { - encoder := &encoder.Schema_encoder - value := struct { - Schema []byte - }{ - Schema: value.Schema, - } - { - if value.Schema != nil { - buf[pos] = 253 - binary.BigEndian.PutUint16(buf[pos+1:], uint16(421)) - pos += 3 - pos += uint(enc.TLNum(len(value.Schema)).EncodeInto(buf[pos:])) - copy(buf[pos:], value.Schema) - pos += uint(len(value.Schema)) - } - _ = encoder - _ = value - } + if value.Schema != nil { + buf[pos] = 253 + binary.BigEndian.PutUint16(buf[pos+1:], uint16(421)) + pos += 3 + pos += uint(enc.TLNum(len(value.Schema)).EncodeInto(buf[pos:])) + copy(buf[pos:], value.Schema) + pos += uint(len(value.Schema)) } if value.Anchors != nil { for seq_i, seq_v := range value.Anchors { @@ -1384,7 +1351,7 @@ func (context *SecurityConfigObjectParsingContext) Parse(reader enc.WireView, ig value.Schema = nil } if !handled_Anchors && err == nil { - value.Anchors = nil + // sequence - skip } if err != nil { diff --git a/std/ndn/mgmt_2022/definitions.go b/std/ndn/mgmt_2022/definitions.go index fd7b71e9..c39efbbe 100644 --- a/std/ndn/mgmt_2022/definitions.go +++ b/std/ndn/mgmt_2022/definitions.go @@ -77,6 +77,8 @@ type ControlArgs struct { Flags optional.Optional[uint64] `tlv:"0x6c"` //+field:natural:optional Mask optional.Optional[uint64] `tlv:"0x70"` + //+field:bool + Multicast bool `tlv:"0x90"` //+field:struct:Strategy Strategy *Strategy `tlv:"0x6b"` //+field:natural:optional diff --git a/std/ndn/mgmt_2022/zz_generated.go b/std/ndn/mgmt_2022/zz_generated.go index 28e6626c..823607d4 100644 --- a/std/ndn/mgmt_2022/zz_generated.go +++ b/std/ndn/mgmt_2022/zz_generated.go @@ -251,6 +251,10 @@ func (encoder *ControlArgsEncoder) Init(value *ControlArgs) { l += 1 l += uint(1 + enc.Nat(optval).EncodingLength()) } + if value.Multicast { + l += 1 + l += 1 + } if value.Strategy != nil { l += 1 l += uint(enc.TLNum(encoder.Strategy_encoder.Length).EncodingLength()) @@ -379,6 +383,12 @@ func (encoder *ControlArgsEncoder) EncodeInto(value *ControlArgs, buf []byte) { pos += uint(1 + buf[pos]) } + if value.Multicast { + buf[pos] = byte(144) + pos += 1 + buf[pos] = byte(0) + pos += 1 + } if value.Strategy != nil { buf[pos] = byte(107) pos += 1 @@ -453,6 +463,7 @@ func (context *ControlArgsParsingContext) Parse(reader enc.WireView, ignoreCriti var handled_Count bool = false var handled_Flags bool = false var handled_Mask bool = false + var handled_Multicast bool = false var handled_Strategy bool = false var handled_ExpirationPeriod bool = false var handled_FacePersistency bool = false @@ -683,6 +694,13 @@ func (context *ControlArgsParsingContext) Parse(reader enc.WireView, ignoreCriti value.Mask.Set(optval) } } + case 144: + if true { + handled = true + handled_Multicast = true + value.Multicast = true + err = reader.Skip(int(l)) + } case 107: if true { handled = true @@ -855,6 +873,9 @@ func (context *ControlArgsParsingContext) Parse(reader enc.WireView, ignoreCriti if !handled_Mask && err == nil { value.Mask.Unset() } + if !handled_Multicast && err == nil { + value.Multicast = false + } if !handled_Strategy && err == nil { value.Strategy = nil } @@ -932,6 +953,7 @@ func (value *ControlArgs) ToDict() map[string]any { if optval, ok := value.Mask.Get(); ok { dict["Mask"] = optval } + dict["Multicast"] = value.Multicast if value.Strategy != nil { dict["Strategy"] = value.Strategy.ToDict() } @@ -1088,6 +1110,18 @@ func DictToControlArgs(dict map[string]any) (*ControlArgs, error) { if err != nil { return nil, err } + if vv, ok := dict["Multicast"]; ok { + if v, ok := vv.(bool); ok { + value.Multicast = v + } else { + err = enc.ErrIncompatibleType{Name: "Multicast", TypeNum: 144, ValType: "bool", Value: vv} + } + } else { + value.Multicast = false + } + if err != nil { + return nil, err + } if vv, ok := dict["Strategy"]; ok { if v, ok := vv.(*Strategy); ok { value.Strategy = v diff --git a/std/object/client_announce.go b/std/object/client_announce.go index 9d0660ff..6433347a 100644 --- a/std/object/client_announce.go +++ b/std/object/client_announce.go @@ -52,7 +52,7 @@ func (c *Client) announcePrefix_(args ndn.Announcement) { Cost: optional.Some(args.Cost), } if args.Multicast { - ctrlArgs.Flags = optional.Some(uint64(1)) + ctrlArgs.Multicast = true } _, err := mgmt.ExecServiceCmd( c.engine, true, "dv", "prefix", "announce",