From 6c73ef4c59194af769471336c112affb026293ab Mon Sep 17 00:00:00 2001 From: Karl Schrab Date: Thu, 24 Oct 2024 17:41:27 +0200 Subject: [PATCH] feat(sns): support multi-hop unicast for topological and geographical routing --- .../sns/ambassador/TransmissionSimulator.java | 167 ++++++++---------- .../fed/sns/model/AdhocTransmissionModel.java | 21 ++- .../model/SimpleAdhocTransmissionModel.java | 28 ++- .../SophisticatedAdhocTransmissionModel.java | 56 +++--- .../sns/model/AdhocTransmissionModelTest.java | 109 ++++++++---- 5 files changed, 224 insertions(+), 157 deletions(-) diff --git a/fed/mosaic-sns/src/main/java/org/eclipse/mosaic/fed/sns/ambassador/TransmissionSimulator.java b/fed/mosaic-sns/src/main/java/org/eclipse/mosaic/fed/sns/ambassador/TransmissionSimulator.java index 855972fab..5150ca48d 100644 --- a/fed/mosaic-sns/src/main/java/org/eclipse/mosaic/fed/sns/ambassador/TransmissionSimulator.java +++ b/fed/mosaic-sns/src/main/java/org/eclipse/mosaic/fed/sns/ambassador/TransmissionSimulator.java @@ -35,7 +35,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -93,23 +92,20 @@ Map preProcessInteraction(V2xMessageTransmission int switch (dac.getType()) { case AD_HOC_TOPOCAST: if (log.isDebugEnabled()) { - log.debug( - "Send v2xMessage.id={} from node={} as Topocast (singlehop) @time={}", + log.debug("Send v2xMessage.id={} from node={} as Topocast (singlehop) @time={}", interaction.getMessage().getId(), senderName, TIME.format(interaction.getTime()) ); } return sendMessageAsTopocast(senderName, dac); case AD_HOC_GEOCAST: if (log.isDebugEnabled()) { - log.debug( - "Send v2xMessage.id={} from={} as Geocast (geo routing) @time={}", + log.debug( "Send v2xMessage.id={} from={} as Geocast (geo routing) @time={}", interaction.getMessage().getId(), senderName, TIME.format(interaction.getTime()) ); } return sendMessageAsGeocast(senderName, dac); default: - log.debug( - "V2XMessage is not an ad hoc message. Skip this message. V2XMessage.id={}", + log.debug("V2XMessage is not an ad hoc message. Skip this message. V2XMessage.id={}", interaction.getMessage().getId() ); return null; @@ -137,69 +133,63 @@ private boolean isValidSender(String senderName) { } /** - * Simulates topolocically-scoped Unicast or Broadcast as direct singlehop transmission. - * (NO multi-hops are implemented by now) - *
    - *
  1. Verify if configured transmission can be send using the topocast logic - *
  2. Differentiate between Unicast and Broadcast - *
  3. Simulate transmission with one hop - *
+ * Simulates topolocically-scoped Unicast (singlehop or multihop transmissions) or Broadcast (only singlehop). * * @param senderName The Sender of the message. * @param dac {@link DestinationAddressContainer} containing information about the destination for the message. * @return a Map containing the summarized transmission results */ protected Map sendMessageAsTopocast(String senderName, DestinationAddressContainer dac) { - NetworkAddress destinationAddress = dac.getAddress(); - if (destinationAddress.isAnycast()) { // check for valid destination address - log.warn( - "The SNS only supports SingleHopBroadCasts or SingleHopUniCasts when using TopoCasts." - + " The given destination address {} is not valid. No message will be send.", - destinationAddress - ); - return null; - } - if (dac.getTimeToLive() != SINGLE_HOP_TTL) { // inform about dismissed TTL - log.debug("TTL {} will be dismissed and 1 will be used instead. For Topocast, SNS only supports SingleHopBroadCasts.", dac.getTimeToLive()); + final NetworkAddress destinationAddress = dac.getAddress(); + + if (destinationAddress.isBroadcast() && dac.getTimeToLive() != SINGLE_HOP_TTL) { + log.debug("SNS only supports single hop broadcasts. TTL {} will be dismissed and 1 will be used instead.", dac.getTimeToLive()); } + final TransmissionParameter transmissionParameter = new TransmissionParameter( + randomNumberGenerator, + config.singlehopDelay, + config.singlehopTransmission, + getTtl(dac) + ); // accumulate all potential receivers in direct communication range - SimulationNode sender = SimulationEntities.INSTANCE.getOnlineNode(senderName); - Map allPotentialReceivers; + final SimulationNode sender = SimulationEntities.INSTANCE.getOnlineNode(senderName); + if (destinationAddress.isBroadcast()) { // SingleHopBroadCast - allPotentialReceivers = getPotentialBroadcastReceivers(getTopocastDestinationArea(sender)); + final var allPotentialReceivers = getPotentialBroadcastReceivers(getTopocastDestinationArea(sender)); // remove sender as single radios could not transmit and receive at the same time allPotentialReceivers.remove(senderName); - } else { // SingleHopUniCast - allPotentialReceivers = getAddressedReceiver(destinationAddress, getTopocastDestinationArea(sender)); + log.debug("Addressed nodes in destination area={}", allPotentialReceivers); + // transmission via singlehop broadcast + return transmissionModel.simulateSinglehop( + senderName, allPotentialReceivers, transmissionParameter, SimulationEntities.INSTANCE.getAllOnlineNodes() + ); + } else if (destinationAddress.isUnicast()) { + final String destinationNodeId = IpResolver.getSingleton().reverseLookup(destinationAddress.getIPv4Address()); + final SimulationNode destination = SimulationEntities.INSTANCE.getOnlineNode(destinationNodeId); + + final boolean isInAreaOfSender = isNodeInArea(destination.getPosition(), getTopocastDestinationArea(sender)); + if (isInAreaOfSender) { + return transmissionModel.simulateSinglehop( + senderName, Map.of(destinationNodeId, destination), transmissionParameter, SimulationEntities.INSTANCE.getAllOnlineNodes() + ); + } else { + final TransmissionResult result = transmissionModel.simulateTopologicalUnicast( + senderName, destinationNodeId, destination, transmissionParameter, SimulationEntities.INSTANCE.getAllOnlineNodes() + ); + return Map.of(destinationNodeId, result); + } } - log.debug("Addressed nodes in destination area={}", allPotentialReceivers); - // perform actual transmission - TransmissionParameter transmissionParameter = new TransmissionParameter( - randomNumberGenerator, - config.singlehopDelay, - config.singlehopTransmission, - SINGLE_HOP_TTL - ); - return transmissionModel.simulateTopocast( - senderName, allPotentialReceivers, transmissionParameter, SimulationEntities.INSTANCE.getAllOnlineNodes() + log.warn(""" + The SNS only supports SingleHop BroadCasts or MultiHop UniCasts when using Topological routing." + The given destination address {} is not valid. No message will be send.""", destinationAddress ); + return Map.of(); } /** - * Simulates geocast routing transmission. - * (ONLY Broadcasts are implemented by now) - *
    - *
  1. Verify if configured transmission can be send using the topocast logic - *
  2. determine all potential receiver nodes in the destination area - * (including original sender due to re-broadcasting in geocast)
  3. - *
  4. simulate message transmission via - *
      - *
    1. simplified Multihop mode (possibly needed hops to reach destination not regarded) - *
    2. with more elaborated approaching and flooding modes - *
    - *
+ * Simulates geocast routing transmission, either broadcast or unicast. * * @param senderName The Sender of the message. * @param dac {@link DestinationAddressContainer} containing information about the destination for the message. @@ -207,31 +197,51 @@ protected Map sendMessageAsTopocast(String senderNam */ protected Map sendMessageAsGeocast(String senderName, DestinationAddressContainer dac) { if (dac.getGeoArea() == null) { - return Collections.EMPTY_MAP; + log.error("No target area given for Geographic routing. No message will be send."); + return Map.of(); } + final NetworkAddress destinationAddress = dac.getAddress(); + final Area destinationArea = dac.getGeoArea().toCartesian(); - Area destinationArea = dac.getGeoArea().toCartesian(); - Map allReceivers = getPotentialBroadcastReceivers(destinationArea); - log.debug("Addressed nodes in destination area={}", allReceivers); - - // get ttl value, this will be ignored for the simple transmission model - int ttl; - if (dac.getTimeToLive() == -1) { - ttl = config.maximumTtl; // ttl was null, which is interpreted as maximum + final Map allReceivers; + if (destinationAddress.isUnicast()) { + final String destinationNodeId = IpResolver.getSingleton().reverseLookup(destinationAddress.getIPv4Address()); + if (getPotentialBroadcastReceivers(destinationArea).containsKey(destinationNodeId)) { + allReceivers = Map.of(destinationNodeId, SimulationEntities.INSTANCE.getOnlineNode(destinationNodeId)); + } else { + return Map.of(); + } + } else if (destinationAddress.isBroadcast()){ + allReceivers = getPotentialBroadcastReceivers(destinationArea); + log.debug("Addressed nodes in destination area={}", allReceivers); } else { - ttl = Math.min(dac.getTimeToLive(), config.maximumTtl); // ttl can't be higher than maximumTtl + log.warn(""" + The SNS only supports BroadCasts or UniCasts when using geograpical routing." + The given destination address {} is not valid. No message will be send.""", destinationAddress + ); + return Map.of(); } - TransmissionParameter transmissionParameter = new TransmissionParameter( + + // get ttl value, this will be ignored for the simple transmission model + final TransmissionParameter transmissionParameter = new TransmissionParameter( randomNumberGenerator, config.singlehopDelay, config.singlehopTransmission, - ttl + getTtl(dac) ); return transmissionModel.simulateGeocast( senderName, allReceivers, transmissionParameter, SimulationEntities.INSTANCE.getAllOnlineNodes() ); } + private int getTtl(DestinationAddressContainer dac) { + if (dac.getTimeToLive() == -1) { + return config.maximumTtl; + } else { + return Math.min(dac.getTimeToLive(), config.maximumTtl); + } + } + private Area getTopocastDestinationArea(SimulationNode nodeData) { return new CartesianCircle(nodeData.getPosition(), nodeData.getRadius()); } @@ -242,7 +252,7 @@ private Area getTopocastDestinationArea(SimulationNode nodeData) * @param destinationArea destination area for transmission * @return a map containing the */ - private Map getPotentialBroadcastReceivers(Area destinationArea) { + private static Map getPotentialBroadcastReceivers(Area destinationArea) { return getEntitiesInArea(SimulationEntities.INSTANCE.getAllOnlineNodes(), destinationArea); } @@ -255,11 +265,9 @@ private Map getPotentialBroadcastReceivers(Area getEntitiesInArea( - Map relevantEntities, Area range) { - Map results = new HashMap<>(); - - for (Map.Entry entityEntry : relevantEntities.entrySet()) { + public static Map getEntitiesInArea(Map relevantEntities, Area range) { + final Map results = new HashMap<>(); + for (var entityEntry : relevantEntities.entrySet()) { if (range.contains(entityEntry.getValue().getPosition())) { results.put(entityEntry.getKey(), entityEntry.getValue()); } @@ -267,25 +275,6 @@ public static Map getEntitiesInArea( return results; } - /** - * Returns the addressed receiver, if it is known inside the destination area (more specific check compared to broadcast). - * Note: The resulting Map will always contain 0 or 1 elements. - * - * @param destinationAddress address of the potential receiver - * @param reachableArea area, that can be reached with one hop by the sender - * @return if receiver was found single element map, else empty map - */ - private Map getAddressedReceiver(NetworkAddress destinationAddress, Area reachableArea) { - final Map receiver = new HashMap<>(); - - final String destinationNodeId = IpResolver.getSingleton().reverseLookup(destinationAddress.getIPv4Address()); - SimulationNode entity = SimulationEntities.INSTANCE.getOnlineNode(destinationNodeId); - if (entity != null && isNodeInArea(entity.getPosition(), reachableArea)) { - receiver.put(destinationNodeId, entity); - } - return receiver; - } - private boolean isNodeInArea(CartesianPoint nodePosition, Area destinationArea) { if (nodePosition == null) { log.warn("position of the unit is null"); diff --git a/fed/mosaic-sns/src/main/java/org/eclipse/mosaic/fed/sns/model/AdhocTransmissionModel.java b/fed/mosaic-sns/src/main/java/org/eclipse/mosaic/fed/sns/model/AdhocTransmissionModel.java index 60e0cd4fa..e76de4ea9 100644 --- a/fed/mosaic-sns/src/main/java/org/eclipse/mosaic/fed/sns/model/AdhocTransmissionModel.java +++ b/fed/mosaic-sns/src/main/java/org/eclipse/mosaic/fed/sns/model/AdhocTransmissionModel.java @@ -50,7 +50,8 @@ TransmissionResult simulateTransmission(RandomNumberGenerator randomNumberGenera } /** - * Method to be implemented by extensions of {@link AdhocTransmissionModel}, calculating transmissions using topocast. + * Method to be implemented by extensions of {@link AdhocTransmissionModel}, calculating transmissions using topocast with + * only one single hop, either broadcast or unicast. * * @param senderName The sender of the transmission. * @param receivers The receivers of the transmission. @@ -58,11 +59,27 @@ TransmissionResult simulateTransmission(RandomNumberGenerator randomNumberGenera * @param currentNodes a reference to all currently online nodes * @return Map of the receivers and their transmission results. */ - public abstract Map simulateTopocast( + public abstract Map simulateSinglehop( String senderName, Map receivers, TransmissionParameter transmissionParameter, Map currentNodes ); + /** + * Method to be implemented by extensions of {@link AdhocTransmissionModel}, calculating transmissions using topocast + * with multiple hops, only unicast. + * + * @param senderName The sender of the transmission. + * @param receiverName The receivers of the unicast transmission. + * @param receiver The receiver node information of the unicast transmission. + * @param transmissionParameter Data class holding the maximumTtl, the {@link Delay} and the current map of simulated entities + * @param currentNodes a reference to all currently online nodes + * @return The transmission result to the single receiver. + */ + public abstract TransmissionResult simulateTopologicalUnicast( + String senderName, String receiverName, SimulationNode receiver, + TransmissionParameter transmissionParameter, Map currentNodes + ); + /** * Method to be implemented by extensions of {@link AdhocTransmissionModel}, calculating transmissions using geocast. * diff --git a/fed/mosaic-sns/src/main/java/org/eclipse/mosaic/fed/sns/model/SimpleAdhocTransmissionModel.java b/fed/mosaic-sns/src/main/java/org/eclipse/mosaic/fed/sns/model/SimpleAdhocTransmissionModel.java index 1ff9eddc0..6d8fb409f 100644 --- a/fed/mosaic-sns/src/main/java/org/eclipse/mosaic/fed/sns/model/SimpleAdhocTransmissionModel.java +++ b/fed/mosaic-sns/src/main/java/org/eclipse/mosaic/fed/sns/model/SimpleAdhocTransmissionModel.java @@ -46,18 +46,18 @@ public class SimpleAdhocTransmissionModel extends AdhocTransmissionModel { */ public CTransmission simpleMultihopTransmission = new CTransmission(); + /** - * Simulates a direct transmission between the sender and receivers. Note: If a single addressed receiver is used - * the 'receivers' map will only contain one entry as well as the results. + * Simulates a direct transmission between the sender and receivers. * * @param senderName The sender of the transmission. - * @param receivers The receivers of the transmission. + * @param receivers The receivers of the unicast transmission. * @param transmissionParameter Data class holding the maximumTtl, the {@link Delay} and the current map of simulated entities * @param currentNodes a reference to all currently online nodes - * @return Map of the receivers and their transmission results. + * @return The transmission result to the single receiver. */ @Override - public Map simulateTopocast( + public Map simulateSinglehop( String senderName, Map receivers, TransmissionParameter transmissionParameter, Map currentNodes) { return calculateTransmissions( @@ -66,6 +66,24 @@ public Map simulateTopocast( ); } + /** + * Simulates a multi-hop transmission between the sender and a single receiver. Uses the configured multi-hop delay as a simplification. + * No actual multi-hop is simulated (use {@link SophisticatedAdhocTransmissionModel} instead). + * + * @param senderName The sender of the transmission. + * @param receiverName The receiver name of the unicast transmission. + * @param receiver The receiver node information of the unicast transmission. + * @param transmissionParameter Data class holding the maximumTtl, the {@link Delay} and the current map of simulated entities + * @param currentNodes a reference to all currently online nodes + * @return The transmission result to the single receiver. + */ + @Override + public TransmissionResult simulateTopologicalUnicast( + String senderName, String receiverName, SimulationNode receiver, + TransmissionParameter transmissionParameter, Map currentNodes) { + return simulateTransmission(transmissionParameter.randomNumberGenerator, simpleMultihopDelay, simpleMultihopTransmission); + } + /** * For this simple model Geocast function the same way that Topocasts do, with the only difference being that a * approximating {@link Delay} is used. diff --git a/fed/mosaic-sns/src/main/java/org/eclipse/mosaic/fed/sns/model/SophisticatedAdhocTransmissionModel.java b/fed/mosaic-sns/src/main/java/org/eclipse/mosaic/fed/sns/model/SophisticatedAdhocTransmissionModel.java index 0e9f11bb7..dd9e3ddbd 100644 --- a/fed/mosaic-sns/src/main/java/org/eclipse/mosaic/fed/sns/model/SophisticatedAdhocTransmissionModel.java +++ b/fed/mosaic-sns/src/main/java/org/eclipse/mosaic/fed/sns/model/SophisticatedAdhocTransmissionModel.java @@ -35,8 +35,8 @@ public class SophisticatedAdhocTransmissionModel extends AdhocTransmissionModel private final static Logger log = LoggerFactory.getLogger(SimpleAdhocTransmissionModel.class); @Override - public Map simulateTopocast(String senderName, Map receivers, - TransmissionParameter transmissionParameter, Map currentNodes) { + public Map simulateSinglehop(String senderName, Map receivers, + TransmissionParameter transmissionParameter, Map currentNodes) { Map results = new HashMap<>(); receivers.forEach((receiverName, receiver) -> results .put(receiverName, simulateTransmission( @@ -45,45 +45,54 @@ public Map simulateTopocast(String senderName, Map currentNodes) { + final Map receivers = Map.of(receiverName, receiver); + final Tuple forwardingResult = forwarding(senderName, receivers, transmissionParameter, currentNodes); + return forwardingResult != null ? forwardingResult.getB() : new TransmissionResult(false); + } + @Override public Map simulateGeocast( String senderName, Map receivers, TransmissionParameter transmissionParameter, Map currentNodes) { - Map results; + // sender in destination area or can reach unit in destination area (flooding) if (canReachEntityInDestinationArea(senderName, receivers, currentNodes)) { - receivers.remove(senderName); // sender should never receive its own message - results = flooding(senderName, receivers, transmissionParameter, currentNodes); + return flooding(senderName, receivers, transmissionParameter, currentNodes); } else { // sender outside destination area (forwarding than flooding) - Tuple nodeInsideDestinationArea = - forwarding(senderName, receivers, transmissionParameter, currentNodes); + final Tuple nodeInsideDestinationArea = forwarding( + senderName, receivers, transmissionParameter, currentNodes + ); if (nodeInsideDestinationArea == null) { // if no node has been reached while forwarding GeoArea, set all results to failed Map unsuccessfulForwardAndFlood = new HashMap<>(); receivers.forEach((receiverName, receiver) -> unsuccessfulForwardAndFlood.put(receiverName, new TransmissionResult(false))); - results = unsuccessfulForwardAndFlood; log.info("Greedy Forwarding to destination area failed"); + return unsuccessfulForwardAndFlood; } else { - String newSenderName = nodeInsideDestinationArea.getA(); // get name of node that was reached using greedy forwarding - double forwardingDelay = nodeInsideDestinationArea.getB().delay; // get delay from node that was reached - int forwardingNumberOfHops = nodeInsideDestinationArea.getB().numberOfHops; // get number of hops of node that was reached - transmissionParameter.ttl -= forwardingNumberOfHops; // subtract number of hops from forwarding - results = flooding(newSenderName, receivers, transmissionParameter, currentNodes); + final String floodingInitiatorName = nodeInsideDestinationArea.getA(); // get name of node that was reached using greedy forwarding + final TransmissionResult forwardingTransmission = nodeInsideDestinationArea.getB(); // get hops/delay from node that was reached + transmissionParameter.ttl -= forwardingTransmission.numberOfHops; // subtract number of hops from forwarding + final Map results = flooding(floodingInitiatorName, receivers, transmissionParameter, currentNodes); // add delay that was accumulated during greedy forwarding to all nodes results.forEach((receiverName, transmissionResult) -> { - transmissionResult.delay += forwardingDelay; - transmissionResult.numberOfHops += forwardingNumberOfHops; + transmissionResult.delay += forwardingTransmission.delay; + transmissionResult.numberOfHops += forwardingTransmission.numberOfHops; }); + // set forwarding delay/hops for flooding initiator in destination area + results.put(floodingInitiatorName, forwardingTransmission); + return results; } } - - return results; } /** * The Flood Transmission simulates a flooding approach to using multihop messages. * A vehicle sends messages to every vehicle in range which in turn relay the message to all vehicles in their range. - * This algorithm is result orientated meaning that the the actual transmissions are not simulated. + * This algorithm is result orientated meaning that the actual transmissions are not simulated. * *
      * The transmission is also instant, the delay for the single steps get added at the end;
@@ -99,15 +108,16 @@ public Map simulateGeocast(
     private Map flooding(
             String senderName, Map receivers,
             TransmissionParameter transmissionParameter, Map currentNodes) {
-        Map results = new HashMap<>();
-        receivers.forEach((receiverName, receiver) -> results.put(receiverName, new TransmissionResult(false, 0)));
-
         // this map is used to represent all entities, that will be flooding
         Map floodingEntities = new HashMap<>();
         floodingEntities.put(senderName, currentNodes.get(senderName));
 
         // in the beginning this reflects all receivers except the sender
         Map receiversUnsatisfied = new HashMap<>(receivers);
+        receiversUnsatisfied.remove(senderName);
+
+        Map results = new HashMap<>();
+        receivers.forEach((receiverName, receiver) -> results.put(receiverName, new TransmissionResult(false, 0)));
 
         // this map holds all entities, that can be reached with a single hop
         Map entitiesInReach;
@@ -121,7 +131,7 @@ private Map flooding(
 
             // do this for all of the currently sending entities
             for (Map.Entry floodingEntityEntry : floodingEntities.entrySet()) {
-                CartesianArea singleHopReachArea = new CartesianCircle(
+                final CartesianArea singleHopReachArea = new CartesianCircle(
                         floodingEntityEntry.getValue().getPosition(),
                         floodingEntityEntry.getValue().getRadius()
                 );
@@ -130,7 +140,7 @@ private Map flooding(
 
                 // simulate transmission for unsatisfied receivers in reach
 
-                Map transmissionResults = new HashMap<>();
+                final Map transmissionResults = new HashMap<>();
                 for (Map.Entry entry : entitiesInReach.entrySet()) {
                     transmissionResults.put(
                             entry.getKey(),
diff --git a/fed/mosaic-sns/src/test/java/org/eclipse/mosaic/fed/sns/model/AdhocTransmissionModelTest.java b/fed/mosaic-sns/src/test/java/org/eclipse/mosaic/fed/sns/model/AdhocTransmissionModelTest.java
index dc4a72342..0573f7e45 100644
--- a/fed/mosaic-sns/src/test/java/org/eclipse/mosaic/fed/sns/model/AdhocTransmissionModelTest.java
+++ b/fed/mosaic-sns/src/test/java/org/eclipse/mosaic/fed/sns/model/AdhocTransmissionModelTest.java
@@ -42,9 +42,7 @@
 
 import java.util.Arrays;
 import java.util.HashMap;
-import java.util.HashSet;
 import java.util.Map;
-import java.util.Set;
 
 public class AdhocTransmissionModelTest {
 
@@ -54,7 +52,6 @@ public class AdhocTransmissionModelTest {
     private final RandomNumberGenerator rng = new DefaultRandomNumberGenerator(82937858189209L);
 
     private final Map allNodes = new HashMap<>();
-    private final Set nodesInRow = new HashSet<>();
     private final Map nodesRandomlyDistributed = new HashMap<>();
 
     @Rule
@@ -62,7 +59,6 @@ public class AdhocTransmissionModelTest {
 
     @Before
     public void setup() {
-
         simpleTransmissionModel = new SimpleAdhocTransmissionModel();
         ConstantDelay constantDelay = new ConstantDelay();
         constantDelay.delay = 1; // used to obtain single valued delays
@@ -81,7 +77,6 @@ public void setup() {
             when(newNode.getPosition()).thenReturn(GeoPoint.latLon(currentLatitude, currentLongitude).toCartesian());
             when(newNode.getRadius()).thenReturn(adhocRadius);
             allNodes.put("" + i, newNode);
-            nodesInRow.add("" + i);
         }
 
         double randomLatitude;
@@ -115,8 +110,9 @@ public void simulateSingleHopBroadCast_allUnitsReachable_noPacketLoss() {
 
         // RUN
         // use all entities as receivers
-        Map directTransmissionResults =
-                simpleTransmissionModel.simulateTopocast("0", allNodes, transmissionParameter, allNodes);
+        Map directTransmissionResults = simpleTransmissionModel.simulateSinglehop(
+                "0", allNodes, transmissionParameter, allNodes
+        );
 
         // ASSERT
         // unit without wifi capabilities can't receive transmission
@@ -133,16 +129,16 @@ public void simulateSingleHopBroadCast_someUnitsReachable_noPacketLoss() {
         CTransmission transmission = new CTransmission();
 
         // Note: ttl parameter doesn't matter for direct transmission, the SNS would log a warning if TTL is != 1
-        TransmissionParameter transmissionParameter = new TransmissionParameter(rng, simpleRandomDelay, transmission, 1
-        );
+        TransmissionParameter transmissionParameter = new TransmissionParameter(rng, simpleRandomDelay, transmission, 1);
 
         // RUN
         // increase range of sender
         when(allNodes.get("0").getRadius()).thenReturn(300d);
         // only use Receivers in range, this will be enforced by the transmission simulator
         Map receivers = getAllReceiversInRange("0");
-        Map directTransmissionResults =
-                simpleTransmissionModel.simulateTopocast("0", receivers, transmissionParameter, allNodes);
+        Map directTransmissionResults = simpleTransmissionModel.simulateSinglehop(
+                "0", receivers, transmissionParameter, allNodes
+        );
 
         // ASSERT
 
@@ -169,9 +165,9 @@ public void simulateGeoBroadcast_senderInDestinationArea_allReceiversReachable_n
 
         // RUN
         // use all randomly distributed nodes as receiver except sender
-        Map floodingTransmissionResults =
-                sophisticatedTransmissionModel
-                        .simulateGeocast("30", getAllRandomlyDistributedEntitiesRemoveSender("30"), transmissionParameter, allNodes);
+        Map floodingTransmissionResults = sophisticatedTransmissionModel.simulateGeocast(
+                "30", getAllRandomlyDistributedEntitiesRemoveSender("30"), transmissionParameter, allNodes
+        );
 
         // ASSERT
         // there should be results for every anticipated receiver
@@ -207,9 +203,9 @@ public void simulateGeoBroadcast_senderInDestinationArea_someReceiversReachable_
         // RUN
 
         // use all randomly distributed nodes as receiver
-        Map floodingTransmissionResults =
-                sophisticatedTransmissionModel
-                        .simulateGeocast("30", getAllRandomlyDistributedEntitiesRemoveSender("30"), transmissionParameter, allNodes);
+        Map floodingTransmissionResults = sophisticatedTransmissionModel.simulateGeocast(
+                "30", getAllRandomlyDistributedEntitiesRemoveSender("30"), transmissionParameter, allNodes
+        );
 
         // ASSERT
         // there should be results for every anticipated receiver
@@ -243,10 +239,7 @@ public void simulateGeoBroadcast_senderInDestinationArea_someReceiversReachable_
         // RUN
         // use all randomly distributed nodes as receiver
         Map floodingTransmissionResults = sophisticatedTransmissionModel.simulateGeocast(
-                "30",
-                getAllRandomlyDistributedEntitiesRemoveSender("30"),
-                transmissionParameter,
-                allNodes
+                "30", getAllRandomlyDistributedEntitiesRemoveSender("30"), transmissionParameter, allNodes
         );
 
         // ASSERT
@@ -277,8 +270,9 @@ public void floodingTransmissionRecognizesSenderNotInDestination() {
 
         // RUN
         try {
-            sophisticatedTransmissionModel
-                    .simulateGeocast("0", getAllRandomlyDistributedEntitiesRemoveSender("0"), transmissionParameter, allNodes);
+            sophisticatedTransmissionModel.simulateGeocast(
+                    "0", getAllRandomlyDistributedEntitiesRemoveSender("0"), transmissionParameter, allNodes
+            );
         } catch (RuntimeException e) {
             assertEquals("Sender has to be in destination area to use flooding as transmission model.", e.getMessage());
         }
@@ -296,10 +290,7 @@ public void simulateGeoBroadcast_senderOutsideDestination_allReceiversReachable_
         // RUN
         // use randomly distributed entities as receivers
         Map transmissionResult = sophisticatedTransmissionModel.simulateGeocast(
-                "0",
-                getAllRandomlyDistributedEntitiesRemoveSender("0"),
-                transmissionParameter,
-                allNodes
+                "0", getAllRandomlyDistributedEntitiesRemoveSender("0"), transmissionParameter, allNodes
         );
         // ASSERT
         assertNotNull(transmissionResult);
@@ -324,8 +315,9 @@ public void simulateSingleHopBroadCast_fullLoss() {
 
         // RUN
         // use all entities as receivers
-        Map directTransmissionResults =
-                simpleTransmissionModel.simulateTopocast("0", allNodes, transmissionParameter, allNodes);
+        Map directTransmissionResults = simpleTransmissionModel.simulateSinglehop(
+                "0", allNodes, transmissionParameter, allNodes
+        );
 
         // ASSERT
         // for the entity without wifi module there won't be any transmission attempts
@@ -337,20 +329,65 @@ public void simulateSingleHopBroadCast_fullLoss() {
     }
 
     @Test
-    public void simulateTopocast_addressingSingleReceiver() {
+    public void simulateSingleHopUnicast_success() {
         // SETUP
         TransmissionParameter transmissionParameter = generateTransmissionParameter_NoLoss(1);
         Map receiver = new HashMap<>();
         receiver.put("1", allNodes.get("1"));
 
         // RUN
-        Map directTransmissionResults =
-                simpleTransmissionModel.simulateTopocast("0", receiver, transmissionParameter, allNodes);
+        Map directTransmissionResults = simpleTransmissionModel.simulateSinglehop(
+                "0", receiver, transmissionParameter, allNodes
+        );
 
         // ASSERT
         assertTrue(directTransmissionResults.get("1").success);
     }
 
+    @Test
+    public void simulateMultiHopTopoUnicast_success() {
+        // SETUP
+        TransmissionParameter transmissionParameter = generateTransmissionParameter_NoLoss(10);
+
+        // RUN
+        TransmissionResult transmissionResult = sophisticatedTransmissionModel.simulateTopologicalUnicast(
+                "0", "5", allNodes.get("5"), transmissionParameter, allNodes
+        );
+
+        // ASSERT
+        assertTrue(transmissionResult.success);
+        assertEquals(5, transmissionResult.numberOfHops);
+    }
+
+    @Test
+    public void simulateMultiHopTopoUnicast_tooManyHopsRequired() {
+        // SETUP
+        TransmissionParameter transmissionParameter = generateTransmissionParameter_NoLoss(4); // max 4 hops, but target is 5 hops away
+
+        // RUN
+        TransmissionResult transmissionResult = sophisticatedTransmissionModel.simulateTopologicalUnicast(
+                "0", "5", allNodes.get("5"), transmissionParameter, allNodes
+        );
+
+        // ASSERT
+        assertFalse(transmissionResult.success);
+    }
+
+    @Test
+    public void simulateGeoUnicast_success() {
+        // SETUP
+        TransmissionParameter transmissionParameter = generateTransmissionParameter_NoLoss(10);
+
+        // RUN
+        Map transmissionResult = sophisticatedTransmissionModel.simulateGeocast(
+                "0", Map.of("5", allNodes.get("5")), transmissionParameter, allNodes
+        );
+
+        // ASSERT
+        assertTrue(transmissionResult.get("5").success);
+        assertEquals(5, transmissionResult.get("5").numberOfHops);
+    }
+
     @Test
     public void simulateGeoBroadcast_senderInDestinationArea_FullLoss() {
         // SETUP
@@ -359,10 +396,7 @@ public void simulateGeoBroadcast_senderInDestinationArea_FullLoss() {
         // RUN
         // use all randomly distributed nodes as receiver
         Map floodingTransmissionResults = sophisticatedTransmissionModel.simulateGeocast(
-                "30",
-                getAllRandomlyDistributedEntitiesRemoveSender("30"),
-                transmissionParameter,
-                allNodes
+                "30", getAllRandomlyDistributedEntitiesRemoveSender("30"), transmissionParameter, allNodes
         );
 
         // ASSERT
@@ -407,8 +441,7 @@ private double simulateTransmissionBetweenTwoEntities(TransmissionParameter tran
         receiver.put("1", allNodes.get("1"));
         int totalAttempts = 0;
         for (int i = 0; i < iterations; i++) {
-            tr = simpleTransmissionModel
-                    .simulateTopocast("0", receiver, transmissionParameter, allNodes).entrySet().iterator().next().getValue();
+            tr = simpleTransmissionModel.simulateSinglehop("0", receiver, transmissionParameter, allNodes).entrySet().iterator().next().getValue();
             totalAttempts += tr.attempts;
 
             assertTrue(tr.success);