package brn.swans.mac;

import brn.swans.route.RouteMcExORMsg;
import brn.swans.route.RouteDsrBrnMsg;
import brn.swans.misc.FeedbackMessageAnno;
import brn.sim.data.dump.WiresharkDump;
import jist.runtime.*;
import jist.swans.Constants;
import jist.swans.rate.RateControlAlgorithmIF;
import jist.swans.rate.ConstantRate;
import jist.swans.phy.PhyMessage;
import jist.swans.phy.Phy802_11;
import jist.swans.mac.MacAddress;
import jist.swans.mac.AbstractMac;
import jist.swans.mac.MacInfo;
import jist.swans.mac.MacInterface;
import jist.swans.misc.*;
import jist.swans.misc.Util;
import jist.swans.net.NetAddress;
import jist.swans.net.NetMessage;
import jist.swans.net.NetInterface;
import jist.swans.radio.RadioInfo;
import jist.swans.radio.RadioInterface;
import org.apache.log4j.Logger;
import java.util.LinkedList;
import java.util.List;

/**
 * Implementation of the multi-channel opportunistic mac layer. <p/>
 * Modifications to the 'original' protocol: - Slotted Ack with preferences.
 * Each slotted ack message contains a preference value which indicates the
 * willingness of the candidate to forward the packet. If a node is currently a
 * sender or receiver of a data burst (assumption: data bursts/streams), it
 * increases this value to indicate that his willingness is decreased.
 * Underlying observation: The protocol performs better this way because it
 * frees ultimate senders and receivers from the task of packet relaying. Using
 * the preferenced slotted ack, not only the sender, but also the receivers
 * deciede about the opportunistic forwarder. The preference is time-dependent.
 * Since we do not know whether and how long we are ultimate sender/receiver,
 * the preference is increased with each stream originating from or destinating
 * to us. Preference decreases through time-out. The mac considers only per-hop
 * relaying and is not able to identify these streams. This decision is made in
 * the net layer. observation: forwarding preference is a time-dependant and
 * node-dependant value. <p/> mac the slotted ack informs about the forwarder
 * according to the ordering of candidates in the cs ('backward' direction). The
 * preferences have to be propagated in 'forward' direction to avoid duplication
 * of packets. Solution 1: Ignore. Because all candidates are neighbours, this
 * happens automatically. Solution 2: The sender broadcasts the final forwarder
 * (like ack-ack). Solution 3: A second slotted ack session in 'forward'
 * direction. Applied Solution: The routing ensures that the links between
 * candidates all have a quality better than a certain threashold, so that the
 * probability that the preferences are correctly propagated is very high. <p/>
 * TODO redesign channel switch / packet handling stuff <p/> compressed ack: ein
 * nachfolgender knoten muss pruefen, ob seine vorgaenger ihr ack gesendet
 * haben. Wenn ein knoten kein ack sendet (weil er das packet) nicht empfangen
 * hat), dann sendet der naechste kandidat sein ack (um 10us versetzt). Wenn
 * auch er das packet nicht empfangen hat, dann ist der uebernaechste kandidat
 * mit einer weiteren versetzung von 10us dran, usw. Dadurch entstehen keine
 * pausen von groesser DIFS=50us (bei kleinem Candidate Set), wodurch verhindert
 * wird, dass nicht beteiligte knoten bei ausbleiben eines acks das medium
 * faelschlicherweise fuer frei halten und zwischen die data-ack sequenz senden.
 *
 * TODO implement multi-bitrate
 * TODO migrate preference handling
 * TODO implement contention-based slotted ack.
 * TODO implement rts-cts for slotted ack.
 *
 * New nuggets:
 * - silence neighboring nodes with NAV in slotted acks
 * - cancel the slotted ack process when prematurely receiving the ack of the lowest candidate
 *
 * @author kurth
 * @author zubow
 */
public class MacMcExOR extends AbstractMac implements MacInterface.MacDcf {

  /** logger for mac events. */
  public static final Logger log = Logger.getLogger(MacMcExOR.class.getName());

  // ////////////////////////////////////////////////
  // constants
  //

  // mac modes

  /** mac mode: idle. */
  public static final byte MAC_MODE_SIDLE         = 0;
  /** mac mode: waiting for difs or eifs timer. */
  public static final byte MAC_MODE_DIFS          = 1;
  /** mac mode: waiting for backoff. */
  public static final byte MAC_MODE_SBO           = 2;
  /** mac mode: waiting for virtual carrier sense. */
  public static final byte MAC_MODE_SNAV          = 3;

  /** mac mode: waiting for ACK packet. */
  public static final byte MAC_MODE_SWFACK        = 7;

  /** mac mode: transmitting unicast DATA packet. */
  public static final byte MAC_MODE_XUNICAST      = 10;
  /** mac mode: transmitting broadcast DATA packet. */
  public static final byte MAC_MODE_XBROADCAST    = 11;
  /** mac mode: transmitting ACK packet. */
  public static final byte MAC_MODE_XACK          = 12;

  /** mac mode: channel switching. */
  public static final byte MAC_MODE_SWITCH        = 14;


  // entity hookup

  /** Self-referencing mac entity reference. */
  protected MacInterface.MacDcf self;
  /** Radio downcall entity reference. */
  protected RadioInterface      radioEntity;
  /** Network upcall entity interface. */
  protected NetInterface        netEntity;
  /** informs other entities about rx/tx feedbacks. */
  protected MacRXTXFeedbackInterface feedback;

  /** Local class to used manage sequence number records. */
  private static class SeqEntry {
    /** Invalid sequence number. */
    public static final short SEQ_INVALID     = -1;
    /** Sequence number cache size. */
    public static final short SEQ_CACHE_SIZE  = 100;

    /** src node address. */
    public MacAddress from = MacAddress.NULL;

    /** sequence number. */
    public short      seq = 0;

    /** next record. */
    public SeqEntry   next = null;
  }

  /** The static MAC configuration. */
  protected static class Configuration {

    /** Retransmissions attempted for short packets (those without RTS). */
    public static final byte  RETRY_LIMIT_SHORT     = 7;

    /** Maximum collision window (for backoff). */
    public static final short CW_MAX                = 1023;

    /** PHY layer incl. radio information to pass to bit-rate selection */
    protected Phy802_11       phy;

    /** Radio information of this station. Shortcut to the encapsulated radio info in the phy. */
    protected RadioInfo       radioInfo;

    /** MAC type: 802.11, 802.11a, 802.11b, 802.11g or any combination. */
    protected short           macType;

    /** Delay necessary to switch from one RF channel to another. */
    protected long            delayChannelSwitch;

    /** Whether to use acknowledgement compression */
    protected boolean         useCompression;

    /** Whether to use preferences in ack packets (or send max all the time) */
    protected boolean         usePreferencedAck;

    /** mac address of this interface. */
    protected MacAddress      localAddr;

    /** Number of available RF channels */
    protected int             channels;

    /** The home channel of the MCExOR protocol */
    protected RadioInterface.RFChannel homeChannel;

    /** Indicates whether we can use sequence numbers from app layer to detect duplicates */
    public boolean            useAppSeqNumbers;

    public static final int MAX_NUMBER_OF_FLOWS             = 32; // 2 ^ 5
    public static final int MAX_NUMBER_OF_PACKETS_PER_FLOW  = ( Short.MAX_VALUE + 1 ) / MAX_NUMBER_OF_FLOWS - 1; // 2 ^ 10 - 1

    /** Duplicate suppression algorithm. */
    public boolean            detectDuplicates;

    /** Whether to listen to foreign packets in order to prevent retransmissions. */
    public boolean            passiveAcknowledgement;

    /** the data rate bit-rate selection algorithm used in this MAC entity */
    protected RateControlAlgorithmIF       dataRcAlg;
    /** the control rate bit-rate selection algorithm used in this MAC entity */
    protected RateControlAlgorithmIF       ctrlRcAlg;

    protected long DELAY_INIT_SENSING_WITHOUT_SYNC = Constants.PROPAGATION;

    protected long missingAckSpace            = -1;

    protected double refNoiseFactor           = brn.swans.Constants.DEFAULT_REF_NOISE_FACTOR;

    /**
     * In the compressed slotted ack version of this protocol noise detection is used to
     * detect other candidates ack packets. By enabling this flag for such detected ack
     * the expected candidate is calculated and we assume that he will be the next forwarder.
     * TODO: hot, because we ignore relay prefs; it has not to be necessarily an corresponding ack packet.
     */
    protected boolean updatePrioOnNoiseDetection  = true;

    /**
     * In the slotted ack version of this protocol the following can happen. Case with 3 candidates.
     * The highest candidate sends the ack which is detected only by the second highest candidate.
     * The lowest candidate will wait prematurely sends its ack packet (2*SIFS). To avoid an collision
     * with the following ack of the medium candidate we are using acks with variable sizes. The following
     * formula is used: Suppose we have n candidates. The size of the ack of a candidate with prio x
     * is calculated as followed: ack_size = base_ack_size + (n - x - 1) * SIFS.
     */
    protected boolean useVariableAckSize          = true;

    /** Indicates whether the NAV value in the slotted acks should be set or not. */
    protected boolean useNAVinAcks                = true;
    /** Indicates whether the NAV value in the anycast data should be set or not. */
    protected boolean useNAVinData                = true;

    protected int candidateSetSize                = -1;

    /** rate to use with data packets (units: bytes/second). */
    protected int                          dataRate     = Constants.INVALID_CHANNEL;
    /** rate to use with control packets (ack/rts/cts) (units: bytes/second). */
    protected int                          controlRate  = Constants.INVALID_CHANNEL;

    /** Used to for testing only */
    protected boolean testRun                     = false;

    public Configuration(long delayChannelSwitch,
        MacAddress localAddr, RadioInterface.RFChannel homeChannel, int channels,
        boolean useCompression, boolean usePreferencedAck, boolean useAppSeqNumbers,
        boolean detectDuplicates, boolean passiveAcknowledgement, RadioInfo radioInfo,
                         Phy802_11 phy, double refNoiseFactor, boolean updatePrioOnNoiseDetection,
                         boolean useVariableAckSize, boolean useNAVinAcks, boolean useNAVinData,
                         int candidateSetSize, boolean testRun) {
      super();

      this.delayChannelSwitch = delayChannelSwitch;
      this.localAddr = localAddr;
      this.useCompression = useCompression;
      this.usePreferencedAck = usePreferencedAck;
      this.homeChannel = homeChannel;
      this.channels = channels;
      this.useAppSeqNumbers = useAppSeqNumbers;
      this.detectDuplicates = detectDuplicates;
      this.passiveAcknowledgement = passiveAcknowledgement;
      this.radioInfo = radioInfo;
      this.phy = phy;
      this.macType = radioInfo.getMacType();
      this.missingAckSpace = getSifs();
      this.refNoiseFactor = refNoiseFactor;
      this.updatePrioOnNoiseDetection = updatePrioOnNoiseDetection;
      this.useVariableAckSize = useVariableAckSize;
      this.useNAVinAcks = useNAVinAcks;
      this.useNAVinData = useNAVinData;
      this.candidateSetSize = candidateSetSize;

      this.testRun = testRun;
    }

    /**
     * Returns the channel switch delay.
     *
     * @return the channel switch delay
     */
    public long getDelayChannelSwitch() {
      return (delayChannelSwitch);
    }

