Files
unison/src/wifi/model/ap-wifi-mac.cc
2018-02-26 23:18:47 +01:00

1477 lines
52 KiB
C++

/* -*- Mode:C++; c-file-style:"gnu"; indent-tabs-mode:nil; -*- */
/*
* Copyright (c) 2006, 2009 INRIA
* Copyright (c) 2009 MIRKO BANCHI
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation;
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Authors: Mathieu Lacage <mathieu.lacage@sophia.inria.fr>
* Mirko Banchi <mk.banchi@gmail.com>
*/
#include "ap-wifi-mac.h"
#include "ns3/log.h"
#include "ns3/simulator.h"
#include "ns3/string.h"
#include "ns3/pointer.h"
#include "mac-low.h"
#include "mac-tx-middle.h"
namespace ns3 {
NS_LOG_COMPONENT_DEFINE ("ApWifiMac");
NS_OBJECT_ENSURE_REGISTERED (ApWifiMac);
TypeId
ApWifiMac::GetTypeId (void)
{
static TypeId tid = TypeId ("ns3::ApWifiMac")
.SetParent<RegularWifiMac> ()
.SetGroupName ("Wifi")
.AddConstructor<ApWifiMac> ()
.AddAttribute ("BeaconInterval",
"Delay between two beacons",
TimeValue (MicroSeconds (102400)),
MakeTimeAccessor (&ApWifiMac::GetBeaconInterval,
&ApWifiMac::SetBeaconInterval),
MakeTimeChecker ())
.AddAttribute ("BeaconJitter",
"A uniform random variable to cause the initial beacon starting time (after simulation time 0) "
"to be distributed between 0 and the BeaconInterval.",
StringValue ("ns3::UniformRandomVariable"),
MakePointerAccessor (&ApWifiMac::m_beaconJitter),
MakePointerChecker<UniformRandomVariable> ())
.AddAttribute ("EnableBeaconJitter",
"If beacons are enabled, whether to jitter the initial send event.",
BooleanValue (true),
MakeBooleanAccessor (&ApWifiMac::m_enableBeaconJitter),
MakeBooleanChecker ())
.AddAttribute ("BeaconGeneration",
"Whether or not beacons are generated.",
BooleanValue (true),
MakeBooleanAccessor (&ApWifiMac::SetBeaconGeneration,
&ApWifiMac::GetBeaconGeneration),
MakeBooleanChecker ())
.AddAttribute ("EnableNonErpProtection", "Whether or not protection mechanism should be used when non-ERP STAs are present within the BSS."
"This parameter is only used when ERP is supported by the AP.",
BooleanValue (true),
MakeBooleanAccessor (&ApWifiMac::m_enableNonErpProtection),
MakeBooleanChecker ())
.AddAttribute ("RifsMode", "If non-HT STAs are detected, whether to force RIFS to be disabled within the BSS."
"This parameter is only used when HT is supported by the AP.",
BooleanValue (true),
MakeBooleanAccessor (&ApWifiMac::m_disableRifs),
MakeBooleanChecker ())
;
return tid;
}
ApWifiMac::ApWifiMac ()
: m_enableBeaconGeneration (false)
{
NS_LOG_FUNCTION (this);
m_beaconDca = CreateObject<DcaTxop> ();
m_beaconDca->SetAifsn (1);
m_beaconDca->SetMinCw (0);
m_beaconDca->SetMaxCw (0);
m_beaconDca->SetLow (m_low);
m_beaconDca->SetManager (m_dcfManager);
m_beaconDca->SetTxMiddle (m_txMiddle);
//Let the lower layers know that we are acting as an AP.
SetTypeOfStation (AP);
}
ApWifiMac::~ApWifiMac ()
{
NS_LOG_FUNCTION (this);
m_staList.clear ();
m_nonErpStations.clear ();
m_nonHtStations.clear ();
}
void
ApWifiMac::DoDispose ()
{
NS_LOG_FUNCTION (this);
m_beaconDca->Dispose ();
m_beaconDca = 0;
m_enableBeaconGeneration = false;
m_beaconEvent.Cancel ();
RegularWifiMac::DoDispose ();
}
void
ApWifiMac::SetAddress (Mac48Address address)
{
NS_LOG_FUNCTION (this << address);
//As an AP, our MAC address is also the BSSID. Hence we are
//overriding this function and setting both in our parent class.
RegularWifiMac::SetAddress (address);
RegularWifiMac::SetBssid (address);
}
void
ApWifiMac::SetBeaconGeneration (bool enable)
{
NS_LOG_FUNCTION (this << enable);
if (!enable)
{
m_beaconEvent.Cancel ();
}
else if (enable && !m_enableBeaconGeneration)
{
m_beaconEvent = Simulator::ScheduleNow (&ApWifiMac::SendOneBeacon, this);
}
m_enableBeaconGeneration = enable;
}
bool
ApWifiMac::GetBeaconGeneration (void) const
{
NS_LOG_FUNCTION (this);
return m_enableBeaconGeneration;
}
Time
ApWifiMac::GetBeaconInterval (void) const
{
NS_LOG_FUNCTION (this);
return m_beaconInterval;
}
void
ApWifiMac::SetWifiRemoteStationManager (const Ptr<WifiRemoteStationManager> stationManager)
{
NS_LOG_FUNCTION (this << stationManager);
m_beaconDca->SetWifiRemoteStationManager (stationManager);
RegularWifiMac::SetWifiRemoteStationManager (stationManager);
}
void
ApWifiMac::SetLinkUpCallback (Callback<void> linkUp)
{
NS_LOG_FUNCTION (this << &linkUp);
RegularWifiMac::SetLinkUpCallback (linkUp);
//The approach taken here is that, from the point of view of an AP,
//the link is always up, so we immediately invoke the callback if
//one is set
linkUp ();
}
void
ApWifiMac::SetBeaconInterval (Time interval)
{
NS_LOG_FUNCTION (this << interval);
if ((interval.GetMicroSeconds () % 1024) != 0)
{
NS_LOG_WARN ("beacon interval should be multiple of 1024us (802.11 time unit), see IEEE Std. 802.11-2012");
}
m_beaconInterval = interval;
}
int64_t
ApWifiMac::AssignStreams (int64_t stream)
{
NS_LOG_FUNCTION (this << stream);
m_beaconJitter->SetStream (stream);
return 1;
}
bool
ApWifiMac::GetShortSlotTimeEnabled (void) const
{
if (m_nonErpStations.size () != 0)
{
return false;
}
if (m_erpSupported == true && GetShortSlotTimeSupported () == true)
{
for (std::map<uint16_t, Mac48Address>::const_iterator i = m_staList.begin (); i != m_staList.end (); i++)
{
if (m_stationManager->GetShortSlotTimeSupported (i->second) == false)
{
return false;
}
}
return true;
}
return false;
}
bool
ApWifiMac::GetShortPreambleEnabled (void) const
{
if (m_erpSupported || m_phy->GetShortPlcpPreambleSupported ())
{
for (std::list<Mac48Address>::const_iterator i = m_nonErpStations.begin (); i != m_nonErpStations.end (); i++)
{
if (m_stationManager->GetShortPreambleSupported (*i) == false)
{
return false;
}
}
return true;
}
return false;
}
bool
ApWifiMac::IsNonGfHtStasPresent (void) const
{
bool isNonGfHtStasPresent = false;
for (std::map<uint16_t, Mac48Address>::const_iterator i = m_staList.begin (); i != m_staList.end (); i++)
{
if (m_stationManager->GetGreenfieldSupported (i->second) == false)
{
isNonGfHtStasPresent = true;
break;
}
}
m_stationManager->SetUseGreenfieldProtection (isNonGfHtStasPresent);
return isNonGfHtStasPresent;
}
uint8_t
ApWifiMac::GetVhtOperationalChannelWidth (void) const
{
uint8_t channelWidth = m_phy->GetChannelWidth ();
for (std::map<uint16_t, Mac48Address>::const_iterator i = m_staList.begin (); i != m_staList.end (); i++)
{
if (m_stationManager->GetVhtSupported (i->second))
{
if (m_stationManager->GetChannelWidthSupported (i->second) < channelWidth)
{
channelWidth = m_stationManager->GetChannelWidthSupported (i->second);
}
}
}
return channelWidth;
}
void
ApWifiMac::ForwardDown (Ptr<const Packet> packet, Mac48Address from,
Mac48Address to)
{
NS_LOG_FUNCTION (this << packet << from << to);
//If we are not a QoS AP then we definitely want to use AC_BE to
//transmit the packet. A TID of zero will map to AC_BE (through \c
//QosUtilsMapTidToAc()), so we use that as our default here.
uint8_t tid = 0;
//If we are a QoS AP then we attempt to get a TID for this packet
if (m_qosSupported)
{
tid = QosUtilsGetTidForPacket (packet);
//Any value greater than 7 is invalid and likely indicates that
//the packet had no QoS tag, so we revert to zero, which'll
//mean that AC_BE is used.
if (tid > 7)
{
tid = 0;
}
}
ForwardDown (packet, from, to, tid);
}
void
ApWifiMac::ForwardDown (Ptr<const Packet> packet, Mac48Address from,
Mac48Address to, uint8_t tid)
{
NS_LOG_FUNCTION (this << packet << from << to << +tid);
WifiMacHeader hdr;
//For now, an AP that supports QoS does not support non-QoS
//associations, and vice versa. In future the AP model should
//support simultaneously associated QoS and non-QoS STAs, at which
//point there will need to be per-association QoS state maintained
//by the association state machine, and consulted here.
if (m_qosSupported)
{
hdr.SetType (WIFI_MAC_QOSDATA);
hdr.SetQosAckPolicy (WifiMacHeader::NORMAL_ACK);
hdr.SetQosNoEosp ();
hdr.SetQosNoAmsdu ();
//Transmission of multiple frames in the same Polled TXOP is not supported for now
hdr.SetQosTxopLimit (0);
//Fill in the QoS control field in the MAC header
hdr.SetQosTid (tid);
}
else
{
hdr.SetType (WIFI_MAC_DATA);
}
if (m_htSupported || m_vhtSupported || m_heSupported)
{
hdr.SetNoOrder ();
}
hdr.SetAddr1 (to);
hdr.SetAddr2 (GetAddress ());
hdr.SetAddr3 (from);
hdr.SetDsFrom ();
hdr.SetDsNotTo ();
if (m_qosSupported)
{
//Sanity check that the TID is valid
NS_ASSERT (tid < 8);
m_edca[QosUtilsMapTidToAc (tid)]->Queue (packet, hdr);
}
else
{
m_dca->Queue (packet, hdr);
}
}
void
ApWifiMac::Enqueue (Ptr<const Packet> packet, Mac48Address to, Mac48Address from)
{
NS_LOG_FUNCTION (this << packet << to << from);
if (to.IsBroadcast () || m_stationManager->IsAssociated (to))
{
ForwardDown (packet, from, to);
}
else
{
NotifyTxDrop (packet);
}
}
void
ApWifiMac::Enqueue (Ptr<const Packet> packet, Mac48Address to)
{
NS_LOG_FUNCTION (this << packet << to);
//We're sending this packet with a from address that is our own. We
//get that address from the lower MAC and make use of the
//from-spoofing Enqueue() method to avoid duplicated code.
Enqueue (packet, to, m_low->GetAddress ());
}
bool
ApWifiMac::SupportsSendFrom (void) const
{
NS_LOG_FUNCTION (this);
return true;
}
SupportedRates
ApWifiMac::GetSupportedRates (void) const
{
NS_LOG_FUNCTION (this);
SupportedRates rates;
//If it is an HT-AP or VHT-AP or HE-AP, then add the BSSMembershipSelectorSet
//The standard says that the BSSMembershipSelectorSet
//must have its MSB set to 1 (must be treated as a Basic Rate)
//Also the standard mentioned that at least 1 element should be included in the SupportedRates the rest can be in the ExtendedSupportedRates
if (m_htSupported || m_vhtSupported || m_heSupported)
{
for (uint8_t i = 0; i < m_phy->GetNBssMembershipSelectors (); i++)
{
rates.AddBssMembershipSelectorRate (m_phy->GetBssMembershipSelector (i));
}
}
//
//Send the set of supported rates and make sure that we indicate
//the Basic Rate set in this set of supported rates.
for (uint8_t i = 0; i < m_phy->GetNModes (); i++)
{
WifiMode mode = m_phy->GetMode (i);
uint64_t modeDataRate = mode.GetDataRate (m_phy->GetChannelWidth ());
NS_LOG_DEBUG ("Adding supported rate of " << modeDataRate);
rates.AddSupportedRate (modeDataRate);
//Add rates that are part of the BSSBasicRateSet (manufacturer dependent!)
//here we choose to add the mandatory rates to the BSSBasicRateSet,
//except for 802.11b where we assume that only the non HR-DSSS rates are part of the BSSBasicRateSet
if (mode.IsMandatory () && (mode.GetModulationClass () != WIFI_MOD_CLASS_HR_DSSS))
{
NS_LOG_DEBUG ("Adding basic mode " << mode.GetUniqueName ());
m_stationManager->AddBasicMode (mode);
}
}
//set the basic rates
for (uint8_t j = 0; j < m_stationManager->GetNBasicModes (); j++)
{
WifiMode mode = m_stationManager->GetBasicMode (j);
uint64_t modeDataRate = mode.GetDataRate (m_phy->GetChannelWidth ());
NS_LOG_DEBUG ("Setting basic rate " << mode.GetUniqueName ());
rates.SetBasicRate (modeDataRate);
}
return rates;
}
DsssParameterSet
ApWifiMac::GetDsssParameterSet (void) const
{
NS_LOG_FUNCTION (this);
DsssParameterSet dsssParameters;
if (m_dsssSupported)
{
dsssParameters.SetDsssSupported (1);
dsssParameters.SetCurrentChannel (m_phy->GetChannelNumber ());
}
return dsssParameters;
}
CapabilityInformation
ApWifiMac::GetCapabilities (void) const
{
NS_LOG_FUNCTION (this);
CapabilityInformation capabilities;
capabilities.SetShortPreamble (GetShortPreambleEnabled ());
capabilities.SetShortSlotTime (GetShortSlotTimeEnabled ());
capabilities.SetEss ();
return capabilities;
}
ErpInformation
ApWifiMac::GetErpInformation (void) const
{
NS_LOG_FUNCTION (this);
ErpInformation information;
information.SetErpSupported (1);
if (m_erpSupported)
{
information.SetNonErpPresent (!m_nonErpStations.empty ());
information.SetUseProtection (GetUseNonErpProtection ());
if (GetShortPreambleEnabled ())
{
information.SetBarkerPreambleMode (0);
}
else
{
information.SetBarkerPreambleMode (1);
}
}
return information;
}
EdcaParameterSet
ApWifiMac::GetEdcaParameterSet (void) const
{
NS_LOG_FUNCTION (this);
EdcaParameterSet edcaParameters;
if (m_qosSupported)
{
edcaParameters.SetQosSupported (1);
Ptr<EdcaTxopN> edca;
Time txopLimit;
edca = m_edca.find (AC_BE)->second;
txopLimit = edca->GetTxopLimit ();
edcaParameters.SetBeAci (0);
edcaParameters.SetBeCWmin (edca->GetMinCw ());
edcaParameters.SetBeCWmax (edca->GetMaxCw ());
edcaParameters.SetBeAifsn (edca->GetAifsn ());
edcaParameters.SetBeTXOPLimit (txopLimit.GetMicroSeconds () / 32);
edcaParameters.SetBeAcm (0);
edca = m_edca.find (AC_BK)->second;
txopLimit = edca->GetTxopLimit ();
edcaParameters.SetBkAci (1);
edcaParameters.SetBkCWmin (edca->GetMinCw ());
edcaParameters.SetBkCWmax (edca->GetMaxCw ());
edcaParameters.SetBkAifsn (edca->GetAifsn ());
edcaParameters.SetBkTXOPLimit (txopLimit.GetMicroSeconds () / 32);
edcaParameters.SetBkAcm (0);
edca = m_edca.find (AC_VI)->second;
txopLimit = edca->GetTxopLimit ();
edcaParameters.SetViAci (2);
edcaParameters.SetViCWmin (edca->GetMinCw ());
edcaParameters.SetViCWmax (edca->GetMaxCw ());
edcaParameters.SetViAifsn (edca->GetAifsn ());
edcaParameters.SetViTXOPLimit (txopLimit.GetMicroSeconds () / 32);
edcaParameters.SetViAcm (0);
edca = m_edca.find (AC_VO)->second;
txopLimit = edca->GetTxopLimit ();
edcaParameters.SetVoAci (3);
edcaParameters.SetVoCWmin (edca->GetMinCw ());
edcaParameters.SetVoCWmax (edca->GetMaxCw ());
edcaParameters.SetVoAifsn (edca->GetAifsn ());
edcaParameters.SetVoTXOPLimit (txopLimit.GetMicroSeconds () / 32);
edcaParameters.SetVoAcm (0);
edcaParameters.SetQosInfo (0);
}
return edcaParameters;
}
HtOperation
ApWifiMac::GetHtOperation (void) const
{
NS_LOG_FUNCTION (this);
HtOperation operation;
if (m_htSupported)
{
operation.SetHtSupported (1);
operation.SetRifsMode (GetRifsMode ());
operation.SetNonGfHtStasPresent (IsNonGfHtStasPresent ());
if (m_phy->GetChannelWidth () > 20)
{
operation.SetSecondaryChannelOffset (1);
operation.SetStaChannelWidth (1);
}
if (m_nonHtStations.empty ())
{
operation.SetHtProtection (NO_PROTECTION);
}
else
{
operation.SetHtProtection (MIXED_MODE_PROTECTION);
}
uint64_t maxSupportedRate = 0; //in bit/s
for (uint8_t i = 0; i < m_phy->GetNMcs (); i++)
{
WifiMode mcs = m_phy->GetMcs (i);
if (mcs.GetModulationClass () != WIFI_MOD_CLASS_HT)
{
continue;
}
uint8_t nss = (mcs.GetMcsValue () / 8) + 1;
NS_ASSERT (nss > 0 && nss < 5);
uint64_t dataRate = mcs.GetDataRate (m_phy->GetChannelWidth (), m_phy->GetShortGuardInterval () ? 400 : 800, nss);
if (dataRate > maxSupportedRate)
{
maxSupportedRate = dataRate;
NS_LOG_DEBUG ("Updating maxSupportedRate to " << maxSupportedRate);
}
}
uint8_t maxSpatialStream = m_phy->GetMaxSupportedTxSpatialStreams ();
uint8_t nMcs = m_phy->GetNMcs ();
for (std::map<uint16_t, Mac48Address>::const_iterator i = m_staList.begin (); i != m_staList.end (); i++)
{
if (m_stationManager->GetHtSupported (i->second))
{
uint64_t maxSupportedRateByHtSta = 0; //in bit/s
for (uint8_t j = 0; j < (std::min (nMcs, m_stationManager->GetNMcsSupported (i->second))); j++)
{
WifiMode mcs = m_phy->GetMcs (j);
if (mcs.GetModulationClass () != WIFI_MOD_CLASS_HT)
{
continue;
}
uint8_t nss = (mcs.GetMcsValue () / 8) + 1;
NS_ASSERT (nss > 0 && nss < 5);
uint64_t dataRate = mcs.GetDataRate (m_stationManager->GetChannelWidthSupported (i->second), m_stationManager->GetShortGuardInterval (i->second) ? 400 : 800, nss);
if (dataRate > maxSupportedRateByHtSta)
{
maxSupportedRateByHtSta = dataRate;
}
}
if (maxSupportedRateByHtSta < maxSupportedRate)
{
maxSupportedRate = maxSupportedRateByHtSta;
}
if (m_stationManager->GetNMcsSupported (i->second) < nMcs)
{
nMcs = m_stationManager->GetNMcsSupported (i->second);
}
if (m_stationManager->GetNumberOfSupportedStreams (i->second) < maxSpatialStream)
{
maxSpatialStream = m_stationManager->GetNumberOfSupportedStreams (i->second);
}
}
}
operation.SetRxHighestSupportedDataRate (maxSupportedRate / 1e6); //in Mbit/s
operation.SetTxMcsSetDefined (nMcs > 0);
operation.SetTxMaxNSpatialStreams (maxSpatialStream);
//To be filled in once supported
operation.SetObssNonHtStasPresent (0);
operation.SetDualBeacon (0);
operation.SetDualCtsProtection (0);
operation.SetStbcBeacon (0);
operation.SetLSigTxopProtectionFullSupport (0);
operation.SetPcoActive (0);
operation.SetPhase (0);
operation.SetRxMcsBitmask (0);
operation.SetTxRxMcsSetUnequal (0);
operation.SetTxUnequalModulation (0);
}
return operation;
}
VhtOperation
ApWifiMac::GetVhtOperation (void) const
{
NS_LOG_FUNCTION (this);
VhtOperation operation;
if (m_vhtSupported)
{
operation.SetVhtSupported (1);
uint8_t channelWidth = GetVhtOperationalChannelWidth ();
if (channelWidth == 160)
{
operation.SetChannelWidth (2);
}
else if (channelWidth == 80)
{
operation.SetChannelWidth (1);
}
else
{
operation.SetChannelWidth (0);
}
for (uint8_t nss = 1; nss <= 8; nss++)
{
uint8_t maxMcs;
if (nss <= m_phy->GetMaxSupportedRxSpatialStreams ())
{
maxMcs = 9; //TBD: hardcode to 9 for now since we assume all MCS values are supported
}
else
{
maxMcs = 0;
}
operation.SetMaxVhtMcsPerNss (nss, maxMcs);
}
}
return operation;
}
HeOperation
ApWifiMac::GetHeOperation (void) const
{
NS_LOG_FUNCTION (this);
HeOperation operation;
if (m_heSupported)
{
operation.SetHeSupported (1);
for (uint8_t nss = 1; nss <= m_phy->GetMaxSupportedRxSpatialStreams (); nss++)
{
operation.SetMaxHeMcsPerNss (nss, 11); //TBD: hardcode to 11 for now since we assume all MCS values are supported
}
}
return operation;
}
void
ApWifiMac::SendProbeResp (Mac48Address to)
{
NS_LOG_FUNCTION (this << to);
WifiMacHeader hdr;
hdr.SetType (WIFI_MAC_MGT_PROBE_RESPONSE);
hdr.SetAddr1 (to);
hdr.SetAddr2 (GetAddress ());
hdr.SetAddr3 (GetAddress ());
hdr.SetDsNotFrom ();
hdr.SetDsNotTo ();
hdr.SetNoOrder ();
Ptr<Packet> packet = Create<Packet> ();
MgtProbeResponseHeader probe;
probe.SetSsid (GetSsid ());
probe.SetSupportedRates (GetSupportedRates ());
probe.SetBeaconIntervalUs (m_beaconInterval.GetMicroSeconds ());
probe.SetCapabilities (GetCapabilities ());
m_stationManager->SetShortPreambleEnabled (GetShortPreambleEnabled ());
m_stationManager->SetShortSlotTimeEnabled (GetShortSlotTimeEnabled ());
if (m_dsssSupported)
{
probe.SetDsssParameterSet (GetDsssParameterSet ());
}
if (m_erpSupported)
{
probe.SetErpInformation (GetErpInformation ());
}
if (m_qosSupported)
{
probe.SetEdcaParameterSet (GetEdcaParameterSet ());
}
if (m_htSupported || m_vhtSupported || m_heSupported)
{
probe.SetExtendedCapabilities (GetExtendedCapabilities ());
probe.SetHtCapabilities (GetHtCapabilities ());
probe.SetHtOperation (GetHtOperation ());
}
if (m_vhtSupported || m_heSupported)
{
probe.SetVhtCapabilities (GetVhtCapabilities ());
probe.SetVhtOperation (GetVhtOperation ());
}
if (m_heSupported)
{
probe.SetHeCapabilities (GetHeCapabilities ());
probe.SetHeOperation (GetHeOperation ());
}
packet->AddHeader (probe);
//The standard is not clear on the correct queue for management
//frames if we are a QoS AP. The approach taken here is to always
//use the DCF for these regardless of whether we have a QoS
//association or not.
m_dca->Queue (packet, hdr);
}
void
ApWifiMac::SendAssocResp (Mac48Address to, bool success, bool isReassoc)
{
NS_LOG_FUNCTION (this << to << success << isReassoc);
WifiMacHeader hdr;
hdr.SetType (isReassoc ? WIFI_MAC_MGT_REASSOCIATION_RESPONSE : WIFI_MAC_MGT_ASSOCIATION_RESPONSE);
hdr.SetAddr1 (to);
hdr.SetAddr2 (GetAddress ());
hdr.SetAddr3 (GetAddress ());
hdr.SetDsNotFrom ();
hdr.SetDsNotTo ();
hdr.SetNoOrder ();
Ptr<Packet> packet = Create<Packet> ();
MgtAssocResponseHeader assoc;
StatusCode code;
if (success)
{
code.SetSuccess ();
uint16_t aid;
bool found = false;
if (isReassoc)
{
for (std::map<uint16_t, Mac48Address>::const_iterator i = m_staList.begin (); i != m_staList.end (); ++i)
{
if (i->second == to)
{
aid = i->first;
found = true;
break;
}
}
}
if (!found)
{
aid = GetNextAssociationId ();
m_staList.insert (std::make_pair (aid, to));
}
assoc.SetAssociationId (aid);
}
else
{
code.SetFailure ();
}
assoc.SetSupportedRates (GetSupportedRates ());
assoc.SetStatusCode (code);
assoc.SetCapabilities (GetCapabilities ());
if (m_erpSupported)
{
assoc.SetErpInformation (GetErpInformation ());
}
if (m_qosSupported)
{
assoc.SetEdcaParameterSet (GetEdcaParameterSet ());
}
if (m_htSupported || m_vhtSupported || m_heSupported)
{
assoc.SetExtendedCapabilities (GetExtendedCapabilities ());
assoc.SetHtCapabilities (GetHtCapabilities ());
assoc.SetHtOperation (GetHtOperation ());
}
if (m_vhtSupported || m_heSupported)
{
assoc.SetVhtCapabilities (GetVhtCapabilities ());
assoc.SetVhtOperation (GetVhtOperation ());
}
if (m_heSupported)
{
assoc.SetHeCapabilities (GetHeCapabilities ());
assoc.SetHeOperation (GetHeOperation ());
}
packet->AddHeader (assoc);
//The standard is not clear on the correct queue for management
//frames if we are a QoS AP. The approach taken here is to always
//use the DCF for these regardless of whether we have a QoS
//association or not.
m_dca->Queue (packet, hdr);
}
void
ApWifiMac::SendOneBeacon (void)
{
NS_LOG_FUNCTION (this);
WifiMacHeader hdr;
hdr.SetType (WIFI_MAC_MGT_BEACON);
hdr.SetAddr1 (Mac48Address::GetBroadcast ());
hdr.SetAddr2 (GetAddress ());
hdr.SetAddr3 (GetAddress ());
hdr.SetDsNotFrom ();
hdr.SetDsNotTo ();
hdr.SetNoOrder ();
Ptr<Packet> packet = Create<Packet> ();
MgtBeaconHeader beacon;
beacon.SetSsid (GetSsid ());
beacon.SetSupportedRates (GetSupportedRates ());
beacon.SetBeaconIntervalUs (m_beaconInterval.GetMicroSeconds ());
beacon.SetCapabilities (GetCapabilities ());
m_stationManager->SetShortPreambleEnabled (GetShortPreambleEnabled ());
m_stationManager->SetShortSlotTimeEnabled (GetShortSlotTimeEnabled ());
if (m_dsssSupported)
{
beacon.SetDsssParameterSet (GetDsssParameterSet ());
}
if (m_erpSupported)
{
beacon.SetErpInformation (GetErpInformation ());
}
if (m_qosSupported)
{
beacon.SetEdcaParameterSet (GetEdcaParameterSet ());
}
if (m_htSupported || m_vhtSupported)
{
beacon.SetExtendedCapabilities (GetExtendedCapabilities ());
beacon.SetHtCapabilities (GetHtCapabilities ());
beacon.SetHtOperation (GetHtOperation ());
}
if (m_vhtSupported || m_heSupported)
{
beacon.SetVhtCapabilities (GetVhtCapabilities ());
beacon.SetVhtOperation (GetVhtOperation ());
}
if (m_heSupported)
{
beacon.SetHeCapabilities (GetHeCapabilities ());
beacon.SetHeOperation (GetHeOperation ());
}
packet->AddHeader (beacon);
//The beacon has it's own special queue, so we load it in there
m_beaconDca->Queue (packet, hdr);
m_beaconEvent = Simulator::Schedule (m_beaconInterval, &ApWifiMac::SendOneBeacon, this);
//If a STA that does not support Short Slot Time associates,
//the AP shall use long slot time beginning at the first Beacon
//subsequent to the association of the long slot time STA.
if (m_erpSupported)
{
if (GetShortSlotTimeEnabled () == true)
{
//Enable short slot time
SetSlot (MicroSeconds (9));
}
else
{
//Disable short slot time
SetSlot (MicroSeconds (20));
}
}
}
void
ApWifiMac::TxOk (const WifiMacHeader &hdr)
{
NS_LOG_FUNCTION (this);
RegularWifiMac::TxOk (hdr);
if ((hdr.IsAssocResp () || hdr.IsReassocResp ())
&& m_stationManager->IsWaitAssocTxOk (hdr.GetAddr1 ()))
{
NS_LOG_DEBUG ("associated with sta=" << hdr.GetAddr1 ());
m_stationManager->RecordGotAssocTxOk (hdr.GetAddr1 ());
}
}
void
ApWifiMac::TxFailed (const WifiMacHeader &hdr)
{
NS_LOG_FUNCTION (this);
RegularWifiMac::TxFailed (hdr);
if ((hdr.IsAssocResp () || hdr.IsReassocResp ())
&& m_stationManager->IsWaitAssocTxOk (hdr.GetAddr1 ()))
{
NS_LOG_DEBUG ("association failed with sta=" << hdr.GetAddr1 ());
m_stationManager->RecordGotAssocTxFailed (hdr.GetAddr1 ());
}
}
void
ApWifiMac::Receive (Ptr<Packet> packet, const WifiMacHeader *hdr)
{
NS_LOG_FUNCTION (this << packet << hdr);
Mac48Address from = hdr->GetAddr2 ();
if (hdr->IsData ())
{
Mac48Address bssid = hdr->GetAddr1 ();
if (!hdr->IsFromDs ()
&& hdr->IsToDs ()
&& bssid == GetAddress ()
&& m_stationManager->IsAssociated (from))
{
Mac48Address to = hdr->GetAddr3 ();
if (to == GetAddress ())
{
NS_LOG_DEBUG ("frame for me from=" << from);
if (hdr->IsQosData ())
{
if (hdr->IsQosAmsdu ())
{
NS_LOG_DEBUG ("Received A-MSDU from=" << from << ", size=" << packet->GetSize ());
DeaggregateAmsduAndForward (packet, hdr);
packet = 0;
}
else
{
ForwardUp (packet, from, bssid);
}
}
else
{
ForwardUp (packet, from, bssid);
}
}
else if (to.IsGroup ()
|| m_stationManager->IsAssociated (to))
{
NS_LOG_DEBUG ("forwarding frame from=" << from << ", to=" << to);
Ptr<Packet> copy = packet->Copy ();
//If the frame we are forwarding is of type QoS Data,
//then we need to preserve the UP in the QoS control
//header...
if (hdr->IsQosData ())
{
ForwardDown (packet, from, to, hdr->GetQosTid ());
}
else
{
ForwardDown (packet, from, to);
}
ForwardUp (copy, from, to);
}
else
{
ForwardUp (packet, from, to);
}
}
else if (hdr->IsFromDs ()
&& hdr->IsToDs ())
{
//this is an AP-to-AP frame
//we ignore for now.
NotifyRxDrop (packet);
}
else
{
//we can ignore these frames since
//they are not targeted at the AP
NotifyRxDrop (packet);
}
return;
}
else if (hdr->IsMgt ())
{
if (hdr->IsProbeReq ())
{
NS_ASSERT (hdr->GetAddr1 ().IsBroadcast ());
NS_LOG_DEBUG ("Probe request received from " << from << ": send probe response");
SendProbeResp (from);
return;
}
else if (hdr->GetAddr1 () == GetAddress ())
{
if (hdr->IsAssocReq ())
{
NS_LOG_DEBUG ("Association request received from " << from);
//first, verify that the the station's supported
//rate set is compatible with our Basic Rate set
MgtAssocRequestHeader assocReq;
packet->RemoveHeader (assocReq);
CapabilityInformation capabilities = assocReq.GetCapabilities ();
m_stationManager->AddSupportedPlcpPreamble (from, capabilities.IsShortPreamble ());
SupportedRates rates = assocReq.GetSupportedRates ();
bool problem = false;
bool isHtStation = false;
bool isOfdmStation = false;
bool isErpStation = false;
bool isDsssStation = false;
for (uint8_t i = 0; i < m_stationManager->GetNBasicModes (); i++)
{
WifiMode mode = m_stationManager->GetBasicMode (i);
if (!rates.IsSupportedRate (mode.GetDataRate (m_phy->GetChannelWidth ())))
{
if ((mode.GetModulationClass () == WIFI_MOD_CLASS_DSSS) || (mode.GetModulationClass () == WIFI_MOD_CLASS_HR_DSSS))
{
isDsssStation = false;
}
else if (mode.GetModulationClass () == WIFI_MOD_CLASS_ERP_OFDM)
{
isErpStation = false;
}
else if (mode.GetModulationClass () == WIFI_MOD_CLASS_OFDM)
{
isOfdmStation = false;
}
if (isDsssStation == false && isErpStation == false && isOfdmStation == false)
{
problem = true;
break;
}
}
else
{
if ((mode.GetModulationClass () == WIFI_MOD_CLASS_DSSS) || (mode.GetModulationClass () == WIFI_MOD_CLASS_HR_DSSS))
{
isDsssStation = true;
}
else if (mode.GetModulationClass () == WIFI_MOD_CLASS_ERP_OFDM)
{
isErpStation = true;
}
else if (mode.GetModulationClass () == WIFI_MOD_CLASS_OFDM)
{
isOfdmStation = true;
}
}
}
m_stationManager->AddSupportedErpSlotTime (from, capabilities.IsShortSlotTime () && isErpStation);
if (m_htSupported)
{
//check whether the HT STA supports all MCSs in Basic MCS Set
HtCapabilities htcapabilities = assocReq.GetHtCapabilities ();
if (htcapabilities.IsSupportedMcs (0))
{
isHtStation = true;
for (uint8_t i = 0; i < m_stationManager->GetNBasicMcs (); i++)
{
WifiMode mcs = m_stationManager->GetBasicMcs (i);
if (!htcapabilities.IsSupportedMcs (mcs.GetMcsValue ()))
{
problem = true;
break;
}
}
}
}
if (m_vhtSupported)
{
//check whether the VHT STA supports all MCSs in Basic MCS Set
VhtCapabilities vhtcapabilities = assocReq.GetVhtCapabilities ();
if (vhtcapabilities.GetVhtCapabilitiesInfo () != 0)
{
for (uint8_t i = 0; i < m_stationManager->GetNBasicMcs (); i++)
{
WifiMode mcs = m_stationManager->GetBasicMcs (i);
if (!vhtcapabilities.IsSupportedTxMcs (mcs.GetMcsValue ()))
{
problem = true;
break;
}
}
}
}
if (m_heSupported)
{
//check whether the HE STA supports all MCSs in Basic MCS Set
HeCapabilities hecapabilities = assocReq.GetHeCapabilities ();
if (hecapabilities.GetSupportedMcsAndNss () != 0)
{
for (uint8_t i = 0; i < m_stationManager->GetNBasicMcs (); i++)
{
WifiMode mcs = m_stationManager->GetBasicMcs (i);
if (!hecapabilities.IsSupportedTxMcs (mcs.GetMcsValue ()))
{
problem = true;
break;
}
}
}
}
if (problem)
{
NS_LOG_DEBUG ("One of the Basic Rate set mode is not supported by the station: send association response with an error status");
SendAssocResp (hdr->GetAddr2 (), false, false);
}
else
{
NS_LOG_DEBUG ("The Basic Rate set modes are supported by the station");
//record all its supported modes in its associated WifiRemoteStation
for (uint8_t j = 0; j < m_phy->GetNModes (); j++)
{
WifiMode mode = m_phy->GetMode (j);
if (rates.IsSupportedRate (mode.GetDataRate (m_phy->GetChannelWidth ())))
{
m_stationManager->AddSupportedMode (from, mode);
}
}
if (m_htSupported)
{
HtCapabilities htCapabilities = assocReq.GetHtCapabilities ();
if (htCapabilities.IsSupportedMcs (0))
{
m_stationManager->AddStationHtCapabilities (from, htCapabilities);
}
}
if (m_vhtSupported)
{
VhtCapabilities vhtCapabilities = assocReq.GetVhtCapabilities ();
//we will always fill in RxHighestSupportedLgiDataRate field at TX, so this can be used to check whether it supports VHT
if (vhtCapabilities.GetRxHighestSupportedLgiDataRate () > 0)
{
m_stationManager->AddStationVhtCapabilities (from, vhtCapabilities);
for (uint8_t i = 0; i < m_phy->GetNMcs (); i++)
{
WifiMode mcs = m_phy->GetMcs (i);
if (mcs.GetModulationClass () == WIFI_MOD_CLASS_VHT && vhtCapabilities.IsSupportedTxMcs (mcs.GetMcsValue ()))
{
m_stationManager->AddSupportedMcs (hdr->GetAddr2 (), mcs);
//here should add a control to add basic MCS when it is implemented
}
}
}
}
if (m_heSupported)
{
HeCapabilities heCapabilities = assocReq.GetHeCapabilities ();
//todo: once we support non constant rate managers, we should add checks here whether HE is supported by the peer
m_stationManager->AddStationHeCapabilities (from, heCapabilities);
for (uint8_t i = 0; i < m_phy->GetNMcs (); i++)
{
WifiMode mcs = m_phy->GetMcs (i);
if (mcs.GetModulationClass () == WIFI_MOD_CLASS_HE && heCapabilities.IsSupportedTxMcs (mcs.GetMcsValue ()))
{
m_stationManager->AddSupportedMcs (hdr->GetAddr2 (), mcs);
//here should add a control to add basic MCS when it is implemented
}
}
}
m_stationManager->RecordWaitAssocTxOk (from);
if (!isHtStation)
{
m_nonHtStations.push_back (hdr->GetAddr2 ());
m_nonHtStations.unique ();
}
if (!isErpStation && isDsssStation)
{
m_nonErpStations.push_back (hdr->GetAddr2 ());
m_nonErpStations.unique ();
}
NS_LOG_DEBUG ("Send association response with success status");
SendAssocResp (hdr->GetAddr2 (), true, false);
}
return;
}
else if (hdr->IsReassocReq ())
{
NS_LOG_DEBUG ("Reassociation request received from " << from);
//first, verify that the the station's supported
//rate set is compatible with our Basic Rate set
MgtReassocRequestHeader reassocReq;
packet->RemoveHeader (reassocReq);
CapabilityInformation capabilities = reassocReq.GetCapabilities ();
m_stationManager->AddSupportedPlcpPreamble (from, capabilities.IsShortPreamble ());
SupportedRates rates = reassocReq.GetSupportedRates ();
bool problem = false;
bool isHtStation = false;
bool isOfdmStation = false;
bool isErpStation = false;
bool isDsssStation = false;
for (uint8_t i = 0; i < m_stationManager->GetNBasicModes (); i++)
{
WifiMode mode = m_stationManager->GetBasicMode (i);
if (!rates.IsSupportedRate (mode.GetDataRate (m_phy->GetChannelWidth ())))
{
if ((mode.GetModulationClass () == WIFI_MOD_CLASS_DSSS) || (mode.GetModulationClass () == WIFI_MOD_CLASS_HR_DSSS))
{
isDsssStation = false;
}
else if (mode.GetModulationClass () == WIFI_MOD_CLASS_ERP_OFDM)
{
isErpStation = false;
}
else if (mode.GetModulationClass () == WIFI_MOD_CLASS_OFDM)
{
isOfdmStation = false;
}
if (isDsssStation == false && isErpStation == false && isOfdmStation == false)
{
problem = true;
break;
}
}
else
{
if ((mode.GetModulationClass () == WIFI_MOD_CLASS_DSSS) || (mode.GetModulationClass () == WIFI_MOD_CLASS_HR_DSSS))
{
isDsssStation = true;
}
else if (mode.GetModulationClass () == WIFI_MOD_CLASS_ERP_OFDM)
{
isErpStation = true;
}
else if (mode.GetModulationClass () == WIFI_MOD_CLASS_OFDM)
{
isOfdmStation = true;
}
}
}
m_stationManager->AddSupportedErpSlotTime (from, capabilities.IsShortSlotTime () && isErpStation);
if (m_htSupported)
{
//check whether the HT STA supports all MCSs in Basic MCS Set
HtCapabilities htcapabilities = reassocReq.GetHtCapabilities ();
if (htcapabilities.IsSupportedMcs (0))
{
isHtStation = true;
for (uint8_t i = 0; i < m_stationManager->GetNBasicMcs (); i++)
{
WifiMode mcs = m_stationManager->GetBasicMcs (i);
if (!htcapabilities.IsSupportedMcs (mcs.GetMcsValue ()))
{
problem = true;
break;
}
}
}
}
if (m_vhtSupported)
{
//check whether the VHT STA supports all MCSs in Basic MCS Set
VhtCapabilities vhtcapabilities = reassocReq.GetVhtCapabilities ();
if (vhtcapabilities.GetVhtCapabilitiesInfo () != 0)
{
for (uint8_t i = 0; i < m_stationManager->GetNBasicMcs (); i++)
{
WifiMode mcs = m_stationManager->GetBasicMcs (i);
if (!vhtcapabilities.IsSupportedTxMcs (mcs.GetMcsValue ()))
{
problem = true;
break;
}
}
}
}
if (m_heSupported)
{
//check whether the HE STA supports all MCSs in Basic MCS Set
HeCapabilities hecapabilities = reassocReq.GetHeCapabilities ();
if (hecapabilities.GetSupportedMcsAndNss () != 0)
{
for (uint8_t i = 0; i < m_stationManager->GetNBasicMcs (); i++)
{
WifiMode mcs = m_stationManager->GetBasicMcs (i);
if (!hecapabilities.IsSupportedTxMcs (mcs.GetMcsValue ()))
{
problem = true;
break;
}
}
}
}
if (problem)
{
NS_LOG_DEBUG ("One of the Basic Rate set mode is not supported by the station: send reassociation response with an error status");
SendAssocResp (hdr->GetAddr2 (), false, true);
}
else
{
NS_LOG_DEBUG ("The Basic Rate set modes are supported by the station");
//update all its supported modes in its associated WifiRemoteStation
for (uint8_t j = 0; j < m_phy->GetNModes (); j++)
{
WifiMode mode = m_phy->GetMode (j);
if (rates.IsSupportedRate (mode.GetDataRate (m_phy->GetChannelWidth ())))
{
m_stationManager->AddSupportedMode (from, mode);
}
}
if (m_htSupported)
{
HtCapabilities htCapabilities = reassocReq.GetHtCapabilities ();
if (htCapabilities.IsSupportedMcs (0))
{
m_stationManager->AddStationHtCapabilities (from, htCapabilities);
}
}
if (m_vhtSupported)
{
VhtCapabilities vhtCapabilities = reassocReq.GetVhtCapabilities ();
//we will always fill in RxHighestSupportedLgiDataRate field at TX, so this can be used to check whether it supports VHT
if (vhtCapabilities.GetRxHighestSupportedLgiDataRate () > 0)
{
m_stationManager->AddStationVhtCapabilities (from, vhtCapabilities);
for (uint8_t i = 0; i < m_phy->GetNMcs (); i++)
{
WifiMode mcs = m_phy->GetMcs (i);
if (mcs.GetModulationClass () == WIFI_MOD_CLASS_VHT && vhtCapabilities.IsSupportedTxMcs (mcs.GetMcsValue ()))
{
m_stationManager->AddSupportedMcs (hdr->GetAddr2 (), mcs);
//here should add a control to add basic MCS when it is implemented
}
}
}
}
if (m_heSupported)
{
HeCapabilities heCapabilities = reassocReq.GetHeCapabilities ();
//todo: once we support non constant rate managers, we should add checks here whether HE is supported by the peer
m_stationManager->AddStationHeCapabilities (from, heCapabilities);
for (uint8_t i = 0; i < m_phy->GetNMcs (); i++)
{
WifiMode mcs = m_phy->GetMcs (i);
if (mcs.GetModulationClass () == WIFI_MOD_CLASS_HE && heCapabilities.IsSupportedTxMcs (mcs.GetMcsValue ()))
{
m_stationManager->AddSupportedMcs (hdr->GetAddr2 (), mcs);
//here should add a control to add basic MCS when it is implemented
}
}
}
m_stationManager->RecordWaitAssocTxOk (from);
if (!isHtStation)
{
m_nonHtStations.push_back (hdr->GetAddr2 ());
m_nonHtStations.unique ();
}
if (!isErpStation && isDsssStation)
{
m_nonErpStations.push_back (hdr->GetAddr2 ());
m_nonErpStations.unique ();
}
NS_LOG_DEBUG ("Send reassociation response with success status");
SendAssocResp (hdr->GetAddr2 (), true, true);
}
return;
}
else if (hdr->IsDisassociation ())
{
NS_LOG_DEBUG ("Disassociation received from " << from);
m_stationManager->RecordDisassociated (from);
for (std::map<uint16_t, Mac48Address>::const_iterator j = m_staList.begin (); j != m_staList.end (); j++)
{
if (j->second == from)
{
m_staList.erase (j);
break;
}
}
for (std::list<Mac48Address>::const_iterator j = m_nonErpStations.begin (); j != m_nonErpStations.end (); j++)
{
if ((*j) == from)
{
m_nonErpStations.erase (j);
break;
}
}
for (std::list<Mac48Address>::const_iterator j = m_nonHtStations.begin (); j != m_nonHtStations.end (); j++)
{
if ((*j) == from)
{
m_nonHtStations.erase (j);
break;
}
}
return;
}
}
}
//Invoke the receive handler of our parent class to deal with any
//other frames. Specifically, this will handle Block Ack-related
//Management Action frames.
RegularWifiMac::Receive (packet, hdr);
}
void
ApWifiMac::DeaggregateAmsduAndForward (Ptr<Packet> aggregatedPacket, const WifiMacHeader *hdr)
{
NS_LOG_FUNCTION (this << aggregatedPacket << hdr);
MsduAggregator::DeaggregatedMsdus packets = MsduAggregator::Deaggregate (aggregatedPacket);
for (MsduAggregator::DeaggregatedMsdusCI i = packets.begin ();
i != packets.end (); ++i)
{
if ((*i).second.GetDestinationAddr () == GetAddress ())
{
ForwardUp ((*i).first, (*i).second.GetSourceAddr (),
(*i).second.GetDestinationAddr ());
}
else
{
Mac48Address from = (*i).second.GetSourceAddr ();
Mac48Address to = (*i).second.GetDestinationAddr ();
NS_LOG_DEBUG ("forwarding QoS frame from=" << from << ", to=" << to);
ForwardDown ((*i).first, from, to, hdr->GetQosTid ());
}
}
}
void
ApWifiMac::DoInitialize (void)
{
NS_LOG_FUNCTION (this);
m_beaconDca->Initialize ();
m_beaconEvent.Cancel ();
if (m_enableBeaconGeneration)
{
if (m_enableBeaconJitter)
{
int64_t jitter = m_beaconJitter->GetValue (0, m_beaconInterval.GetMicroSeconds ());
NS_LOG_DEBUG ("Scheduling initial beacon for access point " << GetAddress () << " at time " << jitter << " microseconds");
m_beaconEvent = Simulator::Schedule (MicroSeconds (jitter), &ApWifiMac::SendOneBeacon, this);
}
else
{
NS_LOG_DEBUG ("Scheduling initial beacon for access point " << GetAddress () << " at time 0");
m_beaconEvent = Simulator::ScheduleNow (&ApWifiMac::SendOneBeacon, this);
}
}
RegularWifiMac::DoInitialize ();
}
bool
ApWifiMac::GetUseNonErpProtection (void) const
{
bool useProtection = !m_nonErpStations.empty () && m_enableNonErpProtection;
m_stationManager->SetUseNonErpProtection (useProtection);
return useProtection;
}
bool
ApWifiMac::GetRifsMode (void) const
{
bool rifsMode = false;
if (m_htSupported && !m_vhtSupported) //RIFS mode is forbidden for VHT
{
if (m_nonHtStations.empty () || !m_disableRifs)
{
rifsMode = true;
}
}
if (GetRifsSupported () && rifsMode)
{
m_stationManager->SetRifsPermitted (true);
}
else
{
m_stationManager->SetRifsPermitted (false);
}
return rifsMode;
}
uint16_t
ApWifiMac::GetNextAssociationId (void)
{
//Return the first free AID value between 1 and 2007
for (uint16_t nextAid = 1; nextAid <= 2007; nextAid++)
{
if (m_staList.find (nextAid) == m_staList.end ())
{
return nextAid;
}
}
NS_ASSERT_MSG (false, "No free association ID available!");
return 0;
}
} //namespace ns3