Architecture¶
AntHocNet is implemented as one simulator-agnostic algorithm core with a thin adapter per simulator. The core never includes an NS-2 or NS-3 header; the adapters never reimplement routing logic.
+-------------------------------------------+
| core/ |
| (simulator-agnostic C++, no sim deps) |
| |
| PheromoneTable AntMessage (POD) |
| PheromoneEngine VisitedPath (vector) |
| AntHistoryTracker AntRouterLogic |
| -> RouteDecision |
| Ports: IClock IRng INeighborProvider |
| ITimerScheduler |
+----------------+--------------------------+
|
implements ports | returns RouteDecisions
|
+------------------------+------------------------+
| |
+-------v---------+ +---------v---------+
| ns2/ | | ns3/ |
| AntHocNetAgent | | RoutingProtocol : |
| : Agent | | Ipv4RoutingProtocol|
| AntPacketHeader | | AntHeader:ns3::Hdr |
| Ns2Clock/Ns2Rng | | Ns3Clock/Ns3Rng |
| + source patch | | + contrib module |
+-----------------+ +-------------------+
The core¶
Types and how they relate¶
AntRouterLogic is the only stateful entry point; everything else is either a
value type it moves around, a collaborator it owns, or a port it is handed.
classDiagram
class AntRouterLogic {
-NodeAddress address
-uint32 seqCounter
+onReceiveAnt(msg, prevHop) RouteDecision[]
+onDataPacket(dest, prevHop) RouteDecision[]
+onMaintenanceTick() RouteDecision[]
+reportNeighborLoss(n) RouteDecision[]
+reportTxFailure(next, dest) RouteDecision[]
}
class PheromoneTable {
-map regularByNeighbourDest
-map virtualByNeighbourDest
-set neighbours
+bestRegular(dest) double
+selectNextHop(dest, beta) NodeAddress
+setPheromoneRegular(dest, n, v)
+removePheromoneRegular(dest, n)
}
class PheromoneEngine {
+updateRegular(table, dest, obs)
+updateVirtual(table, hello)
+evaporateAll(table, dt)
+cleanNeighbor(table, n)
}
class AntHistoryTracker {
-deque insertionOrder
+seen(src, seq) bool
+remember(src, seq)
+clear()
}
class GenerationTracker {
+accept(src, seq, hops, time, firstHop) bool
+allowBroadcast(src, seq, max) bool
+clear()
}
class AntMessage {
+AntType type
+AntDirection direction
+NodeAddress src
+NodeAddress dst
+uint32 seqNum
+double timeStart
+double lifeAnt
+int broadcastBudget
+VisitedPath visited
+VisitedPath history
+HelloDest[] helloDests
+isForward() bool
}
class VisitedPath { +AntHop[] hops }
class AntHop { +NodeAddress node
+double time }
class HelloDest { +NodeAddress node
+double pheromone }
class AntMessageCodec {
+encode(msg) bytes
+decode(bytes) AntMessage
}
class RouteDecision {
+RouteAction action
+NodeAddress nextHop
+AntMessage message
}
class ILinkMetric {
<<interface>>
+pheromoneFor(observation) double
}
class ClassicMetric { +pheromoneFor(observation) double }
class IClock { <<interface>> +now() double }
class IRng { <<interface>> +uniform() double }
class ITimerScheduler { <<interface>> +schedule(delay, cb) }
class INeighborProvider { <<interface>> +neighbours() list }
AntRouterLogic *-- PheromoneTable
AntRouterLogic *-- PheromoneEngine
AntRouterLogic *-- AntHistoryTracker
AntRouterLogic *-- GenerationTracker
AntRouterLogic ..> RouteDecision : returns
AntRouterLogic ..> AntMessage : consumes / builds
AntRouterLogic o-- IClock
AntRouterLogic o-- IRng
AntRouterLogic o-- ITimerScheduler
AntRouterLogic o-- INeighborProvider
AntRouterLogic o-- ILinkMetric
PheromoneEngine ..> PheromoneTable : mutates
ILinkMetric <|.. ClassicMetric
AntMessage *-- VisitedPath
AntMessage *-- HelloDest
VisitedPath *-- AntHop
AntMessageCodec ..> AntMessage : encodes / decodes
RouteDecision *-- AntMessage
Three relationships carry the architecture:
- Composition (
*--) is owned state; the tables and trackers live and die with the router logic and are never shared between nodes. - Aggregation (
o--) is injected — every port and the link metric are handed in. This is the entire reason the core compiles without a simulator: there is no path fromAntRouterLogicto a clock, a random number, or a timer except through an interface someone else implements. ILinkMetricis the one open seam.ClassicMetric(the paper's Eq. 2) is the default; energy-aware and fuzzy-composite metrics (#145, #146) and the planned trust factor (#302) plug in here without the core learning about any of them. Metrics are stateless and const, so one process-lifetime instance is shared by every node.
Value types¶
AntMessage— a plain, copyable description of an ant: type, direction, src/dst,seqNum(32-bit), timing,broadcastBudget, the visited/history stacks, and hello adverts. The back-ant deposit state (prevHop/hops/pathTime/ pheromone) is transient — recomputed fromhistory, not carried (ADR-0009). This replaces the original header-residentAntTimeEntry**malloc'd arrays.VisitedPath—std::vector<AntHop>;AntHopis{node, time}.AntMessageCodec— canonical little-endian wire format, reused by both adapters and round-trip tested.
Stateful components¶
PheromoneTable— regular/virtual pheromone maps keyed by(neighbor, destination), the neighbour set, and per-table destination sets. Stochastic next-hop selection takes its randomness from an injectedIRng.PheromoneEngine— the evaporation/reinforcement math (evaporate,reinforce,updateRegular,updateVirtual,cleanNeighbor).AntHistoryTracker—(src, seqNum)duplicate detection, FIFO-capped.AntRouterLogic— owns the above plus the node address and sequence counter. It is pure:onReceiveAnt()/onDataPacket()returnRouteDecisions; they never touch a simulator.
Pluggable link metrics¶
ILinkMetric— the strategy that turns a backward ant'sLinkObservationinto a pheromone value.ClassicMetricis the canonical Eq.2 formula and the default; a metric is pure (const, observation-only), so it never reaches for the simulator, clock, or RNG.metrics::find/metrics::get(link_metric_registry.h) — the shared name → instance mapping both adapters resolve through, so"classic"means the same thing on NS-2 and NS-3. Lookup returns a non-owning pointer to a process-lifetime instance (metrics are stateless, so one instance is shared by every node, andAntRouterLogic's rawconst ILinkMetric*can never dangle). An unknown name is an error —findreturnsnullptr,getthrows — never a silent fall back to classic. With no selection at all nothing consults the registry andAntRouterLogicusesClassicMetricas before.
Ports¶
The adapters implement these so the core stays I/O-free:
| Port | NS-2 | NS-3 |
|---|---|---|
IClock |
Scheduler::instance().clock() |
Simulator::Now() |
IRng |
Random |
UniformRandomVariable |
INeighborProvider |
pheromone-table view | pheromone-table view |
ITimerScheduler |
Scheduler::schedule |
Simulator::Schedule |
Decision flow¶
Two entry points, both pure, both returning RouteDecisions the adapter
executes.
flowchart TB
IN(["incoming ant<br/><b>onReceiveAnt(msg, prevHop)</b>"]) --> DEDUP{"duplicate?"}
DEDUP -->|"reactive fwd ant +<br/>enableMultipath (default on)"| BAND{"within the acceptance band?<br/>a1 = 0.9, or a2 = 2.0 for a new<br/>first hop — on BOTH hops and<br/>travel time ([1] §3.1, #96/#177)"}
DEDUP -->|"all other ants,<br/>or gate off"| STRICT{"strict (src,seq)<br/>dedup"}
BAND -->|no| DROP1["Drop"]
STRICT -->|"seen"| DROP1
BAND -->|"yes — admit, so several<br/>good paths get laid down"| LEARN
STRICT -->|"fresh"| LEARN["learn prevHop as neighbour"]
LEARN --> KIND{"ant type / direction"}
KIND -->|hello| HELLO["update <b>virtual</b> table<br/>consume (never re-forwarded)"]
KIND -->|linkfail| LFN["apply to regular table,<br/>propagate unless our best survives"]
KIND -->|"forward ant"| FWD["stamp self"]
FWD --> ISDST{"dst == self?"}
ISDST -->|yes| SPAWN["spawn <b>back ant</b><br/>(direction = Down)"] --> UNI1["Unicast"]
ISDST -->|no| NEXT{"next hop known?"}
NEXT -->|yes| UNI2["Unicast"]
NEXT -->|no| BC["Broadcast<br/>(bounded per type)"]
KIND -->|"back ant"| REIN["<b>reinforce</b> the travelled link<br/>— the only pheromone write"]
REIN --> BDST{"dst == self?"}
BDST -->|yes| DELIV["Deliver<br/>(flush the pending queue)"]
BDST -->|no| UNI3["Unicast — advance one hop"]
DATA(["local data<br/><b>onDataPacket(dst)</b>"]) --> ROUTE{"route known?"}
ROUTE -->|yes| UNI4["Unicast"]
ROUTE -->|no| QUEUE["Queue<br/>+ reactive forward ant (Broadcast)<br/>≤1 per dest per ReactiveRetryInterval"]
style REIN fill:#fff3d4,stroke:#c48f00,stroke-width:2px
style HELLO fill:#eef,stroke:#5b4fc4
style DELIV fill:#e2f0ed,stroke:#0f7f70
style IN fill:#e2f0ed,stroke:#0f7f70,stroke-width:2px
style DATA fill:#e2f0ed,stroke:#0f7f70,stroke-width:2px
RouteDecision { action, nextHop, message } with
action ∈ {Unicast, Broadcast, Queue, Deliver, Drop, None}. The adapter maps
each action onto its simulator (schedule a send, enqueue, deliver to the local
transport, or drop).
Adapter responsibilities¶
The adapters do only what is intrinsically simulator-specific:
- convert packet headers ⇄
AntMessage; - carry out
RouteDecisions (send/queue/deliver/drop); - own the periodic timers (hello, proactive, maintenance);
- hold the pending-packet queue and, for NS-2, the link-failure callback.