    /**
     * Compute packet transmission time at current bandwidth.
     *
     * @param msg
     *          packet to transmit
     * @return time to transmit given packet at current bandwidth
     */
    protected long transmitTime(Message msg, int rate) {
      return transmitTime(msg.getSize(), rate);
    }

    protected long transmitTime(int size, int rate) {
      if (size == Constants.ZERO_WIRE_SIZE) { return Constants.EPSILON_DELAY; }
      return phy.transmitTime(size, rate, macType);
    }

    public MacAddress getLocalAddr() {
      return localAddr;
    }

    // //////////////////////////////////////////////////
    // short 802.11 lexicon:
    // slot - minimum time to sense medium
    // nav - network allocation vector (virtual carrier sense)
    // sifs - short inter frame space
    // pifs - point coordination inter frame space
    // difs - distributed inter frame space
    // eifs - extended inter frame space
    // cw - collision (avoidance) window
    // DSSS - Direct Sequence Spread spectrum
    // FHSS - Frequency Hopping Spread Spectrum
    //

    /** @return Slot time for the current MAC (unit: us). */
    public long getSlotTime() {
      switch (macType) {
        case Constants.MAC_802_11a:
          return Constants.SLOT_TIME_OFDM;
        case Constants.MAC_802_11:
        case Constants.MAC_802_11b:
        case Constants.MAC_802_11bg:
          return Constants.SLOT_TIME_DSSS;
        case Constants.MAC_802_11g_PURE:
          return Constants.SLOT_TIME_OFDM;
        default:
          throw new RuntimeException("Unknown MAC: " + radioInfo.getMacTypeString());
      }
    }

    /** @return SIFS time for the current MAC (unit: us). Includes Rx-Tx-Turnaround time. */
    public long getSifs() {
      switch (macType) {
        case Constants.MAC_802_11a:
          return Constants.SIFS_OFDM_5GHZ;
        case Constants.MAC_802_11:
        case Constants.MAC_802_11b:
        case Constants.MAC_802_11bg:
          return Constants.SIFS_DSSS;
        case Constants.MAC_802_11g_PURE:
          return Constants.SIFS_OFDM__2_4_GHZ;
        default:
          throw new RuntimeException("Unknown MAC: " + radioInfo.getMacTypeString());
      }
    }

    /** @return The SIFS minus the Rx/Tx turnaround time. During this time the radio is not deaf. */
    public long getTxSifs() {
      return getSifs() - getRxTxTurnaround();
    }

    /** @return PIFS time for the current MAC (unit: us). */
    public long getPifs() {
      return getSlotTime() + getSifs();
    }

    /** @return DIFS time for the current MAC (unit: us). Contains SIFS, thus includes Rx-Tx-Turnaround time. */
    public long getDifs() {
      return 2 * getSlotTime() + getSifs();
    }

    /** @return The DIFS time minus the Rx/Tx turnaround time. During this time the radio is not deaf. */
    public long getTxDifs() {
      return 2 * getSlotTime() + getTxSifs();
    }

    /**
     * Extended inter frame space. Wait used by stations to gain access to the
     * medium after an error.
     *
     * @return EIFS time for the current MAC (unit: us).
     */
    public long getEifs() {

      // Set default basic rate (idx=0 returns the lowest one)
      int rate = getBasicRateSet()[0]; // either 1 Mbps or 6 Mbps

      // See 802.11-1999.pdf, p.85
      /* TODO: how to calculate the number of candidates ???*/
      return getSifs() // for ACK tx by other station
          + phy.transmitTime(MacMcExORMessage.Ack.getSize(0), rate, macType)
          + phy.preambleDuration(rate, macType)
          + phy.headerDuration(rate, macType)
          + getDifs(); // for next tx of this station
    }

    /** @return The EIFS time minus the Rx/Tx turnaround time. During this time the radio is not deaf. */
    public long getTxEifs() {
      return getEifs() - getRxTxTurnaround();
    }

    /**
     * Get RX-TX-turnaround time depending on MAC.
     *
     * A wlan client is not able to send and receive (snoop on the medium) at the
     * same point in time. When the radio switches from receiving to sending mode
     * or vice versa, there is a small time interval where both sending and
     * receiving are not possible (RX/TX turnaround time).
     */
    public long getRxTxTurnaround()
    {
      if (macType == Constants.MAC_802_11a || macType == Constants.MAC_802_11g_PURE)
        return Constants.RX_TX_TURNAROUND__802_11a;
      else return Constants.RX_TX_TURNAROUND__802_11bg;
    }

    /** Minimum collision window (for backoff) for the current MAC. */
    public final short cwMin() {
      return cwMin(macType);
    }

    /** Minimum collision window (for backoff) for the given MAC. */
    public static short cwMin(short mac) {
      if (mac == Constants.MAC_802_11a || mac == Constants.MAC_802_11g_PURE) {
        return 15;
      } else return 31;
    }

    /** Returns the basic rate set for the current MAC in ascending order. */
    public int[] getBasicRateSet() {
      return radioInfo.getBasicRateSet();
    }

    protected void adjustBitrates(Message packet, MessageAnno anno,
        MacMcExORMessage.CSetAddress dest, boolean bufferedPacket, int packetSize, int retry) {

      Integer bitRateAnno = (Integer)anno.get(MessageAnno.ANNO_MAC_BITRATE);

      if (bitRateAnno != null) {
        // bitrate was manually setted
        dataRate = bitRateAnno.intValue();
      } else { // use bitrate-selection algorithm
        /*
         * Bit-rate selection algorithm chooses bitrate(s) for next packet. Pass the
         * total count of tries, but at least 0. Only shortRetry OR longRetry is
         * used during packet transmission.
         */
        dataRate = dataRcAlg.getNextDataRate(packet, anno, dest, packetSize, retry);
      }
      // we are using fixed rates for control data.
      controlRate = ctrlRcAlg.getNextDataRate(null, null, null, -1, -1);
    }

    /**
     * Returns the mnemonic for the given mode.
     * @param mode the mode
     * @return the mnemonic
     */
    public String getModeString(byte mode) {
      switch (mode) {
      case MAC_MODE_SIDLE:
        return "MAC_MODE_SIDLE";
      case MAC_MODE_DIFS:
        return "MAC_MODE_DIFS";
      case MAC_MODE_SBO:
        return "MAC_MODE_SBO";
      case MAC_MODE_SNAV:
        return "MAC_MODE_SNAV";
      case MAC_MODE_SWFACK:
        return "MAC_MODE_SWFACK";
      case MAC_MODE_XUNICAST:
        return "MAC_MODE_XUNICAST";
      case MAC_MODE_XBROADCAST:
        return "MAC_MODE_XBROADCAST";
      case MAC_MODE_XACK:
        return "MAC_MODE_XACK";
      case MAC_MODE_SWITCH:
        return "SWITCH";
      default:
        throw new RuntimeException("unknown mode: " + mode);
      }
    }
  }

  /** The dynamic part of the MAC configuration */
  protected class MacCtx {

    /** network interface number. */
    protected byte netId;

    /**
     * The (current) preference of the mac (node) to relay (forward) packets.
     * This value is triggered by the net on time.
     */
    protected byte forwardPref;

    /** whether mac is in promiscuous mode. */
    protected boolean promisc;

    /** current mac mode. */
    protected byte mode;

    /** radio mode used for carrier sense. */
    protected byte radioMode;

    /** whether last reception had an error. */
    protected boolean needEifs;

    /** timer identifier. */
    protected byte timerId;

    /** sequence number counter. */
    protected short seq;

    /** received sequence number cache list. */
    protected SeqEntry seqCache;

    /** size of received sequence number cache list. */
    protected byte seqCacheSize;

    /** backoff time remaining. */
    protected long bo;

    /** backoff start time. */
    protected long boStart;

    /** current contention window size. */
    protected short cw;

    /** virtual carrier sense; next time when network available. */
    private long nav;

    /** The current RF channel the node operates on. */
    protected RadioInterface.RFChannel channel;

    public MacCtx() {
      netId = -1;
      // properties
      promisc = Constants.MAC_PROMISCUOUS_DEFAULT;
      // status
      mode = MAC_MODE_SIDLE;
      radioMode = Constants.RADIO_MODE_IDLE;
      needEifs = false;
      // timer identifier
      timerId = 0;
      // backoff
      bo = 0;
      boStart = 0;
      cw = config.cwMin();
      // virtual carrier sense
      setNav(-1);
      // sequence numbers
      seq = 0;
      seqCache = null;
      seqCacheSize = 0;
      // forward preference
      forwardPref = brn.swans.Constants.PREFERENCE_MAX;
      // channel
      channel = RadioInterface.RFChannel.INVALID_RF_CHANNEL;
    }

    public void setNav(long nav) {
      setNav(nav, null);
    }

    public void setNav(long nav, MacAddress src) {
      this.nav = nav;

      if (navChangedEvent != null && navChangedEvent.isActive())
        navChangedEvent.handle(nav, src);
    }

    public long getNav() {
      return nav;
    }

    /**
     * Set promiscuous mode (whether to pass all packets through).
     *
     * @param promisc promiscuous flag
     */
    public void setPromiscuous(boolean promisc) {
      this.promisc = promisc;
    }

    /**
     * Retrieves whether the radio switches channels.
     *
     * @return true if switching, false otherwise
     */
    public boolean isSwitching() {
      return (mode == MAC_MODE_SWITCH);
    }

    public byte getForwardPref() {
      return forwardPref;
    }

    public void setForwardPref(byte forwardPref) {
      if (config.usePreferencedAck)
        this.forwardPref = forwardPref;
      else
        this.forwardPref = brn.swans.Constants.PREFERENCE_MAX;
    }

    /**
     * @return Returns the localAddr.
     */
    public MacAddress getLocalAddr() {
      return config.localAddr;
    }

    public byte getMode() {
      return mode;
    }

    /**
     * Set the current mac mode.
     *
     * @param mode new mac mode
     */
    protected void setMode(byte mode) {

      if (this.mode != mode && macModeChanged.isActive())
        macModeChanged.handle(this.mode, mode);

      if (log.isDebugEnabled())
        log.debug(getNode().getNodeId() + "(" + JistAPI.getTime()
            + "): mode change" + " from " + config.getModeString(this.mode)
            + " to " + config.getModeString(mode) + ".");

      this.mode = mode;
    }

    /**
     * Return whether the mac is currently waiting for a response. Since we are
     * using exor, we must stall the mac also in the case of xack. Otherwise
     * incoming packets during slotted ack will corrupt our state and the mac
     * will explode.
     *
     * @return whether mac waiting for response
     */
    public boolean isAwaitingResponse() {
      switch (mode) {
      case MAC_MODE_SWFACK:
      case MAC_MODE_XACK:
        return true;
      default:
        return false;
      }
    }

    /**
     * Return whether the mac is currently transmitting.
     *
     * @return whether mac is currently transmitting
     */
    public boolean isTransmitting() {
      switch (mode) {
      case MAC_MODE_XUNICAST:
      case MAC_MODE_XBROADCAST:
      case MAC_MODE_XACK:
        return true;
      default:
        return false;
      }
    }

    // ////////////////////////////////////////////////
    // backoff
    //

    /**
     * Return whether there is a backoff.
     *
     * @return whether backoff non-zero.
     */
    protected boolean hasBackoff() {
      return bo > 0;
    }

    /** Reset backoff counter to zero. */
    protected void clearBackoff() {
      if (backoffEvent.isActive())
        backoffEvent.handle(JistAPI.getTime() - boStart);

      log.debug(getNode().getNodeId() + "(" + JistAPI.getTime() + "): clearBackoff()");
      boStart = 0;
      bo = 0;
      // illegal value, must be resetted ...
      //mode = MAC_MODE_SIDLE - MAC_MODE_DIFS;
    }

