/* * Copyright (c) 2005-2010 KOM – Multimedia Communications Lab * * This file is part of PeerfactSim.KOM. * * PeerfactSim.KOM is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * any later version. * * PeerfactSim.KOM is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with PeerfactSim.KOM. If not, see . * */ package de.tud.kom.p2psim.impl.topology; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; import de.tud.kom.p2psim.api.common.HostProperties; import de.tud.kom.p2psim.api.common.SimHost; import de.tud.kom.p2psim.api.linklayer.mac.MacAddress; import de.tud.kom.p2psim.api.linklayer.mac.MacLayer; import de.tud.kom.p2psim.api.linklayer.mac.PhyType; import de.tud.kom.p2psim.api.topology.Topology; import de.tud.kom.p2psim.api.topology.TopologyComponent; import de.tud.kom.p2psim.api.topology.movement.MovementModel; import de.tud.kom.p2psim.api.topology.placement.PlacementModel; import de.tud.kom.p2psim.api.topology.views.TopologyView; import de.tud.kom.p2psim.impl.simengine.Simulator; import de.tudarmstadt.maki.simonstrator.api.Event; import de.tudarmstadt.maki.simonstrator.api.EventHandler; import de.tudarmstadt.maki.simonstrator.api.Graphs; import de.tudarmstadt.maki.simonstrator.api.Host; import de.tudarmstadt.maki.simonstrator.api.Oracle; import de.tudarmstadt.maki.simonstrator.api.Randoms; import de.tudarmstadt.maki.simonstrator.api.Time; import de.tudarmstadt.maki.simonstrator.api.common.graph.Graph; import de.tudarmstadt.maki.simonstrator.api.common.graph.IEdge; import de.tudarmstadt.maki.simonstrator.api.common.graph.INode; import de.tudarmstadt.maki.simonstrator.api.common.graph.INodeID; import de.tudarmstadt.maki.simonstrator.api.component.ComponentNotAvailableException; import de.tudarmstadt.maki.simonstrator.api.component.network.NetInterface; import de.tudarmstadt.maki.simonstrator.api.component.network.NetworkComponent.NetInterfaceName; import de.tudarmstadt.maki.simonstrator.api.component.sensor.location.AttractionPoint; import de.tudarmstadt.maki.simonstrator.api.component.sensor.location.Location; import de.tudarmstadt.maki.simonstrator.api.component.sensor.location.LocationListener; import de.tudarmstadt.maki.simonstrator.api.component.sensor.location.LocationRequest; import de.tudarmstadt.maki.simonstrator.api.component.sis.SiSComponent; import de.tudarmstadt.maki.simonstrator.api.component.sis.SiSDataCallback; import de.tudarmstadt.maki.simonstrator.api.component.sis.SiSInfoProperties; import de.tudarmstadt.maki.simonstrator.api.component.sis.SiSInformationProvider.SiSProviderHandle; import de.tudarmstadt.maki.simonstrator.api.component.sis.exception.InformationNotAvailableException; import de.tudarmstadt.maki.simonstrator.api.component.sis.type.SiSTypes; import de.tudarmstadt.maki.simonstrator.api.component.sis.util.SiSTopologyProvider; import de.tudarmstadt.maki.simonstrator.api.component.topology.TopologyID; import de.tudarmstadt.maki.simonstrator.api.component.transport.ConnectivityListener; /** * Default implementation of a {@link TopologyComponent}. * * @author Bjoern Richerzhagen * @version 1.0, 29.02.2012 */ public class DefaultTopologyComponent implements TopologyComponent { protected static Random rnd = Randoms.getRandom(AttractionPoint.class); private SimHost host; private final PositionVector position; private Topology topology; private double currentMovementSpeed = -1; private Map openRequests = new LinkedHashMap(); private List listeners = new LinkedList<>(); private MovementModel movementModel; private PlacementModel placementModel; /** * Create a TopologyComponent for the current host. * * @param host * @param topology * @param movementModel */ public DefaultTopologyComponent(SimHost host, Topology topology, MovementModel movementModel, PlacementModel placementModel) { this.topology = topology; this.host = host; this.position = new PositionVector(0, 0); this.movementModel = movementModel; if (this.movementModel != null) { this.movementModel.addComponent(this); } this.placementModel = placementModel; if (this.placementModel != null) { this.placementModel.addComponent(this); } } @Override public void initialize() { /* * Set the component's initial position and notify listeners of the * Topology that this component is initialized. */ topology.addComponent(this); movementModel.placeComponent(this); if (placementModel != null) { /* * Legacy support for placement models. */ position.set(placementModel.place(this)); } try { final SiSComponent sis = host.getComponent(SiSComponent.class); sis.provide().nodeState(SiSTypes.PHY_LOCATION, new SiSDataCallback() { Set localID = INodeID .getSingleIDSet(getHost().getId()); @Override public Location getValue(INodeID nodeID, SiSProviderHandle providerHandle) throws InformationNotAvailableException { if (nodeID.equals(getHost().getId())) { return getLastLocation(); } else { throw new InformationNotAvailableException(); } } @Override public Set getObservedNodes() { return localID; } @Override public SiSInfoProperties getInfoProperties() { return new SiSInfoProperties(); } }); // Provide Underlay topology Event.scheduleImmediately(new EventHandler() { @Override public void eventOccurred(Object content, int type) { if (getHost().getLinkLayer().hasPhy(PhyType.WIFI)) { new SiSTopologyProvider(sis, SiSTypes.NEIGHBORS_WIFI, DefaultTopologyComponent.this, getTopologyID(NetInterfaceName.WIFI, true), DefaultTopologyComponent.class); } } }, null, 0); } catch (ComponentNotAvailableException e) { // OK } } @Override public void shutdown() { topology = null; host = null; movementModel = null; } @Override public SimHost getHost() { return host; } @Override public PositionVector getRealPosition() { return position; } @Override public Topology getTopology() { return topology; } /** * Access to the movement model * @return */ public MovementModel getMovementModel() { return movementModel; } @Override public double getMinMovementSpeed() { HostProperties properties = getHost().getProperties(); return properties.getMinMovementSpeed(); } @Override public double getMaxMovementSpeed() { HostProperties properties = getHost().getProperties(); return properties.getMaxMovementSpeed(); } private void calcRandomMovementSpeed() { double min_speed = getMinMovementSpeed(); double max_speed = getMaxMovementSpeed(); double value = rnd.nextDouble(); this.currentMovementSpeed = (value * (max_speed - min_speed)) + min_speed; } @Override public double getMovementSpeed() { if (currentMovementSpeed == -1) { calcRandomMovementSpeed(); } return this.currentMovementSpeed; } @Override public void setMovementSpeed(double speed) { this.currentMovementSpeed = speed; } @Override public Location getLastLocation() { /* * As we want to mimic real world behavior, the current position * snapshot is cloned to prevent information propagation due to Java. */ return position.clone(); } @Override public void updateCurrentLocation(Location location) { position.set(location); // notify "non-request" listeners for (LocationListener locationListener : listeners) { locationListener.onLocationChanged(getHost(), getLastLocation()); } } @Override public void setTargetAttractionPoint(AttractionPoint targetAttractionPoint) throws UnsupportedOperationException { movementModel.changeTargetLocation(this, targetAttractionPoint); } @Override public AttractionPoint getCurrentTargetAttractionPoint() { return movementModel.getTargetLocation(this); } @Override public Set getAllAttractionPoints() { return movementModel.getAllAttractionPoints(); } @Override public void requestLocationUpdates(LocationRequest request, LocationListener listener) { if (openRequests.containsKey(listener)) { throw new AssertionError( "This LocationListener is already in use."); } if (request == null) { /* * This listener wants to be triggered on EVERY position update, but * it does not want to request position updates. */ if (!listeners.contains(listener)) { listeners.add(listener); } } else { /* * Listener has its own request timing. */ LocationRequestImpl req = (LocationRequestImpl) request; openRequests.put(listener, req); req.immunizeAndStart(listener); } } @Override public void removeLocationUpdates(LocationListener listener) { listeners.remove(listener); LocationRequestImpl impl = openRequests.remove(listener); if (impl != null) { impl.cancel(listener); } } @Override public LocationRequest getLocationRequest() { return new LocationRequestImpl(); } /** * Update 15.03.16 added support for multiple listeners (however, frequency * etc. is immune after the first request is registered.) * * @author Bjoern Richerzhagen * @version 1.0, Mar 15, 2016 */ private class LocationRequestImpl implements LocationRequest, EventHandler { private boolean immune = false; private long interval = 1 * Simulator.MINUTE_UNIT; private int priority = LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY; private Location lastLocation = null; private List listeners = new LinkedList(); private int eventTypeSeq = 0; public LocationRequestImpl() { // nothing to do } protected void cancel(LocationListener listener) { boolean removed = listeners.remove(listener); if (listeners.isEmpty()) { // upcoming event is no longer valid! eventTypeSeq++; } assert removed; } protected void immunizeAndStart(LocationListener listener) { immune = true; assert interval > 0; if (listeners.isEmpty()) { // Only start once! lastLocation = null; Event.scheduleImmediately(this, null, eventTypeSeq); } else { // Fire each new listener at least once listener.onLocationChanged(getHost(), getLastLocation()); } listeners.add(listener); } @Override public void setInterval(long interval) { if (!immune) { this.interval = interval; } } @Override public void setPriority(int priority) { if (!immune) { this.priority = priority; } } @Override public void eventOccurred(Object content, int type) { if (eventTypeSeq != type) { /* * Discard invalid events caused when a client cancels updates * but reactivates the request within the update frequency * interval. In this case, the old events continue rescheduling * themselves. */ return; } if (!listeners.isEmpty()) { // Only reschedule, if at least one listener is ... listening Location newLoc = getLastLocation(); listeners.forEach((LocationListener listener) -> listener .onLocationChanged(getHost(), newLoc)); lastLocation = newLoc; Event.scheduleWithDelay(interval, this, null, eventTypeSeq); } } } /* * Methods for the Graph Interface */ /** * Graph views: static, as we use global knowledge and maintain one shared * graph (potentially with partitions!) */ private final static LinkedHashMap graphViews = new LinkedHashMap<>(); @Override public TopologyID getTopologyID(NetInterfaceName netName, boolean onlyOnline) { TopologyID id = TopologyID.getIdentifier( netName.toString() + (onlyOnline ? "-online" : "-all"), DefaultTopologyComponent.class); if (!this.graphViews.containsKey(id)) { this.graphViews.put(id, new LocalGraphView(netName, onlyOnline)); } return id; } @Override public TopologyID getTopologyID(NetInterfaceName netName, boolean onlyOnline, double range) { TopologyID id = TopologyID.getIdentifier( netName.toString() + (onlyOnline ? "-online" : "-all") + String.valueOf(range), DefaultTopologyComponent.class); if (!this.graphViews.containsKey(id)) { this.graphViews.put(id, new LocalGraphView(netName, onlyOnline, range)); } return id; } @Override public INode getNode(TopologyID identifier) { assert graphViews.containsKey(identifier); return graphViews.get(identifier).getOwnNode(host); } @Override public Set getNeighbors(TopologyID topologyIdentifier) { assert graphViews.containsKey(topologyIdentifier); return graphViews.get(topologyIdentifier).getNeighbors(host); } @Override public Graph getLocalView(TopologyID topologyIdentifier) { assert graphViews.containsKey(topologyIdentifier); return graphViews.get(topologyIdentifier).getLocalView(); } @Override public Iterable getTopologyIdentifiers() { return graphViews.keySet(); } /** * This is calculated based on global knowledge. It only registers as * {@link LocationListener}, if a range is specified by the Provider. * * @author Bjoern Richerzhagen * @version 1.0, May 13, 2015 */ private class LocalGraphView implements LocationListener, ConnectivityListener { /** * Marker: has there been any movement since the graph view was last * requested? If so: recalculate! Otherwise, we ignore this object to * not perform calculations if no one is interested... */ private final double distance; private final boolean isDistanceBased; private final NetInterfaceName medium; private final TopologyView topoView; private final boolean onlyOnline; private Graph currentView; private boolean isInvalid = true; private final PhyType phy; public LocalGraphView(NetInterfaceName medium, boolean onlyOnline) { this(medium, onlyOnline, -1); } public LocalGraphView(NetInterfaceName medium, boolean onlyOnline, double distance) { this.medium = medium; PhyType localPhy = null; for (PhyType currPhy : PhyType.values()) { if (currPhy.getNetInterfaceName() == medium && getTopology().getTopologyView(currPhy) != null) { localPhy = currPhy; break; } } phy = localPhy; assert localPhy != null; this.topoView = getTopology().getTopologyView(localPhy); this.distance = distance; this.onlyOnline = onlyOnline; this.isDistanceBased = (distance > 0); assert !isDistanceBased || phy.isBroadcastMedium(); if (phy.isBroadcastMedium() || onlyOnline) { for (Host host : Oracle.getAllHosts()) { if (phy.isBroadcastMedium()) { try { DefaultTopologyComponent dcomp = host.getComponent( DefaultTopologyComponent.class); dcomp.requestLocationUpdates(null, LocalGraphView.this); } catch (ComponentNotAvailableException e) { continue; } } if (onlyOnline) { if (host.getNetworkComponent() .getByName(medium) != null) { host.getNetworkComponent().getByName(medium) .addConnectivityListener( LocalGraphView.this); } } } } } private void recalculateLocalView() { if (!isInvalid) { /* * Graphs are invalidated (i) based on movement, IFF a range was * specified, (ii) based on online/offline events, IFF only * online hosts are to be considered. */ return; } /* * Calculate a complete global connectivity graph */ // Create new, empty graph currentView = Graphs.createGraph(); // Add all (online?) nodes for (MacLayer mac : topoView.getAllMacs()) { if (!onlyOnline || mac.isOnline()) { INode node = currentView.createNode(mac.getHost().getId()); node.setProperty(SiSTypes.PHY_LOCATION, topoView.getPosition(mac.getMacAddress()).clone()); currentView.addElement(node); } } if (isDistanceBased) { // Build neighbors solely based on an assumed range for (MacLayer mac : topoView.getAllMacs()) { // Fix Christoph Storm: // Do not take offline nodes into account, unless told to do // so... if (onlyOnline && !currentView .containsNode(mac.getHost().getId())) { continue; } // Consider all nodes as potential neighbors for (MacLayer neighborMac : topoView.getAllMacs()) { // create, but do NOT add the node object INode neighbor = currentView .createNode(neighborMac.getHost().getId()); // only online nodes (already in graph) if (!onlyOnline || currentView.containsNode(neighbor.getId())) { // Distance? if (topoView.getDistance(mac.getMacAddress(), neighborMac.getMacAddress()) <= distance) { IEdge edge = currentView.createEdge( mac.getHost().getId(), neighborMac.getHost().getId()); currentView.addElement(edge); } } } } } else { // Build neighborhoods based on underlay neighbors (1-hop) for (MacLayer mac : topoView.getAllMacs()) { // Fix Christoph Storm: // Do not take offline nodes into account, unless told to do // so... if (onlyOnline && !currentView .containsNode(mac.getHost().getId())) { continue; } // Rely on underlay for neighbors List neighbors = topoView .getNeighbors(mac.getMacAddress()); for (MacAddress neighborMac : neighbors) { // create, but do NOT add the node object INode neighbor = currentView.createNode( topoView.getMac(neighborMac).getHost().getId()); // only online nodes (already in graph) if (!onlyOnline || currentView.containsNode(neighbor.getId())) { IEdge edge = currentView.createEdge( mac.getHost().getId(), topoView.getMac(neighborMac).getHost() .getId()); currentView.addElement(edge); edge.setProperty(SiSTypes.PHY_DISTANCE, topoView.getDistance(mac.getMacAddress(), neighborMac)); } } } } isInvalid = false; } public INode getOwnNode(SimHost ownHost) { MacLayer mac = ownHost.getLinkLayer().getMac(phy); if (!onlyOnline || mac.isOnline()) { return currentView.createNode(ownHost.getId()); } return null; } public Set getNeighbors(SimHost ownHost) { recalculateLocalView(); INode ownNode = getOwnNode(ownHost); return currentView.getOutgoingEdges(ownNode.getId()); } /** * This is the global view, therefore we do not distinguish between * hosts. * * @return */ public Graph getLocalView() { recalculateLocalView(); return currentView; } @Override public void onLocationChanged(Host host, Location location) { this.isInvalid = true; } @Override public void wentOnline(Host host, NetInterface netInterface) { assert netInterface.getName() == medium; this.isInvalid = true; } @Override public void wentOffline(Host host, NetInterface netInterface) { assert netInterface.getName() == medium; this.isInvalid = true; } } @Override public String toString() { return "TopoComp: " + getHost().getId() + " at " + position.toString(); } }