PageRenderTime 70ms CodeModel.GetById 37ms app.highlight 27ms RepoModel.GetById 1ms app.codeStats 1ms

/INET-OverSim-20101019/src/linklayer/ethernet/EtherMACBase.cc

https://bitbucket.org/indigopony/omnetproject
C++ | 673 lines | 503 code | 101 blank | 69 comment | 82 complexity | 0f1966c9a8bce860830cd8bf7b7fa58f MD5 | raw file
  1//
  2// Copyright (C) 2006 Levente Meszaros
  3// Copyright (C) 2004 Andras Varga
  4//
  5// This program is free software; you can redistribute it and/or
  6// modify it under the terms of the GNU Lesser General Public License
  7// as published by the Free Software Foundation; either version 2
  8// of the License, or (at your option) any later version.
  9//
 10// This program is distributed in the hope that it will be useful,
 11// but WITHOUT ANY WARRANTY; without even the implied warranty of
 12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 13// GNU Lesser General Public License for more details.
 14//
 15// You should have received a copy of the GNU Lesser General Public License
 16// along with this program; if not, see <http://www.gnu.org/licenses/>.
 17//
 18
 19#include <stdio.h>
 20#include <string.h>
 21#include <omnetpp.h>
 22#include "EtherMACBase.h"
 23#include "IPassiveQueue.h"
 24#include "IInterfaceTable.h"
 25#include "InterfaceTableAccess.h"
 26
 27static const double SPEED_OF_LIGHT = 200000000.0;
 28
 29EtherMACBase::EtherMACBase()
 30{
 31    nb = NULL;
 32    queueModule = NULL;
 33    interfaceEntry = NULL;
 34    endTxMsg = endIFGMsg = endPauseMsg = NULL;
 35}
 36
 37EtherMACBase::~EtherMACBase()
 38{
 39    cancelAndDelete(endTxMsg);
 40    cancelAndDelete(endIFGMsg);
 41    cancelAndDelete(endPauseMsg);
 42}
 43
 44void EtherMACBase::initialize()
 45{
 46    physOutGate = gate("phys$o");
 47
 48    initializeFlags();
 49
 50    initializeTxrate();
 51    WATCH(txrate);
 52
 53    initializeMACAddress();
 54    initializeQueueModule();
 55    initializeNotificationBoard();
 56    initializeStatistics();
 57
 58    registerInterface(txrate); // needs MAC address
 59
 60    // initialize queue
 61    txQueue.setName("txQueue");
 62
 63    // initialize self messages
 64    endTxMsg = new cMessage("EndTransmission", ENDTRANSMISSION);
 65    endIFGMsg = new cMessage("EndIFG", ENDIFG);
 66    endPauseMsg = new cMessage("EndPause", ENDPAUSE);
 67
 68    // initialize states
 69    transmitState = TX_IDLE_STATE;
 70    receiveState = RX_IDLE_STATE;
 71    WATCH(transmitState);
 72    WATCH(receiveState);
 73
 74    // initalize pause
 75    pauseUnitsRequested = 0;
 76    WATCH(pauseUnitsRequested);
 77
 78    // initialize queue limit
 79    txQueueLimit = par("txQueueLimit");
 80    WATCH(txQueueLimit);
 81}
 82
 83void EtherMACBase::initializeQueueModule()
 84{
 85    if (par("queueModule").stringValue()[0])
 86    {
 87        cModule *module = getParentModule()->getSubmodule(par("queueModule").stringValue());
 88        queueModule = check_and_cast<IPassiveQueue *>(module);
 89        EV << "Requesting first frame from queue module\n";
 90        queueModule->requestPacket();
 91    }
 92}
 93
 94void EtherMACBase::initializeMACAddress()
 95{
 96    const char *addrstr = par("address");
 97
 98    if (!strcmp(addrstr,"auto"))
 99    {
100        // assign automatic address
101        address = MACAddress::generateAutoAddress();
102
103        // change module parameter from "auto" to concrete address
104        par("address").setStringValue(address.str().c_str());
105    }
106    else
107    {
108        address.setAddress(addrstr);
109    }
110}
111
112void EtherMACBase::initializeNotificationBoard()
113{
114    hasSubscribers = false;
115    if (interfaceEntry) {
116        nb = NotificationBoardAccess().getIfExists();
117        notifDetails.setInterfaceEntry(interfaceEntry);
118        nb->subscribe(this, NF_SUBSCRIBERLIST_CHANGED);
119        updateHasSubcribers();
120    }
121}
122
123void EtherMACBase::initializeFlags()
124{
125    // initialize connected flag
126    connected = physOutGate->getPathEndGate()->isConnected();
127    if (!connected)
128        EV << "MAC not connected to a network.\n";
129    WATCH(connected);
130
131    // TODO: this should be settable from the gui
132    // initialize disabled flag
133    // Note: it is currently not supported to enable a disabled MAC at runtime.
134    // Difficulties: (1) autoconfig (2) how to pick up channel state (free, tx, collision etc)
135    disabled = false;
136    WATCH(disabled);
137
138    // initialize promiscuous flag
139    promiscuous = par("promiscuous");
140    WATCH(promiscuous);
141}
142
143void EtherMACBase::initializeStatistics()
144{
145    framesSentInBurst = 0;
146    bytesSentInBurst = 0;
147
148    numFramesSent = numFramesReceivedOK = numBytesSent = numBytesReceivedOK = 0;
149    numFramesPassedToHL = numDroppedBitError = numDroppedNotForUs = 0;
150    numFramesFromHL = numDroppedIfaceDown = 0;
151    numPauseFramesRcvd = numPauseFramesSent = 0;
152
153    WATCH(framesSentInBurst);
154    WATCH(bytesSentInBurst);
155
156    WATCH(numFramesSent);
157    WATCH(numFramesReceivedOK);
158    WATCH(numBytesSent);
159    WATCH(numBytesReceivedOK);
160    WATCH(numFramesFromHL);
161    WATCH(numDroppedIfaceDown);
162    WATCH(numDroppedBitError);
163    WATCH(numDroppedNotForUs);
164    WATCH(numFramesPassedToHL);
165    WATCH(numPauseFramesRcvd);
166    WATCH(numPauseFramesSent);
167
168    numFramesSentVector.setName("framesSent");
169    numFramesReceivedOKVector.setName("framesReceivedOK");
170    numBytesSentVector.setName("bytesSent");
171    numBytesReceivedOKVector.setName("bytesReceivedOK");
172    numDroppedIfaceDownVector.setName("framesDroppedIfaceDown");
173    numDroppedBitErrorVector.setName("framesDroppedBitError");
174    numDroppedNotForUsVector.setName("framesDroppedNotForUs");
175    numFramesPassedToHLVector.setName("framesPassedToHL");
176    numPauseFramesRcvdVector.setName("pauseFramesRcvd");
177    numPauseFramesSentVector.setName("pauseFramesSent");
178}
179
180void EtherMACBase::registerInterface(double txrate)
181{
182    IInterfaceTable *ift = InterfaceTableAccess().getIfExists();
183    if (!ift)
184        return;
185
186    interfaceEntry = new InterfaceEntry();
187
188    // interface name: our module name without special characters ([])
189    char *interfaceName = new char[strlen(getParentModule()->getFullName())+1];
190    char *d=interfaceName;
191    for (const char *s=getParentModule()->getFullName(); *s; s++)
192        if (isalnum(*s))
193            *d++ = *s;
194    *d = '\0';
195
196    interfaceEntry->setName(interfaceName);
197    delete [] interfaceName;
198
199    // data rate
200    interfaceEntry->setDatarate(txrate);
201
202    // generate a link-layer address to be used as interface token for IPv6
203    interfaceEntry->setMACAddress(address);
204    interfaceEntry->setInterfaceToken(address.formInterfaceIdentifier());
205    //InterfaceToken token(0, simulation.getUniqueNumber(), 64);
206    //interfaceEntry->setInterfaceToken(token);
207
208    // MTU: typical values are 576 (Internet de facto), 1500 (Ethernet-friendly),
209    // 4000 (on some point-to-point links), 4470 (Cisco routers default, FDDI compatible)
210    interfaceEntry->setMtu(par("mtu"));
211
212    // capabilities
213    interfaceEntry->setMulticast(true);
214    interfaceEntry->setBroadcast(true);
215
216    // add
217    ift->addInterface(interfaceEntry, this);
218}
219
220
221bool EtherMACBase::checkDestinationAddress(EtherFrame *frame)
222{
223    // If not set to promiscuous = on, then checks if received frame contains destination MAC address
224    // matching port's MAC address, also checks if broadcast bit is set
225    if (!promiscuous && !frame->getDest().isBroadcast() && !frame->getDest().equals(address))
226    {
227        EV << "Frame `" << frame->getName() <<"' not destined to us, discarding\n";
228        numDroppedNotForUs++;
229        numDroppedNotForUsVector.record(numDroppedNotForUs);
230        delete frame;
231
232        return false;
233    }
234
235    return true;
236}
237
238void EtherMACBase::calculateParameters()
239{
240    if (disabled || !connected)
241    {
242        bitTime = slotTime = interFrameGap = jamDuration = shortestFrameDuration = 0;
243        carrierExtension = frameBursting = false;
244        return;
245    }
246
247    if (txrate != ETHERNET_TXRATE && txrate != FAST_ETHERNET_TXRATE &&
248        txrate != GIGABIT_ETHERNET_TXRATE && txrate != FAST_GIGABIT_ETHERNET_TXRATE)
249    {
250        error("nonstandard transmission rate %g, must be %g, %g, %g or %g bit/sec",
251            txrate, ETHERNET_TXRATE, FAST_ETHERNET_TXRATE, GIGABIT_ETHERNET_TXRATE, FAST_GIGABIT_ETHERNET_TXRATE);
252    }
253
254    bitTime = 1/(double)txrate;
255
256    // set slot time
257    if (txrate==ETHERNET_TXRATE || txrate==FAST_ETHERNET_TXRATE)
258        slotTime = SLOT_TIME;
259    else
260        slotTime = GIGABIT_SLOT_TIME;
261
262    // only if Gigabit Ethernet
263    frameBursting = (txrate==GIGABIT_ETHERNET_TXRATE || txrate==FAST_GIGABIT_ETHERNET_TXRATE);
264    carrierExtension = (slotTime == GIGABIT_SLOT_TIME && !duplexMode);
265
266    interFrameGap = INTERFRAME_GAP_BITS/(double)txrate;
267    jamDuration = 8*JAM_SIGNAL_BYTES*bitTime;
268    shortestFrameDuration = carrierExtension ? GIGABIT_MIN_FRAME_WITH_EXT : MIN_ETHERNET_FRAME;
269}
270
271void EtherMACBase::printParameters()
272{
273    // Dump parameters
274    EV << "MAC address: " << address << (promiscuous ? ", promiscuous mode" : "") << endl;
275    EV << "txrate: " << txrate << ", " << (duplexMode ? "duplex" : "half-duplex") << endl;
276#if 0
277    EV << "bitTime: " << bitTime << endl;
278    EV << "carrierExtension: " << carrierExtension << endl;
279    EV << "frameBursting: " << frameBursting << endl;
280    EV << "slotTime: " << slotTime << endl;
281    EV << "interFrameGap: " << interFrameGap << endl;
282    EV << endl;
283#endif
284}
285
286void EtherMACBase::processFrameFromUpperLayer(EtherFrame *frame)
287{
288    EV << "Received frame from upper layer: " << frame << endl;
289
290    if (frame->getDest().equals(address))
291    {
292        error("logic error: frame %s from higher layer has local MAC address as dest (%s)",
293              frame->getFullName(), frame->getDest().str().c_str());
294    }
295
296    if (frame->getByteLength() > MAX_ETHERNET_FRAME)
297        error("packet from higher layer (%d bytes) exceeds maximum Ethernet frame size (%d)", (int)(frame->getByteLength()), MAX_ETHERNET_FRAME);
298
299    // must be EtherFrame (or EtherPauseFrame) from upper layer
300    bool isPauseFrame = (dynamic_cast<EtherPauseFrame*>(frame)!=NULL);
301    if (!isPauseFrame)
302    {
303        numFramesFromHL++;
304
305        if (txQueueLimit && txQueue.length()>txQueueLimit)
306            error("txQueue length exceeds %d -- this is probably due to "
307                  "a bogus app model generating excessive traffic "
308                  "(or if this is normal, increase txQueueLimit!)",
309                  txQueueLimit);
310
311        // fill in src address if not set
312        if (frame->getSrc().isUnspecified())
313            frame->setSrc(address);
314
315        // store frame and possibly begin transmitting
316        EV << "Packet " << frame << " arrived from higher layers, enqueueing\n";
317        txQueue.insert(frame);
318    }
319    else
320    {
321        EV << "PAUSE received from higher layer\n";
322
323        // PAUSE frames enjoy priority -- they're transmitted before all other frames queued up
324        if (!txQueue.empty())
325            txQueue.insertBefore(txQueue.front(), frame);  // front() frame is probably being transmitted
326        else
327            txQueue.insert(frame);
328    }
329
330}
331
332void EtherMACBase::processMsgFromNetwork(cPacket *frame)
333{
334    EV << "Received frame from network: " << frame << endl;
335
336    // frame must be EtherFrame or EtherJam
337    if (dynamic_cast<EtherFrame*>(frame)==NULL && dynamic_cast<EtherJam*>(frame)==NULL)
338        error("message with unexpected message class '%s' arrived from network (name='%s')",
339                frame->getClassName(), frame->getFullName());
340
341    // detect cable length violation in half-duplex mode
342    if (!duplexMode && simTime()-frame->getSendingTime()>=shortestFrameDuration)
343        error("very long frame propagation time detected, maybe cable exceeds maximum allowed length? "
344              "(%lgs corresponds to an approx. %lgm cable)",
345              SIMTIME_STR(simTime() - frame->getSendingTime()),
346              SIMTIME_STR((simTime() - frame->getSendingTime())*SPEED_OF_LIGHT));
347}
348
349void EtherMACBase::frameReceptionComplete(EtherFrame *frame)
350{
351    int pauseUnits;
352    EtherPauseFrame *pauseFrame;
353
354    if ((pauseFrame=dynamic_cast<EtherPauseFrame*>(frame))!=NULL)
355    {
356        pauseUnits = pauseFrame->getPauseTime();
357        delete frame;
358        numPauseFramesRcvd++;
359        numPauseFramesRcvdVector.record(numPauseFramesRcvd);
360        processPauseCommand(pauseUnits);
361    }
362    else
363    {
364        processReceivedDataFrame((EtherFrame *)frame);
365    }
366}
367
368void EtherMACBase::processReceivedDataFrame(EtherFrame *frame)
369{
370    // bit errors
371    if (frame->hasBitError())
372    {
373        numDroppedBitError++;
374        numDroppedBitErrorVector.record(numDroppedBitError);
375        delete frame;
376        return;
377    }
378
379    // strip preamble and SFD
380    frame->addByteLength(-PREAMBLE_BYTES-SFD_BYTES);
381
382    // statistics
383    numFramesReceivedOK++;
384    numBytesReceivedOK += frame->getByteLength();
385    numFramesReceivedOKVector.record(numFramesReceivedOK);
386    numBytesReceivedOKVector.record(numBytesReceivedOK);
387
388    if (!checkDestinationAddress(frame))
389        return;
390
391    numFramesPassedToHL++;
392    numFramesPassedToHLVector.record(numFramesPassedToHL);
393
394    // pass up to upper layer
395    send(frame, "upperLayerOut");
396}
397
398void EtherMACBase::processPauseCommand(int pauseUnits)
399{
400    if (transmitState==TX_IDLE_STATE)
401    {
402        EV << "PAUSE frame received, pausing for " << pauseUnitsRequested << " time units\n";
403        if (pauseUnits>0)
404            scheduleEndPausePeriod(pauseUnits);
405    }
406    else if (transmitState==PAUSE_STATE)
407    {
408        EV << "PAUSE frame received, pausing for " << pauseUnitsRequested << " more time units from now\n";
409        cancelEvent(endPauseMsg);
410        if (pauseUnits>0)
411            scheduleEndPausePeriod(pauseUnits);
412    }
413    else
414    {
415        // transmitter busy -- wait until it finishes with current frame (endTx)
416        // and then it'll go to PAUSE state
417        EV << "PAUSE frame received, storing pause request\n";
418        pauseUnitsRequested = pauseUnits;
419    }
420}
421
422void EtherMACBase::handleEndIFGPeriod()
423{
424    if (transmitState!=WAIT_IFG_STATE)
425        error("Not in WAIT_IFG_STATE at the end of IFG period");
426
427    if (txQueue.empty())
428        error("End of IFG and no frame to transmit");
429
430    // End of IFG period, okay to transmit, if Rx idle OR duplexMode
431    cPacket *frame = (cPacket *)txQueue.front();
432    EV << "IFG elapsed, now begin transmission of frame " << frame << endl;
433
434    // Perform carrier extension if in Gigabit Ethernet
435    if (carrierExtension && frame->getByteLength() < GIGABIT_MIN_FRAME_WITH_EXT)
436    {
437        EV << "Performing carrier extension of small frame\n";
438        frame->setByteLength(GIGABIT_MIN_FRAME_WITH_EXT);
439    }
440
441    // start frame burst, if enabled
442    if (frameBursting)
443    {
444        EV << "Starting frame burst\n";
445        framesSentInBurst = 0;
446        bytesSentInBurst = 0;
447    }
448}
449
450void EtherMACBase::handleEndTxPeriod()
451{
452    // we only get here if transmission has finished successfully, without collision
453    if (transmitState!=TRANSMITTING_STATE || (!duplexMode && receiveState!=RX_IDLE_STATE))
454        error("End of transmission, and incorrect state detected");
455
456    if (txQueue.empty())
457        error("Frame under transmission cannot be found");
458
459    // get frame from buffer
460    cPacket *frame = (cPacket *)txQueue.pop();
461
462    numFramesSent++;
463    numBytesSent += frame->getByteLength();
464    numFramesSentVector.record(numFramesSent);
465    numBytesSentVector.record(numBytesSent);
466
467    if (dynamic_cast<EtherPauseFrame*>(frame)!=NULL)
468    {
469        numPauseFramesSent++;
470        numPauseFramesSentVector.record(numPauseFramesSent);
471    }
472
473    EV << "Transmission of " << frame << " successfully completed\n";
474    delete frame;
475}
476
477void EtherMACBase::handleEndPausePeriod()
478{
479    if (transmitState != PAUSE_STATE)
480        error("At end of PAUSE not in PAUSE_STATE!");
481    EV << "Pause finished, resuming transmissions\n";
482    beginSendFrames();
483}
484
485void EtherMACBase::processMessageWhenNotConnected(cMessage *msg)
486{
487    EV << "Interface is not connected -- dropping packet " << msg << endl;
488    delete msg;
489    numDroppedIfaceDown++;
490}
491
492void EtherMACBase::processMessageWhenDisabled(cMessage *msg)
493{
494    EV << "MAC is disabled -- dropping message " << msg << endl;
495    delete msg;
496}
497
498void EtherMACBase::scheduleEndIFGPeriod()
499{
500    scheduleAt(simTime()+interFrameGap, endIFGMsg);
501    transmitState = WAIT_IFG_STATE;
502}
503
504void EtherMACBase::scheduleEndTxPeriod(cPacket *frame)
505{
506    scheduleAt(simTime()+frame->getBitLength()*bitTime, endTxMsg);
507    transmitState = TRANSMITTING_STATE;
508}
509
510void EtherMACBase::scheduleEndPausePeriod(int pauseUnits)
511{
512    // length is interpreted as 512-bit-time units
513    simtime_t pausePeriod = pauseUnits*PAUSE_BITTIME*bitTime;
514    scheduleAt(simTime()+pausePeriod, endPauseMsg);
515    transmitState = PAUSE_STATE;
516}
517
518bool EtherMACBase::checkAndScheduleEndPausePeriod()
519{
520    if (pauseUnitsRequested>0)
521    {
522        // if we received a PAUSE frame recently, go into PAUSE state
523        EV << "Going to PAUSE mode for " << pauseUnitsRequested << " time units\n";
524
525        scheduleEndPausePeriod(pauseUnitsRequested);
526        pauseUnitsRequested = 0;
527        return true;
528    }
529
530    return false;
531}
532
533void EtherMACBase::beginSendFrames()
534{
535    if (!txQueue.empty())
536    {
537        // Other frames are queued, therefore wait IFG period and transmit next frame
538        EV << "Transmit next frame in output queue, after IFG period\n";
539        scheduleEndIFGPeriod();
540    }
541    else
542    {
543        transmitState = TX_IDLE_STATE;
544        if (queueModule)
545        {
546            // tell queue module that we've become idle
547            EV << "Requesting another frame from queue module\n";
548            queueModule->requestPacket();
549        }
550        else
551        {
552            // No more frames set transmitter to idle
553            EV << "No more frames to send, transmitter set to idle\n";
554        }
555    }
556}
557
558void EtherMACBase::fireChangeNotification(int type, cPacket *msg)
559{
560    if (nb) {
561        notifDetails.setPacket(msg);
562        nb->fireChangeNotification(type, &notifDetails);
563    }
564}
565
566void EtherMACBase::finish()
567{
568    if (!disabled)
569    {
570        simtime_t t = simTime();
571        recordScalar("simulated time", t);
572        recordScalar("txrate (Mb)", txrate/1000000);
573        recordScalar("full duplex", duplexMode);
574        recordScalar("frames sent",    numFramesSent);
575        recordScalar("frames rcvd",    numFramesReceivedOK);
576        recordScalar("bytes sent",     numBytesSent);
577        recordScalar("bytes rcvd",     numBytesReceivedOK);
578        recordScalar("frames from higher layer", numFramesFromHL);
579        recordScalar("frames from higher layer dropped (iface down)", numDroppedIfaceDown);
580        recordScalar("frames dropped (bit error)",  numDroppedBitError);
581        recordScalar("frames dropped (not for us)", numDroppedNotForUs);
582        recordScalar("frames passed up to HL", numFramesPassedToHL);
583        recordScalar("PAUSE frames sent",  numPauseFramesSent);
584        recordScalar("PAUSE frames rcvd",  numPauseFramesRcvd);
585
586        if (t>0)
587        {
588            recordScalar("frames/sec sent", numFramesSent/t);
589            recordScalar("frames/sec rcvd", numFramesReceivedOK/t);
590            recordScalar("bits/sec sent",   8*numBytesSent/t);
591            recordScalar("bits/sec rcvd",   8*numBytesReceivedOK/t);
592        }
593    }
594}
595
596void EtherMACBase::updateDisplayString()
597{
598    // icon coloring
599    const char *color;
600    if (receiveState==RX_COLLISION_STATE)
601        color = "red";
602    else if (transmitState==TRANSMITTING_STATE)
603        color = "yellow";
604    else if (transmitState==JAMMING_STATE)
605        color = "red";
606    else if (receiveState==RECEIVING_STATE)
607        color = "#4040ff";
608    else if (transmitState==BACKOFF_STATE)
609        color = "white";
610    else if (transmitState==PAUSE_STATE)
611        color = "gray";
612    else
613        color = "";
614    getDisplayString().setTagArg("i",1,color);
615    if (!strcmp(getParentModule()->getClassName(),"EthernetInterface"))
616        getParentModule()->getDisplayString().setTagArg("i",1,color);
617
618    // connection coloring
619    updateConnectionColor(transmitState);
620
621#if 0
622    // this code works but didn't turn out to be very useful
623    const char *txStateName;
624    switch (transmitState) {
625        case TX_IDLE_STATE:      txStateName="IDLE"; break;
626        case WAIT_IFG_STATE:     txStateName="WAIT_IFG"; break;
627        case TRANSMITTING_STATE: txStateName="TX"; break;
628        case JAMMING_STATE:      txStateName="JAM"; break;
629        case BACKOFF_STATE:      txStateName="BACKOFF"; break;
630        case PAUSE_STATE:        txStateName="PAUSE"; break;
631        default: error("wrong tx state");
632    }
633    const char *rxStateName;
634    switch (receiveState) {
635        case RX_IDLE_STATE:      rxStateName="IDLE"; break;
636        case RECEIVING_STATE:    rxStateName="RX"; break;
637        case RX_COLLISION_STATE: rxStateName="COLL"; break;
638        default: error("wrong rx state");
639    }
640
641    char buf[80];
642    sprintf(buf, "tx:%s rx: %s\n#boff:%d #cTx:%d",
643                 txStateName, rxStateName, backoffs, numConcurrentTransmissions);
644    getDisplayString().setTagArg("t",0,buf);
645#endif
646}
647
648void EtherMACBase::updateConnectionColor(int txState)
649{
650    const char *color;
651    if (txState==TRANSMITTING_STATE)
652        color = "yellow";
653    else if (txState==JAMMING_STATE || txState==BACKOFF_STATE)
654        color = "red";
655    else
656        color = "";
657
658    cGate *g = physOutGate;
659    while (g && g->getType()==cGate::OUTPUT)
660    {
661        g->getDisplayString().setTagArg("o",0,color);
662        g->getDisplayString().setTagArg("o",1, color[0] ? "3" : "1");
663        g = g->getNextGate();
664    }
665}
666
667void EtherMACBase::receiveChangeNotification(int category, const cPolymorphic *)
668{
669    if (category==NF_SUBSCRIBERLIST_CHANGED)
670        updateHasSubcribers();
671}
672
673