    /** Set new random backoff, if current backoff timer has elapsed. */
    protected void setBackoff() {
      if (!hasBackoff()) {
        log.debug(getNode().getNodeId() + "(" + JistAPI.getTime() + "): setBackoff()");
        bo = Constants.random.nextInt(cw) * config.getSlotTime();
        if (Main.ASSERT) Util.assertion(boStart == 0);
        if (Main.ASSERT) Util.assertion(bo >= 0);
      }
    }

    /** Pause the current backoff (invoked when the channel becomes busy). */
    private void pauseBackoff() {
      if (Main.ASSERT) Util.assertion(boStart > 0);
      if (Main.ASSERT) Util.assertion(bo > 0);

      log.debug(getNode().getNodeId() + "(" + JistAPI.getTime() + "): pauseBackoff()");

      bo -= JistAPI.getTime() - boStart;

      if (0 == bo) bo = Constants.EPSILON_DELAY;

      if (backoffEvent.isActive())
        backoffEvent.handle(JistAPI.getTime() - boStart);

      if (Main.ASSERT) Util.assertion(bo > 0);
      boStart = 0;
    }

    /** Perform backoff. */
    protected void backoff() {
      if (Main.ASSERT) Util.assertion(boStart == 0);
      if (Main.ASSERT) Util.assertion(bo > 0);

      log.debug(getNode().getNodeId() + "(" + JistAPI.getTime() + "): backoff()");

      boStart = JistAPI.getTime();
      startTimer(bo, MAC_MODE_SBO);
    }

    /** Increase Collision Window. */
    private void incCW() {
      cw = (short) Math.min(2 * cw + 1, Configuration.CW_MAX);
      if (cwChangedEvent.isActive())
        cwChangedEvent.handle(cw);
    }

    /** Decrease Collision Windows. */
    protected void decCW() {
      cw = config.cwMin();
      if (cwChangedEvent.isActive())
        cwChangedEvent.handle(cw);
    }

    // ////////////////////////////////////////////////
    // nav
    //

    /**
     * Return whether the virtual carrier sense (network allocation vector)
     * indicates that the channel is reserved.
     *
     * @return virtual carrier sense
     */
    protected boolean waitingNav() {
      return getNav() > JistAPI.getTime();
    }

    /** Clear the virtual carrier sense (network allocation vector). */
    protected void resetNav() {
      setNav(-1);
    }

    /**
     * Determine whether channel is idle according to both physical and virtual
     * carrier sense.
     *
     * @return physical and virtual carrier sense
     */
    protected boolean isCarrierIdle() {
      return !waitingNav() && isRadioIdle();
    }

    // ////////////////////////////////////////////////
    // sequence numbers
    //

    /**
     * Increment local sequence counter.
     *
     * @return new sequence number.
     */
    private short incSeq() {
      seq = (short) ((seq + 1) % MacMcExORMessage.Data.MAX_SEQ);
      return seq;
    }

    /**
     * Return latest seen sequence number from given address.
     *
     * @param from source address
     * @return latest sequence number from given address
     */
    private short getSeqEntry(MacAddress from) {
      SeqEntry curr = seqCache, prev = null;
      short seq = SeqEntry.SEQ_INVALID;
      while (curr != null) {
        if (from.equals(curr.from)) {
          seq = curr.seq;
          if (prev != null) {
            prev.next = curr.next;
            curr.next = seqCache;
            seqCache = curr;
          }
          break;
        }
        prev = curr;
        curr = curr.next;
      }
      return seq;
    }

    /**
     * Update latest sequence number entry for given address.
     *
     * @param from source address
     * @param seq latest sequence number
     */
    private void updateSeqEntry(MacAddress from, short seq) {
      SeqEntry curr = seqCache, prev = null;
      while (curr != null) {
        if (seqCacheSize == SeqEntry.SEQ_CACHE_SIZE && curr.next == null) {
          curr.from = from;
          curr.seq = seq;
          break;
        }
        if (from.equals(curr.from)) {
          curr.seq = seq;
          if (prev != null) {
            prev.next = curr.next;
            curr.next = seqCache;
            seqCache = curr;
          }
          break;
        }
        prev = curr;
        curr = curr.next;
      }
      if (curr == null) {

        Util.assertion (seqCacheSize < SeqEntry.SEQ_CACHE_SIZE);
        curr = new SeqEntry();
        curr.from = from;
        curr.seq = seq;
        curr.next = seqCache;
        seqCache = curr;
        seqCacheSize++;
      }
    }

    protected boolean isRadioIdle() {
      return radioMode == Constants.RADIO_MODE_IDLE;
    }

    protected void setRadioMode(byte mode) {
      this.radioMode = mode;
    }

    public void setChannel(RadioInterface.RFChannel channel) {
      clearBackoff(); // bo = 0
      boStart = 0;
      decCW(); // cw = CW_MIN
      this.channel = channel;
      needEifs = false;
    }

  }

  /** Encapsulation of all transmission-related state. */
  protected class TransmissionCtx {

    /** The channel to use. */
    protected RadioInterface.RFChannel channel;

    /** packet currently being transmitted. */
    protected Message packet;

    /** next hop of packet current being transmitted. {@link List} of {@link MacAddress} */
    protected MacMcExORMessage.CSetAddress packetNextHops;

    /** next hop of packet current being transmitted. {@link List} of {@link MacAddress} */
    protected List packetOpenHops;

    /** short retry counter. */
    protected byte shortRetry;

    /** List of ack packets {@link MacMcExORMessage.Ack} */
    protected List lstAcksReceived;

    /** Packet msg annotation */
    protected MessageAnno anno;

    /** the address of the lowest priorized candidate */
    protected MacAddress addrLowestCandidate;

    /** indicates whether this TX follows a RTS/CTS. */
    public boolean afterCts;

    public TransmissionCtx(Message msg, MacMcExORMessage.CSetAddress nextHops,
                           RadioInterface.RFChannel channel, MessageAnno anno) {
      // check arguments
      if ( channel.equals(RadioInterface.RFChannel.INVALID_RF_CHANNEL) ||
          channel.getChannel() > config.channels)
        throw new IllegalArgumentException("Wrong RF Channel.");
      if (msg == null)
        throw new IllegalArgumentException("Message is null.");

      // check next hops, if ANY is contained, mark as broadcast (nextHops null)
      if (Main.ASSERT) {
        Util.assertion (nextHops.getCandidateSetSize() > 0);
        Util.assertion (!nextHops.contains(MacAddress.ANY) || nextHops.getCandidateSetSize()==1);
      }

      this.channel              = channel;
      this.packet               = msg;
      this.packetNextHops       = nextHops;
      this.packetOpenHops       = new LinkedList(nextHops.getLstAddresses());
      this.shortRetry           = 0;
      this.lstAcksReceived      = new LinkedList();
      this.anno                 = anno;
      this.addrLowestCandidate  = (MacAddress)packetOpenHops.get(packetOpenHops.size() - 1);
      this.afterCts             = false;
    }

    public void receiveAck(MacMcExORMessage.Ack ack, MessageAnno ackAnno) {
      if (!packetNextHops.contains(ack.getSrc())) {
        throw new RuntimeException("processing wrong ack packet.");
      }
      else if (!packetOpenHops.remove(ack.getSrc())) {
        log.warn(getNode().getNodeId() + "(" + JistAPI.getTime()
            + "): received duplicate ack from " + ack.getSrc()
            + " with id=" + macCtx.seq);
      }
      log.info(getNode().getNodeId() + "(" + JistAPI.getTime()
          + "): received ack from " + ack.getSrc() + " forwarder is candidate "
          + ack.getCandidateId() + " remaining ack " + packetOpenHops.size()
          + " with id=" + macCtx.seq + " pref " + ack.getForwardPref() + " ownpref "
          + ack.getOwnPref());

      handleRecvAckEvent(ackAnno, ack);

      // remember we received the ack.
      lstAcksReceived.add(ack);
      /** finish the slotted ack: all acks received or received lowest prioritized ack */
      finishSlottedAck(ack);
    }

    protected void finishSlottedAck(MacMcExORMessage.Ack ack) {
      /** AZu: Nugget - if we receive the ack of the lowest prioritized ack we can leave the W_ACK state */
      if (packetOpenHops.isEmpty() || (ack.getSrc().equals(txCtx.addrLowestCandidate)) ) { // all acks received
        if (macTxFinished.isActive())
          macTxFinished.handle(packet, anno, true, shortRetry);
        cancelTimer();
        macCtx.decCW();
        self.cfDone(true, true);
      }
    }

    protected void sendData() {
      if (packetNextHops.isBroadCast()) {
        sendDataBroadcast();
      } else {
        sendDataAnycast();
      }
    }

    protected void sendDataBroadcast() {

      config.adjustBitrates(packet, anno, packetNextHops, true, packet.getSize(), getRetry());

      // create data packet, clone the next-hop list because we are modifing it
      MacMcExORMessage.Data data = new MacMcExORMessage.Data(macCtx
          .getLocalAddr(), macCtx.forwardPref, packet);

      // set mode and transmit
      macCtx.setMode(MAC_MODE_XBROADCAST);
      long delay = config.getRxTxTurnaround();

      // Add PHY layer information to the MAC message
      PhyMessage phyMessage = new PhyMessage(data, config.dataRate, config.phy, txCtx.channel);
      long duration = phyMessage.txDuration();

      //printMsg(phyMessage, "Sending Data Broadcast");

      if (sendEvent.isActive())
        sendEvent.handle(data, anno, delay + duration + Constants.EPSILON_DELAY, config.dataRate, false);

      if (txCtx.packetNextHops.isBroadCast())
        log.debug(getNode().getNodeId() + "(" + JistAPI.getTime() + "): "
            + "Sending bcast packet to " + txCtx.packetNextHops + "on ch " + txCtx.channel);
      else
        log.info(getNode().getNodeId() + "(" + JistAPI.getTime() + "): "
            + "Sending anycast packet to " + txCtx.packetNextHops + "on ch " + txCtx.channel);

      radioEntity.transmit(phyMessage, delay, duration, anno);

      // wait for EOT, check for outgoing packet
      JistAPI.sleep(delay + duration + Constants.EPSILON_DELAY);
      self.cfDone(true, true);
    }

