/*
* 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.linklayer.mac;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
import de.tud.kom.p2psim.api.analyzer.LinklayerAnalyzer;
import de.tud.kom.p2psim.api.analyzer.MessageAnalyzer;
import de.tud.kom.p2psim.api.analyzer.MessageAnalyzer.Reason;
import de.tud.kom.p2psim.api.common.SimHost;
import de.tud.kom.p2psim.api.energy.ComponentType;
import de.tud.kom.p2psim.api.energy.EnergyCommunicationComponent;
import de.tud.kom.p2psim.api.energy.EnergyEventListener;
import de.tud.kom.p2psim.api.linklayer.LinkLayerMessage;
import de.tud.kom.p2psim.api.linklayer.LinkMessageEvent;
import de.tud.kom.p2psim.api.linklayer.LinkMessageListener;
import de.tud.kom.p2psim.api.linklayer.mac.Link;
import de.tud.kom.p2psim.api.linklayer.mac.MacAddress;
import de.tud.kom.p2psim.api.linklayer.mac.MacEventInformation;
import de.tud.kom.p2psim.api.linklayer.mac.MacLayer;
import de.tud.kom.p2psim.api.linklayer.mac.PhyType;
import de.tud.kom.p2psim.api.network.BandwidthImpl;
import de.tud.kom.p2psim.api.scenario.ConfigurationException;
import de.tud.kom.p2psim.api.topology.views.TopologyView;
import de.tud.kom.p2psim.impl.linklayer.ModularLinkLayer;
import de.tud.kom.p2psim.impl.network.BandwidthEstimator;
import de.tud.kom.p2psim.impl.util.LiveMonitoring;
import de.tud.kom.p2psim.impl.util.LiveMonitoring.ProgressValue;
import de.tudarmstadt.maki.simonstrator.api.Event;
import de.tudarmstadt.maki.simonstrator.api.Message;
import de.tudarmstadt.maki.simonstrator.api.Monitor;
import de.tudarmstadt.maki.simonstrator.api.Randoms;
import de.tudarmstadt.maki.simonstrator.api.Time;
import de.tudarmstadt.maki.simonstrator.api.component.core.MonitorComponent.AnalyzerNotAvailableException;
import de.tudarmstadt.maki.simonstrator.api.component.network.NetID;
/**
* Basic implementation of a MacLayer running on a PHY-CommunicationComponent.
* As with the {@link ModularLinkLayer} you should extend this class to add a
* more advanced MAC to ensure basic functionality and consistent behavior. For
* this reason, some methods in this class are marked as final...
*
* If a MAC wants to implement a protocol (ie. send messages to other MACs
* without triggering a deliver to higher layers) it can do so by implementing a
* corresponding Message type and/or {@link MacEventInformation}. A Message
* is only delivered to the higher layers if notifyLinkLayer is called.
*
* This Layer takes care of energy consumption, as long as the sendUnicast and
* sendBroadcast-methods are used to dispatch all messages (also the
* control-messages, if a protocol is implemented)
*
* @author Bjoern Richerzhagen
* @version 1.0, 21.02.2012
*/
public abstract class AbstractMacLayer implements MacLayer {
public static enum DropReason {
LINK_DROP, QUEUE_FULL, QUEUE_TIMEOUT, NO_LINK, SENDING_MAC_OFFLINE, RECEIVING_MAC_OFFLINE
}
/**
* The PHY we operate on
*/
private PhyType phy;
/**
* The {@link TopologyView} we operate on
*/
private TopologyView topoView;
/**
* Our own Host
*/
private SimHost host;
/**
* Our own MacAddress on this PHY
*/
private MacAddress macAddress;
/**
* Our own IP used by the NetLayer on this PHY. Not available during initialization.
*/
private NetID netID;
/**
* Each MAC has a Message queue for outgoing messages
*/
private LinkedList queue;
/**
* The LinkLayer registers itself as a MessageListener
*/
private LinkMessageListener messageListener;
/**
* Number of retransmissions before the message is dropped.
* provideErrorControl must be true.
*/
private final int maxRetransmissions;
/**
* If this is set to true, the MAC will request broken packets
* maxRetransmission times.
*/
private final boolean enableErrorControl;
/**
*
*/
private boolean isOnline;
/**
* The maximum length of the outgoing queue (if 0, queue has no limit)
*/
private final int maxQueueLength;
/**
* The maximum time a message is kept in the queue before it is dropped (if
* 0, there is no limit)
*/
private final long maxTimeInQueue;
/**
* An {@link EnergyCommunicationComponent} for this MAC
*/
private EnergyCommunicationComponent energyComponent;
/**
* Contains the configured bandwidth for this MAC
*/
protected final BandwidthImpl maximumBandwidth;
/**
* Returns an approximation of the current bandwidth
*/
protected final BandwidthEstimator currentBandwidth;
private final Random random = Randoms.getRandom(AbstractMacLayer.class);
/*
* Analyzing
*/
public static boolean[] _analyzersInitialized = new boolean[PhyType
.values().length];
public static MacMessagesDropReasonProgress[] _dropReasonAnalyzer = new MacMessagesDropReasonProgress[PhyType
.values().length];
public static final int MESSAGE_DROPPED = 1;
public static final int MESSAGE_SENT = 2;
public static final int MESSAGE_RECEIVED = 3;
public static final int MESSAGE_AT_SUBNET = 4;
/**
* Create a new MacLayer without bounds on the outgoing message queue (ie.
* endless size, no limit on the waiting time).
*
* @param ownMacAddress
* @param phy
* @param maxRetransmissions
* before the message is dropped. note: Broadcasts are never
* retransmitted. Set this to zero to enable ErrorControl at the
* MAC
* @param bandwidth
* the maximum BW of this MAC (i.e. the BW that would be achieved
* if messages are not queued)
*/
public AbstractMacLayer(SimHost host, MacAddress ownMacAddress,
PhyType phy, int maxRetransmissions, BandwidthImpl bandwidth) {
this(host, ownMacAddress, phy, 0, 0, maxRetransmissions, bandwidth);
}
/**
* Create a new MacLayer with a bounded outgoing queue (max length) and a
* timeout for messages in said queue.
*
* @param ownMacAddress
* @param phy
* @param maxQueueLength
* if 0, the queue is not limited
* @param maxTimeInQueue
* if 0, there is no timeout for messages in the queue
* @param maxRetransmissions
* before the message is dropped. note: Broadcasts are never
* retransmitted
* @param bandwidth
* the maximum BW of this MAC (i.e. the BW that would be achieved
* if messages are not queued)
*/
public AbstractMacLayer(SimHost host, MacAddress ownMacAddress,
PhyType phy,
int maxQueueLength, long maxTimeInQueue, int maxRetransmissions,
BandwidthImpl bandwidth) {
this.host = host;
this.enableErrorControl = maxRetransmissions > 0;
this.macAddress = ownMacAddress;
this.phy = phy;
this.maxQueueLength = maxQueueLength;
this.maxTimeInQueue = maxTimeInQueue;
this.maxRetransmissions = maxRetransmissions;
this.maximumBandwidth = bandwidth;
// ensure, that max and current are NOT the same object instance!
this.currentBandwidth = new BandwidthEstimator(5); // 5s historical data
this.queue = new LinkedList();
if (!_analyzersInitialized[phy.ordinal()]) {
_dropReasonAnalyzer[phy.ordinal()] = new MacMessagesDropReasonProgress();
LiveMonitoring.addProgressValueIfNotThere(_dropReasonAnalyzer[phy
.ordinal()]);
_analyzersInitialized[phy.ordinal()] = true;
}
}
@Override
public void initialize() throws ConfigurationException {
/*
* fetch an energy-component if one is present
*/
if (host.getEnergyModel() != null) {
List energyComponents = host
.getEnergyModel().getComponents(
ComponentType.COMMUNICATION,
EnergyCommunicationComponent.class);
for (EnergyCommunicationComponent comp : energyComponents) {
if (comp.getPhyType().equals(phy)) {
energyComponent = comp;
}
}
}
if (energyComponent == null) {
// Log.warn("The MAC-Layer did not find an Energy-Component for "
// + phy.toString()
// +
// ". This is fine, if you did not intend to measure energy consumption. Otherwise, please check your configuration!");
energyComponent = new EnergyComponentStub(phy);
}
/*
* Fetch a TopologyView for this PHY
*/
if (host.getTopologyComponent() == null
|| host.getTopologyComponent().getTopology() == null) {
throw new ConfigurationException(
"In order to use the LinkLayer you have to specify a Topology as a seperate component of your hosts.");
}
topoView = host.getTopologyComponent().getTopology()
.getTopologyView(phy);
if (topoView == null) {
throw new ConfigurationException(
"There is no TopologyView for the PHY " + phy.toString()
+ " configured!");
}
}
@Override
public void shutdown() {
throw new AssertionError("You are not supposed to shutdown a MAC.");
}
@Override
public final TopologyView getTopologyView() {
return topoView;
}
/**
* Use this component to account for energy consumption. This is safe to
* use, as it is always backed by a stub - it is never null.
*
* @return
*/
@Override
public EnergyCommunicationComponent getEnergyComponent() {
return energyComponent;
}
/**
* Current size of the outgoing Queue
*
* @return
*/
protected final int getQueueSize() {
return queue.size();
}
/**
* Maximum length of the outgoing queue, if equal to zero there is no limit
*
* @return
*/
protected final int getMaxQueueLength() {
return maxQueueLength;
}
/**
* Gets the maximal retransmissions.
*
* @return the maximal retransmissions.
*/
public final int getMaxRetransmissions() {
return maxRetransmissions;
}
/**
* Maximum time a message is kept in the outgoing queue. If equal to zero
* there is no limit.
*
* @return
*/
protected final long getMaxTimeInQueue() {
return maxTimeInQueue;
}
/**
* Retrieves and removes the first element in the Queue, after all
* entries that already timed out are deleted.
*
* @return
*/
protected final QueueEntry getQueueHead() {
removeOutdatedQueueEntries();
return queue.poll();
}
/**
* Removes all messages that are already waiting longer than maxTimeInQueue,
* notifying messageDropped() for each dropped message. The reason will be
* QUEUE_TIMEOUT in this case.
*
*/
private void removeOutdatedQueueEntries() {
if (maxTimeInQueue == 0) {
return;
}
long currentTime = Time.getCurrentTime();
QueueEntry peek = queue.peek();
while (peek != null
&& peek.getTimeEntered() + maxTimeInQueue < currentTime) {
queue.poll();
messageDropped(DropReason.QUEUE_TIMEOUT, peek.getMessage());
peek = queue.peek();
}
}
/**
* This is called whenever a drop occurs within the MAC/PHY. A great
* opportunity to add an Analyzer or some error handling for higher layers.
*
* Please note, that this might happen asynchronously - the messages are not
* removed from the queue the second their timeout expires but instead as
* soon as a new send() or a new getQueueHead() is issued.
*
* @param reason
* @param msg
*/
protected void messageDropped(DropReason reason, Message msg) {
ModularLinkLayer._linkDropped++;
_dropReasonAnalyzer[phy.ordinal()].increment(reason);
// System.out.println(Simulator.getFormattedTime(Simulator
// .getCurrentTime())
// + " LinkLayer DROP "
// + reason.toString()
// + " of " + msg.toString());
if (msg instanceof LinkLayerMessage) {
_linkMsgEvent((LinkLayerMessage) msg, getHost(),
MessageAnalyzer.Reason.DROP);
}
}
/**
* Notification: a new Entry was added to the outgoing queue.
*/
abstract protected void handleNewQueueEntry();
/**
* Notification: a message arrived!
*
* @param message
* the message itself
* @param info
* additional eventInformation
*/
abstract protected void handleReceivedMessage(MacEventInformation eventInfo);
/**
* This is to be called by the MAC if a Message should be passed to upper
* layers (ie. it is no MAC_ctrl-Message)
*
* @param eventInfo
*/
protected final void notifyLinkLayer(LinkMessageEvent eventInfo) {
if (this.isOnline()) {
messageListener.messageArrived(eventInfo);
}
}
/**
* Send a Message to the MAC on the receiver-side (event scheduling).
*
* @param receiver
* @param eventInformation
* @param delay
* time it takes the message to reach the receiver
* @param dropped
* receive-events are also used in the case of a message being
* dropped. This will enable a persistent callback on the sending
* mac as soon as the message is or would be received.
*/
protected final void scheduleReceive(MacLayer receiver,
MacEventInformation eventInformation, long delay, boolean dropped) {
// Event
assert delay > 0 : "Delay is equal to or less than 0!";
if (dropped) {
Event.scheduleWithDelay(delay, receiver, eventInformation,
MESSAGE_DROPPED);
} else {
Event.scheduleWithDelay(delay, receiver, eventInformation,
MESSAGE_RECEIVED);
}
}
protected void scheduleSendDone(long timeUntilSendDone) {
// not used in this MAC
}
/**
* Use this method to dispatch a broadcast message in your MAC-Layer. It
* will take care of energy consumption and scheduling.
*
* @param eventInfo
* an implementation of {@link MacEventInformation} containing
* information about the message to send as well as the message
* itself.
*/
protected final long sendBroadcast(MacEventInformation eventInfo) {
List txNeighbors = getTopologyView().getNeighbors(
getMacAddress());
/*
* For each Link we have to check whether the message arrives or not.
* There are NO retransmissions of broadcasts, as there are no ACKs of
* broadcast either.
*/
Message msg = eventInfo.getMessage();
long timeToSend = -1;
for (MacAddress receiver : txNeighbors) {
Link l = getTopologyView()
.getLinkBetween(getMacAddress(), receiver);
assert !receiver.equals(getMacAddress());
assert l.isConnected();
/*
* Broadcasts do not depend on an ACK or CTS by a receiver, so we do
* not care about the link back to the sender.
*/
long thisTimeToSend = tryToSend(eventInfo, l, null);
if (timeToSend == -1) {
timeToSend = thisTimeToSend;
}
if (timeToSend != thisTimeToSend) {
throw new AssertionError(
"Inconsitency in the MAC: a broadcast timeToSend is not constant across all links!");
}
}
if (timeToSend == -1) {
/*
* We have no neighbors, but we still sent the message - get the
* Broadcast-BW and account the corresponding energy consumption.
*/
Link selfLink = getTopologyView().getLinkBetween(getMacAddress(),
getMacAddress());
timeToSend = getUploadTime(msg, selfLink, true);
scheduleReceive(this, eventInfo, timeToSend, true);
}
if (eventInfo.getMessage() instanceof LinkLayerMessage) {
_linkMsgEvent((LinkLayerMessage) eventInfo.getMessage(), getHost(),
MessageAnalyzer.Reason.SEND);
}
getEnergyComponent().send(timeToSend, msg, true);
return timeToSend;
}
/**
* Send an unicast message
*
* @param toSend
*/
protected final long sendUnicast(MacEventInformation eventInfo) {
Message msg = eventInfo.getMessage();
long timeToSend = 0;
Link l = getTopologyView().getLinkBetween(getMacAddress(),
eventInfo.getReceiver());
if (l.isConnected()) {
if (enableErrorControl) {
Link reverseLink = getTopologyView().getLinkBetween(
eventInfo.getReceiver(), getMacAddress());
timeToSend = tryToSend(eventInfo, l, reverseLink);
} else {
timeToSend = tryToSend(eventInfo, l, null);
}
assert timeToSend > 0;
getEnergyComponent().send(timeToSend, msg, false);
/*
* Account for energy consumption due to listening in a
* broadcast-medium. This will happen even if the Message was not
* delivered due to an asymmetric link.
*/
if (phy.isBroadcastMedium()) {
List neighbors = getTopologyView().getNeighbors(
getMacAddress());
/*
* saving some calls to equals
*/
boolean skippedReceiver = false;
for (MacAddress neighbor : neighbors) {
if (skippedReceiver
|| neighbor.equals(eventInfo.getReceiver())) {
skippedReceiver = true;
continue;
}
assert !neighbor.equals(getMacAddress());
MacLayer macReceiver = getTopologyView().getMac(neighbor);
if (macReceiver.isOnline()) {
macReceiver.getEnergyComponent().receive(timeToSend,
msg, false, false);
}
}
}
} else {
messageDropped(DropReason.NO_LINK, msg);
/*
* We have no neighbors!
*/
eventInfo.arrivedAt(this, true);
timeToSend = 1 * Time.MICROSECOND;
}
return timeToSend;
}
@Override
public final BandwidthImpl getCurrentBandwidth() {
return currentBandwidth.getEstimatedBandwidth();
}
@Override
public final BandwidthImpl getMaxBandwidth() {
return maximumBandwidth;
}
/**
* Try to deliver the message over the provided link. If the message is a
* broadcast, the BW should be constant across all links (we assume the PHY
* selects a fixed, more robust modulation for Broadcasts), as there is no
* handshaking. Therefore, Broadcasts have no retransmits.
*
* This method must be called exactly once for every receiver of a
* message (in the unicast case, it will be called only once). The receiver
* is determined by the link-object. Access this method via the sendUnicast
* and sendBroadcast methods to ensure correct behavior of all callbacks and
* events.
*
* @param eventInfo
* containing the Message
* @param l
* the Link from source to receiver
* @param backlink
* the Link from receiver to source. This may be null, if you do
* not want to take link asymmetries into account.
* @return the time it took to send the message (time the radio had to stay
* active), which is for example used to account for energy
* consumption. This is NOT the same as the time we waited before
* scheduling the receive-event at the MAC, as latency on the link
* is not included.
*/
private final long tryToSend(MacEventInformation eventInfo, Link l,
Link backlink) {
/*
* Time the MACs need to operate in HighPower (are active)
*/
long totalSendingTime = 0;
boolean isBroadcast = eventInfo.isBroadcast();
/*
* Delay is the time we wait before scheduling the receive-event at the
* other MAC.
*/
long delay = 0;
long latency = l.getLatency();
long uploadTime = getUploadTime(eventInfo.getMessage(), l, isBroadcast);
/*
* The following assertions are to ensure proper working conditions,
* enable assertions during development if you extend this MAC.
*/
assert enableErrorControl && (backlink != null || isBroadcast)
|| !enableErrorControl;
assert l.isConnected();
/*
* TODO: virtual MTU-behavior -> if l.getMTU < msgSize, calculate drop
* for every virtual MTU to determine, if a packet can be delivered.
*/
/*
* Does a message arrive at the receiver?
*/
boolean dropped = false;
if (enableErrorControl) {
/*
* Number of Retransmissions (message is sent retransmissions + 1
* times). We do not really send and receive ACKs here, this is up
* to more advanced MACs. Instead, assume zero-time ACKs that might
* get lost (drop), and if this is the case, we would resend the
* message which will add on the total delay.
*/
int allowedRetransmissions = 0;
if (!isBroadcast) {
allowedRetransmissions = maxRetransmissions;
}
int tries = 0;
do {
if (tries > allowedRetransmissions) {
dropped = true;
break;
}
double candidate = random.nextDouble();
dropped = candidate <= l.getDropProbability();
tries++;
} while (dropped);
/*
* Calculate the Retransmits that would occur due to ACKs not being
* received by the sender. This is not done for broadcasts.
*/
if (!dropped && tries <= maxRetransmissions) {
int ackTransmits = 0;
boolean ackDropped = false;
do {
if (ackTransmits + tries > allowedRetransmissions) {
ackDropped = true;
break;
}
double candidate = random.nextDouble();
ackDropped = candidate <= backlink.getDropProbability();
ackTransmits++;
} while (ackDropped);
assert (ackDropped && tries + ackTransmits == allowedRetransmissions + 1)
|| (!ackDropped);
tries += ackTransmits;
}
/*
* FIXME: ACK-timing? Do we need to take this into account or do we
* just assume zero-time ACKs?
*/
delay = (latency + uploadTime) * tries;
totalSendingTime = uploadTime * tries;
} else {
/*
* Just send. ETHERNET-Style, no error control on lower layers.
*/
double candidate = random.nextDouble();
dropped = candidate <= l.getDropProbability();
delay = latency + uploadTime;
totalSendingTime = uploadTime;
}
/*
* Consume energy at the receiver, even if the message was not
* successfully delivered - he had to listen all the time.
*/
MacLayer macReceiver = getTopologyView().getMac(l.getDestination());
if (macReceiver.isOnline()) {
macReceiver.getEnergyComponent().receive(totalSendingTime,
eventInfo.getMessage(), eventInfo.isBroadcast(), true);
}
if (dropped) {
/*
* Notify Mac, if the message has been dropped due to
* MAX_RETRANSMITS. We do not care for dropped Broadcasts
*/
if (!eventInfo.isBroadcast()) {
messageDropped(DropReason.LINK_DROP, eventInfo.getMessage());
}
} else {
if (!eventInfo.isBroadcast()
&& eventInfo.getMessage() instanceof LinkLayerMessage) {
_linkMsgEvent((LinkLayerMessage) eventInfo.getMessage(),
getHost(), MessageAnalyzer.Reason.SEND);
}
}
/*
* Schedule MAC-Events even for dropped Messages in order to get the
* callback as soon as the current MAC is able to send again.
*/
scheduleReceive(macReceiver, eventInfo, delay, dropped);
currentBandwidth.outgoingTransmission(eventInfo.getMessage().getSize());
/*
* Latency is not included, because we only have to stay active until
* the message is being sent, not until it is fully delivered. In a
* broadcast-scenario tries will always be one.
*/
assert totalSendingTime > 0;
return totalSendingTime;
}
/**
* Calculate the time it takes to upload the message via the given link (if
* sending would be possible at the full RawBandwidth of the PHY). A more
* advanced MAC will add some coding (making the message larger) and access
* control scheme for multiple transmissions.
*
* This time is later used to account for energy consumption at sender and
* receiver.
*
* @param msg
* @param l
* @return
*/
protected long getUploadTime(Message msg, Link l, boolean isBroadcast) {
// Size is in byte, bandwidth in bit/s
long uploadTime = Math.max(
msg.getSize() * 8 * Time.SECOND / l.getBandwidth(isBroadcast),
Time.MICROSECOND);
assert uploadTime > 0;
return uploadTime;
}
@Override
public PhyType getPhyType() {
return phy;
}
/**
* Overwrite this method to implement additional event handling, if needed.
*
* @param se
*/
protected void handleEvent(Object data, int type) {
// not needed here.
}
@Override
public final void eventOccurred(Object data, int type) {
if (type == MESSAGE_RECEIVED) {
if (data instanceof MacEventInformation) {
MacEventInformation meInfo = (MacEventInformation) data;
meInfo.arrivedAt(this, !isOnline());
if (isOnline()) {
currentBandwidth.incomingTransmission(meInfo.getMessage()
.getSize());
this.handleReceivedMessage(meInfo);
} else {
// receiving mac (our mac) is offline
if (!meInfo.isBroadcast()) {
this.messageDropped(DropReason.RECEIVING_MAC_OFFLINE,
meInfo.getMessage());
}
}
} else {
throw new AssertionError(
"A SimulationEvents getData() in the MAC has to return a MacEventInformation!");
}
} else if (type == MESSAGE_DROPPED) {
/*
* We use MESSAGE_DROPPED to schedule events for messages that are
* dropped. This helps us to send messages "one after the other" by
* using a callback from the receiving MAC even if the message was
* not transmitted.
*/
if (data instanceof MacEventInformation) {
MacEventInformation meInfo = (MacEventInformation) data;
meInfo.arrivedAt(this, true);
} else {
throw new AssertionError(
"A SimulationEvents getData() in the MAC has to return a MacEventInformation!");
}
}
handleEvent(data, type);
}
@Override
public boolean isOnline() {
return isOnline;
}
@Override
public void goOffline() {
energyComponent.turnOff();
isOnline = false;
/*
* TODO Maybe its cooler to let the entries timeout instead...
* Suggestions?
*/
// drop messages in queue
Iterator it = queue.iterator();
while (it.hasNext()) {
QueueEntry entry = it.next();
it.remove();
messageDropped(DropReason.SENDING_MAC_OFFLINE, entry.getMessage());
}
}
@Override
public void goOnline() {
/*
* Only go online if the battery still permits it
*/
if (energyComponent.turnOn()) {
isOnline = true;
}
}
@Override
public final void send(MacAddress receiver, LinkLayerMessage message) {
if (!isOnline()) {
// we are offline
messageDropped(DropReason.SENDING_MAC_OFFLINE, message);
} else {
// clean up the queue
removeOutdatedQueueEntries();
if (maxQueueLength > 0 && queue.size() >= maxQueueLength) {
// queue is full, even after clean up
messageDropped(DropReason.QUEUE_FULL, message);
// FIXME BR DEBUG Print it
// int idx = 0;
// for (QueueEntry qe : queue) {
// Message msg = qe.getMessage();
// while (msg.getPayload() != null) {
// msg = msg.getPayload();
// }
// System.out.println(idx + " "
// + msg.getClass().getSimpleName() + " "
// + msg.toString());
// idx++;
// }
} else {
// still a spot in the queue, add message and notify MAC
queue.add(new QueueEntry(receiver, message));
handleNewQueueEntry();
}
}
}
@Override
public final MacAddress getMacAddress() {
return macAddress;
}
@Override
public NetID getNetId() {
if (netID == null) {
// Wow... :)
netID = host.getNetworkComponent().getByName(getPhyType().getNetInterfaceName()).getLocalInetAddress();
if (netID == null) {
throw new AssertionError();
}
}
return netID;
}
@Override
public final void setMessageListener(LinkMessageListener listener) {
this.messageListener = listener;
}
@Override
public final SimHost getHost() {
return host;
}
@Override
public String toString() {
return "MacLayer: " + macAddress.toString() + " " + phy.toString();
}
/**
* An entry in the Message queue of the MAC
*
* @author Bjoern Richerzhagen
* @version 1.0, 21.02.2012
*/
public class QueueEntry {
/**
* The timestamp when the message was added to the queue
*/
private long timeEntered;
private MacAddress receiver;
private LinkLayerMessage message;
public QueueEntry(MacAddress receiver, LinkLayerMessage message) {
this.receiver = receiver;
this.message = message;
this.timeEntered = Time.getCurrentTime();
}
public LinkLayerMessage getMessage() {
return message;
}
public MacAddress getReceiver() {
return receiver;
}
public long getTimeEntered() {
return timeEntered;
}
}
/**
* A Stub for an {@link EnergyCommunicationComponent} to allow simulations
* without an energy model. Otherwise we would have to flood the code with
* if == null statements.
*
* @author Bjoern Richerzhagen
* @version 1.0, 27.02.2012
*/
private class EnergyComponentStub implements EnergyCommunicationComponent {
private PhyType phy;
private boolean on = false;
public EnergyComponentStub(PhyType phy) {
this.phy = phy;
}
@Override
public ComponentType getType() {
return ComponentType.COMMUNICATION;
}
@Override
public void setEnergyEventListener(EnergyEventListener listener) {
// not interested
}
@Override
public void eventOccurred(Object content, int type) {
// will not happen
}
@Override
public PhyType getPhyType() {
return phy;
}
@Override
public void send(long duration, Message msg, boolean isBrodcast) {
// nothing to do
}
@Override
public void receive(long duration, Message msg, boolean isBroadcast,
boolean isIntendedReceiver) {
// nothing to do
}
@Override
public boolean turnOff() {
on = false;
return true;
}
public boolean turnOn() {
on = true;
return true;
}
@Override
public boolean isOn() {
return on;
}
@Override
public void doFakeStateChange() {
// TODO Auto-generated method stub
}
}
/**
* A Live-Analyzer that aggregates Message Drop reasons inside the MAC.
*
* @author Bjoern Richerzhagen
* @version 1.0, 26.03.2012
*/
public class MacMessagesDropReasonProgress implements ProgressValue {
public long[] _messageDropCounters;
public final String name = "Link " + getPhyType().toString()
+ " DropReason";
public MacMessagesDropReasonProgress() {
_messageDropCounters = new long[DropReason.values().length];
}
@Override
public String getName() {
return name;
}
public void increment(DropReason reason) {
_messageDropCounters[reason.ordinal()]++;
}
@Override
public String getValue() {
StringBuffer out = new StringBuffer();
for (DropReason reason : DropReason.values()) {
out.append(reason.toString());
out.append(": ");
out.append(_messageDropCounters[reason.ordinal()]);
out.append("\n");
}
return out.toString();
}
}
private static boolean _linkAnalyzerInitialized = false;
private static LinklayerAnalyzer _linkAnalyzer = null;
protected static void _linkMsgEvent(LinkLayerMessage msg, SimHost host,
Reason reason) {
if (!_linkAnalyzerInitialized) {
try {
_linkAnalyzer = Monitor.get(LinklayerAnalyzer.class);
} catch (AnalyzerNotAvailableException e) {
_linkAnalyzer = null;
}
_linkAnalyzerInitialized = true;
}
if (_linkAnalyzerInitialized && _linkAnalyzer != null) {
_linkAnalyzer.linkMsgEvent(msg, host, reason);
}
}
}