    /** send (opportunistic) anycast packet (--> slotted ack) */
    protected void sendDataAnycast() {

      // Do bit-rate selection according to algorithm.
      config.adjustBitrates(packet, anno, packetNextHops, true, packet.getSize(), getRetry());

      // estimate the number of candidates
      int toBeAcked = packetOpenHops.size();

      // calculate time budget for slotted ack (NAV)
      long msgDuration = 0;

      /** silence neighboring nodes by setting NAV in ACK packets. */
      if (config.useNAVinData) {
        msgDuration = Constants.PROPAGATION // Constants.PROPAGATION of this data packet
          + toBeAcked // for all acks
          * (config.transmitTime(MacMcExORMessage.Ack.getSize(toBeAcked), config.controlRate) // rate-dep part
              + Constants.PROPAGATION + config.getSifs()); // rate-undep part

        /** AZu: to avoid collisions between candidate 0 and 2 acks have different sizes */
        if (config.useVariableAckSize) {
          int addTime = 0;
          for (int i = 0; i < toBeAcked; i++)
            addTime += (toBeAcked - i - 1);
          msgDuration += addTime * config.getSifs();
        }
      }

      // do we have a retry???
      boolean retry = getRetry() > 0;

      // gather seq number
      short seqToSend = retry ? macCtx.seq : macCtx.incSeq();
      /** we are using the sequence numbers from application layer */
      if (config.useAppSeqNumbers) {
        if (!(!retry || macCtx.seq == getApplicationSeq(packet))) {
          log.error("MAC: wrong state: " + this);
          throw new RuntimeException("MAC: wrong state: " + this);
        }
        macCtx.seq = getApplicationSeq(packet);
        if (-1 == macCtx.seq) {
          macCtx.seq = retry ? macCtx.seq : macCtx.incSeq();
          // throw new RuntimeException("Could not determine udp message id");
        }
        seqToSend = macCtx.seq;
      }

      // create data packet
      MacMcExORMessage.Data data = new MacMcExORMessage.Data(
          (MacMcExORMessage.CSetAddress)packetNextHops.clone(), macCtx.getLocalAddr(),
          (int) msgDuration, seqToSend, retry, macCtx.forwardPref, packet);

      // set mode and transmit
      macCtx.setMode(MAC_MODE_XUNICAST);
      log.info(getNode().getNodeId() + "(" + JistAPI.getTime()
          + "): send packet with id=" + seqToSend + " (" + data.getDsts() + ")");

      long delay = getMacDelay();

      // Add PHY layer information to the MAC message

      PhyMessage phyMessage = new PhyMessage(data, config.dataRate, config.phy, txCtx.channel);
      long duration = phyMessage.txDuration();

      if (sendEvent.isActive())
        sendEvent.handle(data, anno, delay + duration + Constants.EPSILON_DELAY, config.dataRate, retry);

      log.info(getNode().getNodeId() + "(" + JistAPI.getTime() + "): "
          + "Sending packet to " + txCtx.packetNextHops + "on ch " + txCtx.channel);

      radioEntity.transmit(phyMessage, delay, duration, anno);

      // wait for EOT, and schedule ACK timer
      JistAPI.sleep(delay + duration + Constants.EPSILON_DELAY);

      // data tx completed + delay
      long ackDuration = config.transmitTime(MacMcExORMessage.Ack.getSize(toBeAcked), config.controlRate);
      long timeout = Constants.PROPAGATION + config.getSlotTime()
        + toBeAcked * (ackDuration + Constants.PROPAGATION + config.getSifs());

      /** AZu: to avoid collisions between candidate 0 and 2 acks have different sizes */
      if (config.useVariableAckSize) {
        int addTime = 0;
        for (int i = 0; i < toBeAcked; i++)
          addTime += (toBeAcked - i - 1);
        timeout += addTime * config.getSifs();
      }

      self.startTimer(timeout, MAC_MODE_SWFACK);
    }

    protected long getMacDelay() {
      return config.getRxTxTurnaround();
    }

    // ////////////////////////////////////////////////
    // retry
    //

    protected void retry() {
      if (!lstAcksReceived.isEmpty()) {
        /** we received at least one ack */
        cancelTimer();
        macCtx.decCW();
        self.cfDone(true, true);
        return;
      }

      if (shortRetry < Configuration.RETRY_LIMIT_SHORT) {
        if (retryEvent.isActive())
          retryEvent.handle(this.packet, this.anno, shortRetry, true);
        retryYes();
      } else {
        if (macTxFinished.isActive())
          macTxFinished.handle(packet, anno, false, shortRetry);
        retryNo();
      }
    }

    protected void retryYes() {
      shortRetry++;
      log.info(getNode().getNodeId() + "(" + JistAPI.getTime()
          + "): Retransmitting packet id=" + macCtx.seq + " (retry counter "
          + shortRetry + ").");

      //processEvent(Event.MAC_RETRY, new Object[] { this, new Byte(shortRetry) });

      macCtx.incCW();
      macCtx.setBackoff();
      doDifs();
    }

    protected void retryNo() {
      log.error(getNode().getNodeId() + "(" + JistAPI.getTime()
          + "): Maximum retransmission count of " + Configuration.RETRY_LIMIT_SHORT
          + " reached, discarding packet id=" + macCtx.seq);
      shortRetry = 0;

      if (discardEvent.isActive())
        discardEvent.handle(packet, anno);
      //processEvent(Event.MAC_DISCARD, new Object[] { this });

      // todo:
      // NetworkIpNotifyOfPacketDrop(packet, packetNextHop);
      macCtx.decCW();
      self.cfDone(true, true);
    }

    public void receiveForeign(MacMcExORMessage msg) {
      if (!config.passiveAcknowledgement)
        return;

      if(!packetNextHops.contains(msg.getSrc())
          || !(msg instanceof MacMcExORMessage.Data))
        return;

      MacMcExORMessage.Data data = (MacMcExORMessage.Data) msg;
      if (!(data.getBody() instanceof NetMessage.Ip)
          || !(packet instanceof NetMessage.Ip))
        return;

      NetMessage.Ip ipMsg1 = (NetMessage.Ip) data.getBody();
      NetMessage.Ip ipMsg2 = (NetMessage.Ip) packet;
      if (ipMsg1.getProtocol() != Constants.NET_PROTOCOL_MCEXOR
          || ipMsg2.getProtocol() != Constants.NET_PROTOCOL_MCEXOR)
        return;
      if (!ipMsg1.getSrc().equals(ipMsg2.getSrc())
          || !ipMsg1.getDst().equals(ipMsg2.getDst()))
        return;

      RouteMcExORMsg ExORMsg1 = (RouteMcExORMsg) ipMsg1.getPayload();
      RouteMcExORMsg ExORMsg2 = (RouteMcExORMsg) ipMsg2.getPayload();
      if (ExORMsg1.getSeqNr() != ExORMsg2.getSeqNr())
        return;

      log.info(getNode().getNodeId() + "(" + JistAPI.getTime()
          + "): received" + " passive ack from " + msg.getSrc()
          + " for id=" + msg.getSeq() + " (routing id="
          + ExORMsg1.getSeqNr() + ")");
      /*
      processEvent(Event.MAC_PACKETDONE, new Object[] { this,
          new Integer(packetOpenHops.size()), new Integer(1) });
      */
      cancelTimer();
      macCtx.decCW();
      self.cfDone(true, true);
    }

    /**
     * Return whether current packet is to be broadcast.
     *
     * @return whether current packet is to be broadcast.
     */
    protected boolean isBroadcast() {
      return packetNextHops.isBroadCast();
    }

    public byte getRetry() {
      if (null == packet)
        return 0; // no packet, no retry ...
      return shortRetry;
    }
  }

  /** Encapsulation of all receiving-related state. */
  protected class ReceivingCtx {

    /**
     * The own preference value, cached during a receive action. This is because
     * it must not change during a slotted ack session.
     */
    protected byte highestPref;

    /** id of the candidate from which we heard an ack */
    protected byte highestPrioCandidate;

    /**
     * Address of the mac entity initiated an opportunistic transfer <p/> only
     * valid during slotted ack and we are a candidate
     */
    protected MacAddress addrExorSrc;

    /* the used RF channel */
    protected RadioInterface.RFChannel channel;

    /** Packet msg annotation */
    protected MessageAnno anno;

    /** Flag indicates whether we already sent our ack. */
    protected boolean ackSent;

    public ReceivingCtx(final MacAddress addrExorSrc, final byte highestPrioCandidate,
       final byte highestPref, final RadioInterface.RFChannel rfChannel, final MessageAnno anno) {
      this.addrExorSrc = addrExorSrc;
      this.highestPref = highestPref;
      this.highestPrioCandidate = highestPrioCandidate;
      this.channel = rfChannel;
      this.anno = anno;
      this.ackSent = false;
    }

    public void receiveAck(MacMcExORMessage.Ack ack, MessageAnno ackAnno) {
      log.info(getNode().getNodeId() + "(" + JistAPI.getTime()
          + "): received ack from " + ack.getSrc() + " forwarder "
          + ack.getCandidateId() + " pref " + ack.getForwardPref());

      handleRecvAckEvent(ackAnno, ack);

      /** duplicate supression: the following happend - we are a higher candidate and therefore send our ack
       * before which however was not successfully received by this lower candidate. Now this candidate
       * believes that he or another lower candidate is the highest candidate. So he or the lower candidate
       * will wrongly forward this packet. The idea here is that we should not forward this packet.
       * Tradeoff between opportunistic and duplicates. What is more expensive??? */
      byte currCandId = ack.getCandidates().getCandId(ack.getSrc());
      byte ownCandId  = ack.getCandidates().getCandId(macCtx.getLocalAddr());

      //
      if (ackSent && currCandId > ownCandId && ack.getCandidateId() > highestPrioCandidate) {
        log.info(getNode().getNodeId() + "(" + JistAPI.getTime() + "): we shall forward this packet, "
          + " however a lower cand is not informed about this and we already sent our ack; "
            + " we drop this packet to prevent duplicates.");
        highestPrioCandidate = ack.getCandidateId();
        highestPref = ack.getForwardPref();
        return;
      }


      /*
       * check priorities, update if necessary. Update only if own preference is
       * higher than the one received. case 1) The ack comes from a higher
       * priorized candidate. Only if own preference is higher, anounce ourself
       * as forwarder. Otherwise take the received candidate. case 2) The ack
       * comes from a lower priorized candidate. Update the forwarding candidate
       * if a higher preference is recognized.
       */
      if (highestPref <= ack.getForwardPref()
          && highestPrioCandidate > ack.getCandidateId()) {
        highestPrioCandidate = ack.getCandidateId();
        highestPref = ack.getForwardPref();
      } else if (highestPref < ack.getForwardPref()) {
        highestPrioCandidate = ack.getCandidateId();
        highestPref = ack.getForwardPref();
      }
    }

    public void receiveData(final MacMcExORMessage.Data msg, final byte ownCandId, final MessageAnno anno) {

      boolean forwarder = false;

      if (log.isInfoEnabled())
        log.info(getNode().getNodeId() + "(" + JistAPI.getTime() + "): receiving packet with id " + msg.getSeq());

      /*
       * processes the complete ack phase. Enters at the point in time the ack
       * is received, and returns after finishing the slotted ack phase.
       */
      processAck(msg, ownCandId);

      // check highest prio candidate
      Util.assertion (highestPrioCandidate >= 0);

      // duplicate suppression
      if (msg.getRetry() && (macCtx.getSeqEntry(msg.getSrc()) == msg.getSeq()) && config.detectDuplicates) {

        if (duplicateEvent.isActive())
          duplicateEvent.handle(msg, anno, macCtx.getLocalAddr());

        log.warn(getNode().getNodeId() + "(" + JistAPI.getTime()
            + "): received " + " duplicate packet (" + msg.getSrc() + "," + msg.getSeq() + ")");
      } else {
        macCtx.updateSeqEntry(msg.getSrc(), msg.getSeq());

        // forward only if we are the forwarding node !
        if (highestPrioCandidate == ownCandId) {

          /** mark us as forwarder */
          forwarder = true;
          if (forwarderEvent.isActive())
            forwarderEvent.handle(msg, anno, macCtx.getLocalAddr());

          log.warn(getNode().getNodeId() + "(" + JistAPI.getTime()
              + "): I am the new forwarder (" + msg.getSrc() + "," + msg.getSeq() + ")");

          /* put some ExOR stuff like candidateSet into annos */
          anno.put(MessageAnno.ANNO_MAC_CANDIDATES, msg.getDsts());

          netEntity.receive(msg.getBody(), msg.getSrc(), macCtx.netId, false, anno);
        }
      }

      highestPref = brn.swans.Constants.PREFERENCE_MIN;

      if (receiveEvent.isActive()) {
        long duration = ((Long)anno.get(MessageAnno.ANNO_MAC_DURATION)).longValue() + config.getRxTxTurnaround();
        long recv_time = ((Long)anno.get(MessageAnno.ANNO_MAC_RECV_TIME)).longValue();
        // TODO use forward event for this purpose
        //receiveEvent.handle(recv_time, msg, anno, duration, forwarder);
        receiveEvent.handle(msg, anno, duration, recv_time, forwarder);
      }

      // release the mac layer here, because we must wait all ack slots !!!
      self.cfDone(false, false);
    }

    /**
     * Processes the complete slotted ack phase of the (Mc-)ExOR protocol. Realizes the
     * compressed slotted ack. Must be called right after the data packet was
     * received and returns when the complete ack phase is over.
     *
     * @param msg The received data message.
     * @param ownCandId Own id within the candidate set.
     */
    private void processAck(MacMcExORMessage.Data msg, byte ownCandId) {
      /** cancel all active timers */
      cancelTimer();
      /** entering ack mode */
      macCtx.setMode(MAC_MODE_XACK);

      /** Relay Preferences - if I am the final destination use the highest preference value */
      if (config.usePreferencedAck) {
        highestPref = macCtx.forwardPref;

        /** we detected a duplicate- send relay preference final to prevent other candidates in forwarding this packet. */
        if (config.detectDuplicates && msg.getRetry() && (macCtx.getSeqEntry(msg.getSrc()) == msg.getSeq())) {
          highestPref = brn.swans.Constants.PREFERENCE_FNL;
          log.info(getNode().getNodeId() + "(" + JistAPI.getTime()
              + "): Duplicate " + "for id=" + msg.getSeq() + " detected, sending pref final");
        }
        /** relay preference final for the last hop */
        if (isForMe(msg.getBody())) {
          Util.assertion (ownCandId == 0); /** this is something the routing layer ensures */
          highestPref = brn.swans.Constants.PREFERENCE_FNL;
        }
      } else {
        highestPref = brn.swans.Constants.PREFERENCE_FNL;
      }

      /** TX duration of such a slotted ack packet */
      final long durationAck = config.transmitTime(
          MacMcExORMessage.Ack.getSize(msg.getDsts().getCandidateSetSize()), config.controlRate);
      MacMcExORMessage.CSetAddress candidates = (MacMcExORMessage.CSetAddress) msg.getDsts().clone();
      int expectedCandId = 0; /** we expect to receive the ack from this candidate */
      JoinSlottedAck(candidates, candidates.getCandidateSetSize(),
          expectedCandId, ownCandId, msg.getSrc(), msg.getSeq(), durationAck);
    }

    /**
     * If we receive the data packet or an ack of a higher candidate we have to join the slotted ack process.
     * @param candidates the candidates enlisted in the received data packet or ack
     * @param slots overall number of candidates
     * @param expectedCandId we join the slotted ack with this offset; this can happen when we missed the data packet
     * @param ownCandId our own candidate id
     * @param srcOfData the originator of the anycast packet
     * @param seq the used sequence id
     * @param durationAck the time it take to transmit one ack
     */
    private void JoinSlottedAck(MacMcExORMessage.CSetAddress candidates, int slots, int expectedCandId, int ownCandId,
        MacAddress srcOfData, int seq, long durationAck) {

      int missedAcks = 0;

      if (config.useCompression) {
        /**
         * We are using the compressed slotted ack. The idea here is that if a candidate observes that a higher candidate
         * missed the data packet (no received ack) it prematurely sends its own ack packet. This is important to avoid
         * points in time where the medium is idle for more than DIFS and therefore competing stations to start their
         * own transmissions which would collide with subsequent acks ...
         */

        /** this variable indicates the id of the candidate for which we expect an ack. due to elapsing time this
         * variable is incremented */

        // Check if higher priorized candidates send their ack
        while (expectedCandId <= ownCandId) {
          JistAPI.sleepBlock(config.getSifs() - config.getRxTxTurnaround());

          /** Get reference noise level */
          double refNoise = ((RadioInterface.Noise)radioEntity).getNoise_mW();
          log.info(getNode().getNodeId() + "(" + JistAPI.getTime() + "): refNoise: " + refNoise);

          /*
           * All expect current node should wait for the point in time where the
           * ack should arrive. Then they check if the medium is idle or the
           * current candidate is sending.
           */
          while (expectedCandId < ownCandId) {
            /*
             * Did not receive the ack from a previous (higher) candidate, so wait until the
             * next round (until it is our round).
             */
            JistAPI.sleepBlock(config.missingAckSpace); // normally SIFS
            Util.assertion (Constants.RADIO_MODE_SLEEP != macCtx.radioMode &&
                Constants.RADIO_MODE_TRANSMITTING != macCtx.radioMode);

            if (log.isInfoEnabled()) {
              log.info(getNode().getNodeId() + "(" + JistAPI.getTime()
                  + "): try to overhear acks of higher candidates; ownCandId: " + ownCandId + " id=" + seq);
            }

            /** try to detect an ack by comparing the current noise with the one we measured SIFS before */
            double noise = ((RadioInterface.Noise)radioEntity).getNoise_mW();
            log.info(getNode().getNodeId() + "(" + JistAPI.getTime() + "): noise: " + noise);

            boolean ackDetected = (Constants.RADIO_MODE_RECEIVING == macCtx.radioMode
                || Constants.RADIO_MODE_SENSING == macCtx.radioMode)
                && noise > config.refNoiseFactor * refNoise;

            if (ackDetected) {
              if (log.isInfoEnabled()) {
                log.info(getNode().getNodeId() + "(" + JistAPI.getTime()
                    + "):" + ownCandId + "= we detected the ack of the current candidate "
                    + expectedCandId + " -> we have to wait until he finished; seq="
                    + seq + "(rmode=" + macCtx.radioMode + ")");
              }
              /** we detected the ack of a higher candidate ... let him be the next forwarder
               * problem: we were not able to decode the ack ... what about relay prefs???? */
              if (config.updatePrioOnNoiseDetection && highestPrioCandidate > expectedCandId) {
                log.info(getNode().getNodeId() + "(" + JistAPI.getTime()
                    + "): Updating highestPrioCandidate by noise detection: " + ownCandId + "/" + expectedCandId + " id=" + seq);

                highestPrioCandidate = (byte)expectedCandId;
              }

              /** AZu: to avoid collisions between candidate 0 and 2 acks have different sizes */
              if (config.useVariableAckSize) {
                int addTime = 0;
                if (slots > 1)
                  addTime = (slots - expectedCandId - 1);
                JistAPI.sleepBlock(addTime * config.getSifs() + durationAck - config.missingAckSpace + config.getRxTxTurnaround());
              } else {
                /** The current candidate did sent the ack packet, so wait until finished */
              JistAPI .sleepBlock(durationAck - config.missingAckSpace + config.getRxTxTurnaround());
              }

              /** wait for the ack of the next candidate */
              break;
            }

            if (log.isInfoEnabled()) {
              log.info(getNode().getNodeId() + "(" + JistAPI.getTime()
                  + "):" + ownCandId + "= we did NOT detect the ack of the current candidate " + expectedCandId + " seq=" + seq);
            }
            /** we missed an ack */
            missedAcks++;

            expectedCandId++;
        }
          expectedCandId++;
      }
      } else { /** no compression - simple time division multiplexing */
        long waitTime = ownCandId * (config.getSifs() + durationAck) + config.getSifs() - config.getRxTxTurnaround();
        /** AZu: to avoid collisions between candidate 0 and 2 acks have different sizes */
        if (config.useVariableAckSize) {
          int addTime = 0;
          for (int i = slots - ownCandId; i < slots; i++)
            addTime += i;
          waitTime += addTime * config.getSifs();
        }

        JistAPI.sleepBlock(waitTime);
      }

      // Note: the h.p.cand. field could be modified during the sleep phase.
      log.info(getNode().getNodeId() + "(" + JistAPI.getTime()
          + "): cand/slot " + ownCandId + "/" + ownCandId
          + " sending ack for id=" + seq + " (hp-cand "
          + rxCtx.highestPrioCandidate + ", hp-pref" + rxCtx.highestPref
          + ", own-pref" + macCtx.forwardPref + ")");
      /** the time has come to send our ack packet. */
      sendAck(srcOfData, candidates, rxCtx.highestPrioCandidate,
          rxCtx.highestPref, macCtx.forwardPref, (short) seq);

      /** schedule upcall */
      long acksToWait = slots - ownCandId - 1;

      if (config.useCompression) {
        /** due to the slotted ack we will return to early. wait additional time. */
        acksToWait += missedAcks;
      }

      Util.assertion (0 <= acksToWait && acksToWait < slots);

      /*
       * note: +1 because otherwise the events 'state change to idle' and 'ack
       * receiption' would clash on certain nodes. we are using a discrete
       * simulator and we must ensure that the execution order is correct (wrt the
       * happened-before relation).
       *
       * due to the compressed ack, the point in time when the ack phase is
       * over is not necessariliy fixed here, we must further listen and see if
       * some acks do not return.
       */
//      JistAPI.sleepBlock(config.getRxTxTurnaround() + durationAck + (durationAck + config.getSifs())
//          * acksToWait + Constants.EPSILON_DELAY);

      /** AZu: to avoid collisions between candidate 0 and 2 acks have different sizes */
      if (config.useVariableAckSize) {
        int addTime = 0;
        for (int i = ownCandId + 1; i < slots; i++)
          addTime += (slots - i - 1);
      JistAPI.sleepBlock(config.getRxTxTurnaround() + durationAck + (durationAck + config.getSifs())
            * acksToWait + Constants.EPSILON_DELAY + addTime * config.getSifs());
      } else {
        JistAPI.sleepBlock(config.getRxTxTurnaround() + durationAck + (durationAck + config.getSifs())
          * acksToWait + Constants.EPSILON_DELAY);
      }


      log.info(getNode().getNodeId() + "(" + JistAPI.getTime() + "): cand "
          + ownCandId + " slotted ack for id=" + seq + " complete");
    }

    private void sendAck(MacAddress srcOfData, MacMcExORMessage.CSetAddress candidates,
        byte priority, byte forwardPref, byte ownPref, short seq) {

      int overallCandidates = candidates.getCandidateSetSize();
      int cId = candidates.getCandId(macCtx.getLocalAddr());
      /** number of acks following this (slotted) ack */
      int followingAcks = (overallCandidates - cId - 1);

      Util.assertion(followingAcks >= 0);

      // calculate ack NAV
      long msgDuration = 0;

      /** silence neighboring nodes by setting NAV in ACK packets. */
      if (config.useNAVinAcks) {
        msgDuration = Constants.PROPAGATION // Constants.PROPAGATION of this ack packet
          + followingAcks // for all following acks
          * (config.transmitTime(MacMcExORMessage.Ack.getSize(overallCandidates), config.controlRate) // rate-dep part
              + Constants.PROPAGATION + config.getSifs()); // rate-undep part
        /** AZu: to avoid collisions between candidate 0 and 2 acks have different sizes */
        if (config.useVariableAckSize) {
          int addTime = 0;
          for (int i = cId + 1; i < overallCandidates; i++)
            addTime += (overallCandidates - i - 1);
          msgDuration += addTime * config.getSifs();
        }
      }

      //TODO think about this !!!!!!!!
//      config.adjustBitrates(dataRcvd, dataRcvdAnno, dataRcvd.getSrc(), false,
//          MacMcExORMessage.Ack.getSize(overallCandidates), 0);

      // create ack
      MacMcExORMessage.Ack ack = new MacMcExORMessage.Ack(macCtx.getLocalAddr(),
          srcOfData, (int)msgDuration, priority, forwardPref, ownPref, seq, candidates);

      long delay = config.getRxTxTurnaround();

      // transmit with no delay, we waited for the right time slot in receiveData

      Util.assertion (MAC_MODE_XACK == macCtx.getMode());

      // Add PHY layer information to the MAC message
      PhyMessage phyMessage = new PhyMessage(ack, config.controlRate, config.phy, rxCtx.channel);
      long duration = phyMessage.txDuration();

      /** AZu: to avoid collisions between candidate 0 and 2 acks have different sizes */
      if (config.useVariableAckSize && overallCandidates > 1)
        duration  += (overallCandidates - cId - 1) * config.getSifs();

      if (sendEvent.isActive())
        sendEvent.handle(ack, rxCtx.anno, delay + duration + Constants.EPSILON_DELAY, config.controlRate, false);

      log.info(getNode().getNodeId() + "(" + JistAPI.getTime() + "): sendAck() - duration = " + duration);

      /** mark ack as sent; this means we have no chance to persuade other candidates */
      ackSent = true;

      radioEntity.transmit(phyMessage, delay, duration, rxCtx.anno);
    }
  }

  /** static MAC configuration (delayChannelSwitch, useCompression) */
  protected Configuration                config;
  /** dynamic MAC configuration: forwardPref, radioMode, mode */
  protected MacCtx                       macCtx;
  /** context of an ongoing transmission (packet, packetNextHops, packetOpenHops) */
  protected TransmissionCtx              txCtx;
  /** context of an ongoing reception (highestPrioCandidate) */
  protected ReceivingCtx                 rxCtx;

  /**
   * Instantiates a new Multi-Channel Opportunistic Protocol.
   *
   * @param addr local mac address
   * @param radioInfo radio properties
   * @param homeChannel the home channel of this multi-channel mac.
   * @param channels the number of available rf channels.
   * @param useCompression indicates whether we want to use the compressed slotted ack
   * @param usePreferencedAck Whether to use preferences in ack packets (or send max all the time)
   * @param useAppSeqNumbers Whether to use sequence numbers to detect duplicates
   */
  public MacMcExOR(MacAddress addr, RadioInfo radioInfo,
                   RadioInterface.RFChannel homeChannel, int channels,
                   boolean useCompression, boolean usePreferencedAck,
                   boolean useAppSeqNumbers, long delayChannelSwitch,
                   double refNoiseFactor, boolean updatePrioOnNoiseDetection,
                   boolean useVariableAckSize, boolean useNAVinAcks, boolean useNAVinData,
                   int candidateSetSize, boolean testRun) {

    /** make use a 802.11 like physical layer */
    Phy802_11 phy = new Phy802_11(radioInfo);

    initCtx(delayChannelSwitch, addr, homeChannel, channels, useCompression, usePreferencedAck,
        useAppSeqNumbers, radioInfo, phy, refNoiseFactor, updatePrioOnNoiseDetection, useVariableAckSize,
        useNAVinAcks, useNAVinData, candidateSetSize, testRun);

    /** JiST proxy */
    self = (MacInterface.MacDcf) JistAPI.proxy(
        new MacInterface.MacDcf.Dlg(this), MacInterface.MacDcf.class);
  }

  protected void initCtx(long delayChannelSwitch, MacAddress addr, RadioInterface.RFChannel homeChannel,
                         int channels, boolean useCompression, boolean usePreferencedAck, boolean useAppSeqNumbers,
                         RadioInfo radioInfo, Phy802_11 phy, double refNoiseFactor, boolean updatePrioOnNoiseDetection,
                         boolean useVariableAckSize, boolean useNAVinAcks, boolean useNAVinData, int candidateSetSize,
                         boolean testRun) {
    config = new Configuration(delayChannelSwitch, addr, homeChannel, channels, useCompression,
        usePreferencedAck, useAppSeqNumbers, true, false, radioInfo, phy, refNoiseFactor,
        updatePrioOnNoiseDetection, useVariableAckSize, useNAVinAcks, useNAVinData, candidateSetSize,
        testRun);

    macCtx  = new MacCtx();
    txCtx   = null;
    rxCtx   = null;
  }

  protected void initRxCtx(final MacAddress addrExorSrc, final byte highestPrioCandidate,
       final byte highestPref, final RadioInterface.RFChannel rfChannel, final MessageAnno anno) {
    rxCtx = new ReceivingCtx(addrExorSrc, highestPrioCandidate, highestPref, rfChannel, anno);
  }

  protected void initTxCtx(Message msg, MacMcExORMessage.CSetAddress nextHops,
                           RadioInterface.RFChannel channel, MessageAnno anno) {
    txCtx = new TransmissionCtx(msg, nextHops, channel, anno);
  }
  // ////////////////////////////////////////////////
  // entity hookup
  //

  /**
   * Retrieve a proxy to the own object
   *
   * @return The self-referencing proxy
   */
  public MacInterface getProxy() {
    return self;
  }

  public Phy802_11 getPhy() {
    return config.phy;
  }

  public MacInfo getMacInfo() {
    return null;
  }

  /**
   * Hook up with the radio entity.
   *
   * @param radio radio entity
   */
  public void setRadioEntity(RadioInterface radio) {
    if (!JistAPI.isEntity(radio))
      throw new IllegalArgumentException("expected entity");
    radioEntity = radio;
  }

  /**
   * Hook up with the network entity.
   *
   * @param net network entity
   * @param netid network interface number
   */
  public void setNetEntity(NetInterface net, byte netid) {
    if (!JistAPI.isEntity(net))
      throw new IllegalArgumentException("expected entity");
    netEntity     = net;
    macCtx.netId  = netid;
  }

  /**
   * @param macFeedback set the feedback interface
   */
  public void setRXTXFeedback(MacRXTXFeedbackInterface macFeedback) {
    if(!JistAPI.isEntity(macFeedback))
      throw new IllegalArgumentException("expected entity");
    this.feedback = macFeedback;
  }

  // ////////////////////////////////////////////////
  // send-related functions
  //

  /**
   * MacInterface; called by NET
   *
   * @param msg the message we want to send
   * @param nextHop the next hop of this message or MacAddress.NULL in case of opportunistic routing
   * @param anno the message annotations.
   *  If {@param nextHop} is MacAddress.NULL the next hops candidates
   *  must be given in the annotations under the key {@link MessageAnno.ANNO_MAC_CANDIDATES}.
   *  If we are using multiple RF Channels the channel must be given in the annotations under
   *  the key {@link MessageAnno.ANNO_MAC_RF_CHANNEL}.
   */
  public void send(Message msg, MacAddress nextHop, MessageAnno anno) {

    MacMcExORMessage.CSetAddress addr;
    if (nextHop != null && !nextHop.equals(MacAddress.NULL)) {
      /** legacy support: convert next hop into opportunistic hop */
      addr = new MacMcExORMessage.CSetAddress(nextHop);
    } else {
      // retrieve the anycast address from annotations
      addr = (MacMcExORMessage.CSetAddress)anno.get(MessageAnno.ANNO_MAC_CANDIDATES);
    }
    /** retrieve forward pref as well as RF Channel of the next hop from annotations. */
    Byte forwardPrefObj = (Byte)anno.get(MessageAnno.ANNO_MAC_FORWARD_PREF);
    RadioInterface.RFChannel txChannel
            = (RadioInterface.RFChannel)anno.get(MessageAnno.ANNO_MAC_RF_CHANNEL);

    /** assertations */
    if (Main.ASSERT) {
      if (addr == null) throw new RuntimeException("send() - nextHop is null!");
      if (forwardPrefObj == null) throw new RuntimeException("send() - forwardPrefObj is null!");
      /** we have a multi-channel protocol */
      if (txChannel == null) throw new RuntimeException("send() - txChannel is null!");
    }

    /** make sure that we are no currently in the process of sending a frame */
    if (txCtx != null)
      throw new RuntimeException("TX context has to be closed.");

    /** init new tx ctx */
    initTxCtx(msg, addr, txChannel, anno);

    /** update forward pref */
    macCtx.setForwardPref(forwardPrefObj.byteValue());

    if (log.isInfoEnabled()) {
      //if (!(txCtx.packetNextHops.isBroadCast()))
        log.debug(getNode().getNodeId() + "(" + JistAPI.getTime() + "): "
            + "Prepare for sending packet to " + txCtx.packetNextHops + "on ch " + txCtx.channel);
    }

    /** Check if we have to proceed a channel switch. In this case a channel switch is initiated.
     *  If this is not the case we are iniatiated the normal TX process (DIFS or backoff) */
    if (macCtx.getMode() == MAC_MODE_SIDLE) {
      if (!macCtx.channel.equals(txCtx.channel)) {
        /** we are on the wrong RF channel; initiate channel switch. */
        doChannelSwitch(txCtx.channel);
      } else if (macCtx.getMode() == MAC_MODE_SIDLE) {
        if (!macCtx.isCarrierIdle()) {
          /** MAC is not idle; process a backoff */
          macCtx.setBackoff();
        }
        /** make DIFS and start TX */
        doDifs();
      }
    }
  }

  /** Process a DIFS. */
  protected void doDifs() {
    if (macCtx.isRadioIdle()) {
      if (macCtx.waitingNav()) {
        startTimer(macCtx.getNav() - JistAPI.getTime(), MAC_MODE_SNAV);
      } else {
        startTimer(macCtx.needEifs ? config.getTxEifs() : config.getTxDifs(), MAC_MODE_DIFS);
      }
    } else {
      idle();
    }
  }

  /**
   * Collision free send sequence complete.
   *
   * @param backoff is a backoff required
   * @param delPacket is processing for this packet complete
   */
  public void cfDone(boolean backoff, boolean delPacket) {

    log.debug(getNode().getNodeId() + "(" + JistAPI.getTime()
        + "): cfDone(" + backoff + "," + delPacket + ").");

    if (backoff) {
      macCtx.setBackoff();
    }

    rxCtx = null;
    doDifs();

    if (delPacket) {
      // feedback to routing
      {
        FeedbackMessageAnno fAnno = (FeedbackMessageAnno) txCtx.anno.clone();
        fAnno.put(FeedbackMessageAnno.ANNO_MAC_RECV_ACKS, new LinkedList(txCtx.lstAcksReceived));
        // MessageAnno.ANNO_MAC_CANDIDATES is already set
        feedback.txfeedback(txCtx.packet, fAnno);
      }

      Message packet    = txCtx.packet;
      MessageAnno anno  = txCtx.anno;
      byte retry        = txCtx.shortRetry;
      boolean isbcast   = txCtx.isBroadcast();

      /** TX completed */
      txCtx             = null;
      idle();

      /** After a TX ends we have to check whether we have to return to our home channel */
      if (!macCtx.channel.equals(config.homeChannel)) {
        /** return to home channel */
        doChannelSwitch(config.homeChannel);
      } else {
        /** broadcast packets are not acknowledged; so we will miss the macTxFinished event. */
        if (isbcast) {
          if (macTxFinished.isActive())
            macTxFinished.handle(packet, anno, true, retry);
        }
        log.debug(getNode().getNodeId() + "(" + JistAPI.getTime()
            + "): cfDone finished, pump for new packet.");

        /** pump new packet from NET layer */
        netEntity.endSend(packet, macCtx.netId, anno);
      }
    }
  }

  /**
   * MacInterface; called by Radio (Radio has received a packet for mac to process.).
   * @param msg the new message
   * @param anno anno of the message
   */
  public void receive(Message msg, MessageAnno anno) {

    PhyMessage phyMsg = (PhyMessage) msg;

    /** assertations */
    if (!(phyMsg.getPayload() instanceof MacMcExORMessage))
      throw new RuntimeException("Message payload is of wrong type.");

    /** feed the bitrate algorithm */
    config.dataRcAlg.reportPacketRx(phyMsg);

    if (anno != null) {
      anno.put(MessageAnno.ANNO_MAC_BITRATE, phyMsg.getPayloadRateObj());
      anno.put(MessageAnno.ANNO_MAC_DURATION, new Long(phyMsg.txDuration()));
      anno.put(MessageAnno.ANNO_MAC_RECV_TIME, new Long(JistAPI.getTime()));
    }

    MacMcExORMessage mcExORMsg = (MacMcExORMessage)phyMsg.getPayload();

    if (macCtx.getMode() == MAC_MODE_SWITCH)
      return;

    // feedback to net (and routing)
    feedback.rxfeedback(mcExORMsg, anno);

    /** last reception was successful */
    macCtx.needEifs = false;

    receivePacket(mcExORMsg, anno);
  }

  protected void receivePacket(MacMcExORMessage mcExORMsg, MessageAnno anno) {
    switch (mcExORMsg.getType()) {
      case MacMcExORMessage.TYPE_DATA: {
        /** handle data packet reception */
        receiveData((MacMcExORMessage.Data) mcExORMsg, anno);
        break;
      }
      case MacMcExORMessage.TYPE_ACK: {
        /** handle ack reception */
        receiveAck((MacMcExORMessage.Ack) mcExORMsg, anno);
        break;
      }
      default:
        throw new RuntimeException("Illegal packet type.");
    }
  }

  /**
   * Handle an incoming data packet. We distinguish between 2 kinds: broadcast and anycast (+unicast)
   *
   * @param msg the incoming data packet
   * @param anno the corresponding annotations.
   */
  protected void receiveData(MacMcExORMessage.Data msg, MessageAnno anno) {

    if (!msg.getDsts().contains(macCtx.getLocalAddr()) && !msg.getDsts().isBroadCast()) {
      /** Unicast packet and I am not enlisted in the list of candidates */
      receiveForeign(msg, anno);
      return;
    }

    // not in standard, but ns-2 does.
    // decCW();
    // shortRetry = 0;
    if (!macCtx.isAwaitingResponse()) {
      if (msg.getDsts().isBroadCast()) {
        /** handle broadcast */
        if (receiveEvent.isActive()) {
          long duration = ((Long)anno.get(MessageAnno.ANNO_MAC_DURATION)).longValue() + config.getRxTxTurnaround();
          long recv_time = ((Long)anno.get(MessageAnno.ANNO_MAC_RECV_TIME)).longValue();
          receiveEvent.handle(msg, anno, duration, recv_time, false);
        }
        /** handover the packet to NET layer */
        netEntity.receive(msg.getBody(), msg.getSrc(), macCtx.netId, false, anno);
        self.cfDone(false, false);
      } else {
        /** case anycast (+unicast) */
        RadioInterface.RFChannel rxChannel
                = (RadioInterface.RFChannel)anno.get(MessageAnno.ANNO_MAC_RF_CHANNEL);
        if (rxChannel == null)
          throw new RuntimeException(getNode().getNodeId() + "(" + JistAPI.getTime() + "): missing channel annotation.");
        /** my id within the cset; reverse order: 0 means highest priority */
        final byte ownCandId  = msg.getDsts().getCandId(macCtx.getLocalAddr());
        /** create new reception context */
        initRxCtx(msg.getSrc(), ownCandId, brn.swans.Constants.PREFERENCE_MIN, rxChannel, anno);
        /** initiate the slotted ack process */
        rxCtx.receiveData(msg, ownCandId, anno);
      }
    }
  }

  /**
   * Handle incoming ack packet. In case of opportunistic protocols the slotted ack is initiated.
   * @param ack the ack packet.
   * @param anno the corresponding annotation.
   */
  private void receiveAck(MacMcExORMessage.Ack ack, MessageAnno anno) {
    MacAddress dst = ack.getDst();

    /** case 1: we sent the data packet and wait for acks */
    if (macCtx.getMode() == MAC_MODE_SWFACK && macCtx.getLocalAddr().equals(dst)) {
      Util.assertion(null != txCtx && macCtx.seq == ack.getSeq());

      txCtx.receiveAck(ack, anno);
    }
    /** case 2: we received the data packet and are one of the candidates */
    else if (MAC_MODE_XACK == macCtx.getMode()
        && rxCtx.addrExorSrc.equals(dst)) {
      Util.assertion (null != rxCtx);

      rxCtx.receiveAck(ack, anno);
    }
    /** case 3: we receive ack and are candidate, but missed the data packet */
    else if (ack.getCandidates().contains(macCtx.getLocalAddr())) {
      if (macCtx.getMode() != MAC_MODE_SIDLE
          && macCtx.getMode() != MAC_MODE_DIFS
          && macCtx.getMode() != MAC_MODE_SBO
          && macCtx.getMode() != MAC_MODE_SNAV)
        return;

      log.info(getNode().getNodeId() + "(" + JistAPI.getTime()
          + "): received ack from " + ack.getSrc() + " and we are candidate , but missed the data packet;"
          + " pref " + ack.getForwardPref());

      handleRecvAckEvent(anno, ack);

      /*
       * nugget: Duplicate suppression through acknowledgement in the case of
       * overhearing the data packet. A candidate sends an ack packet even if he
       * missed the data packet and only received another ack. Therefore he finds
       * himself in the ack packets candidate set (list of addresses).
       */
      int currCandId = 1 + (int) ack.getCandidates().getCandId(ack.getSrc());
      byte ownCandId = ack.getCandidates().getCandId(macCtx.getLocalAddr());

      // Did we miss our slot?
      if (currCandId > ownCandId)
        return;

      cancelTimer();
      /** switch to TX ack mode */
      macCtx.setMode(MAC_MODE_XACK);

      RadioInterface.RFChannel rxChannel
              = (RadioInterface.RFChannel)anno.get(MessageAnno.ANNO_MAC_RF_CHANNEL);
      if (rxChannel == null)
        throw new RuntimeException(getNode().getNodeId() + "(" + JistAPI.getTime() + "): missing channel annotation.");
      /** create new reception ctx */
      initRxCtx(dst, ack.getCandidateId(), ack.getForwardPref(), rxChannel, anno);

      /** join the slotted ack at a later point in time */
      rxCtx.JoinSlottedAck((MacMcExORMessage.CSetAddress)ack.getCandidates().clone(),
          ack.getCandidates().getCandidateSetSize(), currCandId, ownCandId,
          dst, ack.getSeq(), config.transmitTime(ack, config.controlRate));

      // Put seq into cache for duplicate detection
      macCtx.updateSeqEntry(dst, ack.getSeq());

      // release the mac layer
      self.cfDone(false, false);
    }
    /** otherwise: ack does not belong to us */
    else {
      receiveForeign(ack, anno);
    }
  }

  private void handleRecvAckEvent(MessageAnno anno, MacMcExORMessage.Ack ack) {
    if (receiveEvent.isActive()) {
      long duration = ((Long)anno.get(MessageAnno.ANNO_MAC_DURATION)).longValue() + config.getRxTxTurnaround();
      long recv_time = ((Long)anno.get(MessageAnno.ANNO_MAC_RECV_TIME)).longValue();
      if (config.useVariableAckSize) duration += (ack.getCandidates().getCandidateSetSize()
          - ack.getCandidates().getCandId(ack.getSrc()) - 1) * config.getSifs();
      receiveEvent.handle(ack, anno, duration, recv_time, false);
    }
  }

  /**
   * Handle incoming packets (+ack) which are not destined for us.
   * @param msg incoming message.
   * @param anno corresponding annotation.
   */
  protected void receiveForeign(MacMcExORMessage msg, MessageAnno anno) {

    /** Nugget: passive ack */
    if (txCtx != null)
      txCtx.receiveForeign(msg);

    /** update nav, if possible */
    if (!macCtx.isAwaitingResponse()) {
      long currentTime = JistAPI.getTime();
      long nav2 = currentTime + msg.getDuration() + Constants.EPSILON_DELAY;
      if (nav2 > macCtx.getNav()) {
        macCtx.setNav(nav2, msg.getSrc());
        if (macCtx.isRadioIdle() && null != txCtx)
          startTimer(macCtx.getNav() - currentTime, MAC_MODE_SNAV);
      }
    }

    /** introspect packet */
    if (config.passiveAcknowledgement) {
      if (msg instanceof MacMcExORMessage.Ack) {
        MacMcExORMessage.Ack ack = (MacMcExORMessage.Ack) msg;

        /** Put seq into cache for duplicate detection */
        macCtx.updateSeqEntry(ack.getDst(), msg.getSeq());
      } else {
        /** Put seq into cache for duplicate detection */
        macCtx.updateSeqEntry(msg.getSrc(), msg.getSeq());
      }
    }

    long recv_time = ((Long)anno.get(MessageAnno.ANNO_MAC_RECV_TIME)).longValue();
    long duration = ((Long)anno.get(MessageAnno.ANNO_MAC_DURATION)).longValue() + config.getRxTxTurnaround();

    duration = handleRecvForeignAckEvent(msg, duration, anno, recv_time);

    if (macCtx.promisc && msg.getType() == MacMcExORMessage.TYPE_DATA) {
      if (receiveEvent.isActive())
        receiveEvent.handle(msg, anno, duration, recv_time, false);

      MacMcExORMessage.Data macDataMsg = (MacMcExORMessage.Data) msg;
      /** hand over packet to NET layer */
      netEntity.receive(macDataMsg.getBody(), macDataMsg.getSrc(), macCtx.netId, true, null);
    }
  }

  private long handleRecvForeignAckEvent(MacMcExORMessage msg, long duration, MessageAnno anno, long recv_time) {
    /** event handling; e.g. used by Javis event handler */
    if (receiveForeignEvent.isActive() && msg.getType() != MacMcExORMessage.TYPE_DATA) {
        if (config.useVariableAckSize && msg.getType() == MacMcExORMessage.TYPE_ACK) {
          MacMcExORMessage.Ack ack = (MacMcExORMessage.Ack)msg;
          long addTime = (ack.getCandidates().getCandidateSetSize()
              - ack.getCandidates().getCandId(ack.getSrc()) - 1) * config.getSifs();
          duration += addTime;
        }
        receiveForeignEvent.handle(msg, anno, duration, recv_time, false);
    }

    if (receiveForeignEvent.isActive() && msg.getType() == MacMcExORMessage.TYPE_DATA && !macCtx.promisc)
        receiveForeignEvent.handle(msg, anno, duration, recv_time, false);
    return duration;
  }

  // ////////////////////////////////////////////////
  // channel management
  //

  /**
   * This method processes the required channel switch. It switches to the MAC mode MAC_MODE_SWITCH.
   * @param channel switch to this channel.
   */
  protected void doChannelSwitch(RadioInterface.RFChannel channel) {
    /** make sure that the MAC is in the right mode to switch the channel */
    switch (macCtx.getMode()) {
      case MAC_MODE_SIDLE:
      case MAC_MODE_DIFS:
      case MAC_MODE_SBO:
      case MAC_MODE_SNAV:
        /** It is allowed to switch the channel in mode idle, difs, sbo, snav */
        break;
      case MAC_MODE_SWFACK:
      case MAC_MODE_XUNICAST:
      case MAC_MODE_XBROADCAST:
      case MAC_MODE_XACK:
      case MAC_MODE_SWITCH:
      default:
        /** it is prohibited to process a channel switch during this states. */
        throw new RuntimeException("It is not allowed to switch the RF channel in MAC mode " + macCtx.getMode());
    }

    /** make sure that the Radio is in the right mode to switch the channel */
    if (macCtx.radioMode == Constants.RADIO_MODE_TRANSMITTING)
      throw new RuntimeException("It is not allowed to switch the RF channel in Radio mode " + macCtx.getMode());

    /** return if we are already on the right channel */
    if (channel.equals(macCtx.channel)) {
      log.info(getNode().getNodeId() + "(" + JistAPI.getTime()
          + "): Try to switch to channel " + channel + ", but already tuned in.");
      return;
    }

    if (log.isDebugEnabled())
      log.debug(getNode().getNodeId() + "(" + JistAPI.getTime()
          + "): Started channel switch from " + macCtx.channel +  " to channel " + channel);

    /** cancel all currently active timers to avoid interference with channel switch */
    cancelTimer();

    /** to avoid event collisions with the same schedule time */
    JistAPI.sleep(Constants.EPSILON_DELAY);
    /** process channel switch on radio */
    radioEntity.setChannel(channel, config.delayChannelSwitch);
    JistAPI.sleep(Constants.EPSILON_DELAY);

    /** schedule the end of the switch */
    self.startTimer(config.delayChannelSwitch, MAC_MODE_SWITCH);
  }

  /**
   * This method is used to change the home channel of this mode. In general it is called once at start time.
   * However, more complex protocols can use it to change their home channels during runtime.
   * @param newHomeChannel the new home channel of this node.
   */
  public void setHomeChannel(RadioInterface.RFChannel newHomeChannel) {
    /** some assertations. */
    if (newHomeChannel.equals(RadioInterface.RFChannel.INVALID_RF_CHANNEL) ||
        newHomeChannel.getChannel() > config.channels)
      throw new IllegalArgumentException("Wrong RF Channel.");

    /** Prevent correct event ordering */
    JistAPI.sleep(Constants.EPSILON_DELAY);

    /** set new home channel on config as well as on mac ctx */
    config.homeChannel  = newHomeChannel;
    macCtx.channel      = newHomeChannel;

    log.debug(getNode().getNodeId() + "(" + JistAPI.getTime() + "):  setHomeChannel to " + config.homeChannel);
    /** switch to home channel */
    radioEntity.setChannel(config.homeChannel, 0);

    /** Prevent correct event ordering */
    JistAPI.sleep(Constants.EPSILON_DELAY);
  }

  // ////////////////////////////////////////////////
  // radio mode
  //

  /**
   * MacInterface; called by Radio
   * @param mode the new radio mode
   */
  public void setRadioMode(byte mode) {
    log.debug(getNode().getNodeId() + "(" + JistAPI.getTime() + " radio ["
        + macCtx.channel.getChannel() + "] mode switch from " + macCtx.radioMode + " to " + mode);

    /** handle radio mode changed event */
    if (macCtx.radioMode != mode && radioModeChanged.isActive())
      radioModeChanged.handle(macCtx.radioMode, mode);

    macCtx.setRadioMode(mode);
    switch (mode) {
      case Constants.RADIO_MODE_IDLE:
        radioIdle();
        break;
      case Constants.RADIO_MODE_SENSING:
        radioBusy();
        break;
      case Constants.RADIO_MODE_RECEIVING:
        radioBusy();
        break;
      case Constants.RADIO_MODE_SLEEP:
        radioBusy();
        break;
    }
  }

  /** Sets the radio busy. */
  protected void radioBusy() {
    switch (macCtx.getMode()) {
      case MAC_MODE_SBO:
        macCtx.pauseBackoff();
        idle();
        break;
      case MAC_MODE_DIFS:
      case MAC_MODE_SNAV:
        idle();
        break;
      case MAC_MODE_SIDLE:
      case MAC_MODE_SWFACK:
      case MAC_MODE_XBROADCAST:
      case MAC_MODE_XUNICAST:
      case MAC_MODE_XACK:
      case MAC_MODE_SWITCH:
        // don't care
        break;
      default:
        throw new RuntimeException("unexpected mode: " + config.getModeString(macCtx.getMode()));
    }
  }

  /** Sets the radio idle */
  protected void radioIdle() {
    switch (macCtx.getMode()) {
      case MAC_MODE_SIDLE:
      case MAC_MODE_SNAV:
        doDifs();
        break;
      case MAC_MODE_SWFACK:
      case MAC_MODE_DIFS:
      case MAC_MODE_SBO:
      case MAC_MODE_XUNICAST:
      case MAC_MODE_XBROADCAST:
      case MAC_MODE_XACK:
      case MAC_MODE_SWITCH:
        // don't care
        break;
      default:
        throw new RuntimeException("unexpected mode: " + config.getModeString(macCtx.getMode()));
    }
  }

  // ////////////////////////////////////////////////
  // timer routines
  //

  // MacInterface
  public void startTimer(long delay, byte mode) {
    /** Node: all active timers are canceled here */
    cancelTimer();
    macCtx.setMode(mode);
    /** schedule timeout() */
    JistAPI.sleep(delay);
    self.timeout(macCtx.timerId);
  }

  /** Cancel timer event, by incrementing the timer identifer. */
  protected void cancelTimer() {
    macCtx.timerId++;
  }

  protected void idle() {
    cancelTimer();
    macCtx.setMode(MAC_MODE_SIDLE);
  }

  // MacInterface
  public void timeout(int timerId) {

    if (timerId != macCtx.timerId)
      return;

    switch (macCtx.getMode()) {
      case MAC_MODE_SIDLE:
        idle();
        break;
      case MAC_MODE_DIFS:
        if (macCtx.hasBackoff()) {
          macCtx.backoff();
        } else if (null != txCtx) {
          if (!macCtx.channel.equals(txCtx.channel)) {
            /** we are on the wrong RF channel; initiate channel switch. */
            doChannelSwitch(txCtx.channel);
          } else {
            txCtx.sendData();
          }
        } else {
          idle();
        }
        break;
      case MAC_MODE_SBO:
        Util.assertion(macCtx.boStart + macCtx.bo == JistAPI.getTime());
        macCtx.clearBackoff();
        if (null != txCtx) {
          if (!macCtx.channel.equals(txCtx.channel)) {
            /** we are on the wrong RF channel; initiate channel switch. */
            doChannelSwitch(txCtx.channel);
          } else {
            txCtx.sendData();
          }
        } else {
          idle();
        }
        break;
      case MAC_MODE_SNAV:
        macCtx.resetNav();
        doDifs();
        break;
      case MAC_MODE_SWFACK:
        txCtx.retry();
        break;
      case MAC_MODE_SWITCH:
        if (txCtx == null) {
          /** we are returning to our home channel (txCtx is null) */
          macCtx.setChannel(config.homeChannel);
          if (log.isDebugEnabled())
            log.debug(getNode().getNodeId() + "(" + JistAPI.getTime()
                + "): Switch back to homechannel " + macCtx.channel + " complete; pump new packet.");

          doDifs();
          netEntity.endSend(null, macCtx.netId, null);
        } else {
          /** we finished the channel switch and now we can send the packet. */
          /** update/reset the mac state */
          macCtx.setChannel(txCtx.channel);
          macCtx.setMode(MAC_MODE_SIDLE);

          if (log.isDebugEnabled())
            log.debug(getNode().getNodeId() + "(" + JistAPI.getTime()
                + "): Switch to channel " + macCtx.channel + " complete.");

          if (!macCtx.isCarrierIdle()) {
            /** MAC is not idle; process a backoff */
            macCtx.setBackoff();
          }
          /** make DIFS and start TX */
          doDifs();

          //processEvent(Event.MAC_CHANNEL, macCtx.channel);
          /*
          if (null != txCtx) {
            txCtx.sendData();
          } else {
            idle();
          }
          */
        }
        break;
      default:
        throw new RuntimeException("unexpected mode: " + macCtx.getMode());
    }
  }

  /*
   * {@link Mac802_11} produces 802.11 frames
   */
  public int getEncapType() {
    return WiresharkDump.FAKE_DLT_IEEE802_11;
  }

  // MacInterface
  public void peek(Message msg, MessageAnno anno) {
    macCtx.needEifs = true;

    /** log the start of the transmission */
    MacMcExORMessage macMsg = (MacMcExORMessage)((PhyMessage)msg).getPayload();

    if (macMsg.getType()==MacMcExORMessage.TYPE_DATA && receiveStartEvent.isActive()) {
      receiveStartEvent.handle(msg, anno);
    }
  }
  /**
   * Whether packet is for local consumption.
   *
   * @param msg
   *          packet to inspect
   * @return whether packet is for local consumption
   */
  protected boolean isForMe(Message msg) {
    NetMessage.Ip ipMsg = (NetMessage.Ip) msg;
    NetAddress addr = ipMsg.getDst();
    return netEntity.getAddress().equals(addr);
  }

  public String toString() {
    return this.getClass().getName() + ": " + config.localAddr;
  }

  /**
   * Retrieves the unique packet sequence number of application layer.
   * @param ip the incoming ip packet
   * @return the application seq number
   */
  public static short getApplicationSeq(Message ip) {

    if (ip instanceof NetMessage.Ip) {
      NetMessage.Ip ipMsg = (NetMessage.Ip) ip;

      if (ipMsg.getProtocol() == Constants.NET_PROTOCOL_MCEXOR) {
        RouteDsrBrnMsg mcExORMsg = (RouteDsrBrnMsg) ipMsg.getPayload();

        RouteDsrBrnMsg.OptionId id = (RouteDsrBrnMsg.OptionId)
            mcExORMsg.getOption(RouteDsrBrnMsg.OPT_ID);

        if (id.getAnnoFlowId() > Configuration.MAX_NUMBER_OF_FLOWS
            || id.getAnnoPacketId() > Configuration.MAX_NUMBER_OF_PACKETS_PER_FLOW)
          throw new RuntimeException("Max no. of flows or packets reached for using appSeq on MAC.");

        return (short)(id.getAnnoFlowId() << 10 ^ id.getAnnoPacketId());
      }
    }
    throw new IllegalArgumentException("Wrong protocol");
  }

  /** Set the rate selection algorithm to rca. */
  public void setDataRateControlAlgorithm(RateControlAlgorithmIF rca) {
    config.dataRcAlg = rca;
  }

  /** Set the rate selection algorithm to rca. */
  public void setCtrlRateControlAlgorithm(RateControlAlgorithmIF rca) {
    config.ctrlRcAlg = rca;
    //if (rca instanceof ConstantRate) {
      //controlRate = rca.getNextDataRate(null, null, null, -1, -1);
    //}
  }

  public MacAddress getAddress() {
    return this.config.localAddr;
  }
}

