A Discrete-Event Network Simulator
API
channel-access-manager.cc
Go to the documentation of this file.
1 /*
2  * Copyright (c) 2005,2006 INRIA
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License version 2 as
6  * published by the Free Software Foundation;
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program; if not, write to the Free Software
15  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16  *
17  * Author: Mathieu Lacage <mathieu.lacage@sophia.inria.fr>
18  */
19 
20 #include "channel-access-manager.h"
21 
22 #include "txop.h"
23 #include "wifi-mac-queue.h"
24 #include "wifi-phy-listener.h"
25 #include "wifi-phy.h"
26 
27 #include "ns3/eht-frame-exchange-manager.h"
28 #include "ns3/log.h"
29 #include "ns3/simulator.h"
30 
31 #include <sstream>
32 
33 #undef NS_LOG_APPEND_CONTEXT
34 #define NS_LOG_APPEND_CONTEXT std::clog << "[link=" << +m_linkId << "] "
35 
36 namespace ns3
37 {
38 
39 NS_LOG_COMPONENT_DEFINE("ChannelAccessManager");
40 
41 NS_OBJECT_ENSURE_REGISTERED(ChannelAccessManager);
42 
51 {
52  public:
59  : m_cam(cam),
60  m_active(true)
61  {
62  }
63 
64  ~PhyListener() override
65  {
66  }
67 
73  void SetActive(bool active)
74  {
75  m_active = active;
76  }
77 
81  bool IsActive() const
82  {
83  return m_active;
84  }
85 
86  void NotifyRxStart(Time duration) override
87  {
88  if (m_active)
89  {
90  m_cam->NotifyRxStartNow(duration);
91  }
92  }
93 
94  void NotifyRxEndOk() override
95  {
96  if (m_active)
97  {
99  }
100  }
101 
102  void NotifyRxEndError() override
103  {
104  if (m_active)
105  {
107  }
108  }
109 
110  void NotifyTxStart(Time duration, double txPowerDbm) override
111  {
112  if (m_active)
113  {
114  m_cam->NotifyTxStartNow(duration);
115  }
116  }
117 
118  void NotifyCcaBusyStart(Time duration,
119  WifiChannelListType channelType,
120  const std::vector<Time>& per20MhzDurations) override
121  {
122  if (m_active)
123  {
124  m_cam->NotifyCcaBusyStartNow(duration, channelType, per20MhzDurations);
125  }
126  }
127 
128  void NotifySwitchingStart(Time duration) override
129  {
130  m_cam->NotifySwitchingStartNow(this, duration);
131  }
132 
133  void NotifySleep() override
134  {
135  if (m_active)
136  {
138  }
139  }
140 
141  void NotifyOff() override
142  {
143  if (m_active)
144  {
145  m_cam->NotifyOffNow();
146  }
147  }
148 
149  void NotifyWakeup() override
150  {
151  if (m_active)
152  {
154  }
155  }
156 
157  void NotifyOn() override
158  {
159  if (m_active)
160  {
161  m_cam->NotifyOnNow();
162  }
163  }
164 
165  private:
167  bool m_active;
168 };
169 
170 /****************************************************************
171  * Implement the channel access manager of all Txop holders
172  ****************************************************************/
173 
174 TypeId
176 {
177  static TypeId tid =
178  TypeId("ns3::ChannelAccessManager")
180  .SetGroupName("Wifi")
181  .AddConstructor<ChannelAccessManager>()
182  .AddAttribute("GenerateBackoffIfTxopWithoutTx",
183  "Specify whether the backoff should be invoked when the AC gains the "
184  "right to start a TXOP but it does not transmit any frame "
185  "(e.g., due to constraints associated with EMLSR operations), "
186  "provided that the queue is not actually empty.",
187  BooleanValue(false),
191  return tid;
192 }
193 
195  : m_lastAckTimeoutEnd(0),
196  m_lastCtsTimeoutEnd(0),
197  m_lastNavEnd(0),
198  m_lastRx({MicroSeconds(0), MicroSeconds(0)}),
199  m_lastRxReceivedOk(true),
200  m_lastTxEnd(0),
201  m_lastSwitchingEnd(0),
202  m_usingOtherEmlsrLink(false),
203  m_sleeping(false),
204  m_off(false),
205  m_linkId(0)
206 {
207  NS_LOG_FUNCTION(this);
208  InitLastBusyStructs();
209 }
210 
212 {
213  NS_LOG_FUNCTION(this);
214 }
215 
216 void
218 {
219  NS_LOG_FUNCTION(this);
221 }
222 
223 void
225 {
226  NS_LOG_FUNCTION(this);
227  for (Ptr<Txop> i : m_txops)
228  {
229  i->Dispose();
230  i = nullptr;
231  }
232  m_phy = nullptr;
233  m_feManager = nullptr;
234  m_phyListeners.clear();
235 }
236 
237 std::shared_ptr<PhyListener>
239 {
240  if (auto listenerIt = m_phyListeners.find(phy); listenerIt != m_phyListeners.end())
241  {
242  return listenerIt->second;
243  }
244  return nullptr;
245 }
246 
247 void
249 {
250  NS_LOG_FUNCTION(this << phy);
251 
252  if (auto phyListener = GetPhyListener(phy))
253  {
254  // a PHY listener for the given PHY already exists, it must be inactive
255  NS_ASSERT_MSG(!phyListener->IsActive(),
256  "There is already an active listener registered for given PHY");
257  NS_ASSERT_MSG(!m_phy, "Cannot reactivate a listener if another PHY is active");
258  phyListener->SetActive(true);
259  }
260  else
261  {
262  phyListener = std::make_shared<PhyListener>(this);
263  m_phyListeners.emplace(phy, phyListener);
264  phy->RegisterListener(phyListener);
265  }
266  if (m_phy)
267  {
269  }
270  m_phy = phy; // this is the new active PHY
272  if (phy->IsStateSwitching())
273  {
274  auto duration = phy->GetDelayUntilIdle();
275  NS_LOG_DEBUG("switching start for " << duration);
276  m_lastSwitchingEnd = Simulator::Now() + duration;
277  }
278 }
279 
280 void
282 {
283  NS_LOG_FUNCTION(this << phy);
284  if (auto phyListener = GetPhyListener(phy))
285  {
286  phy->UnregisterListener(phyListener);
287  m_phyListeners.erase(phy);
288  // reset m_phy if we are removing listener registered for the active PHY
289  if (m_phy == phy)
290  {
291  m_phy = nullptr;
292  }
293  }
294 }
295 
296 void
298 {
299  NS_LOG_FUNCTION(this << phy);
300  if (auto listener = GetPhyListener(phy))
301  {
302  listener->SetActive(false);
303  }
304  if (m_phy == phy)
305  {
306  m_phy = nullptr;
307  }
308 }
309 
310 void
313  uint8_t linkId)
314 {
315  NS_LOG_FUNCTION(this << phy << channel << linkId);
317  "The given PHY is already expected to switch channel");
319 }
320 
321 void
323 {
324  NS_LOG_FUNCTION(this << +linkId);
325  m_linkId = linkId;
326 }
327 
328 void
330 {
331  NS_LOG_FUNCTION(this << feManager);
332  m_feManager = feManager;
333  m_feManager->SetChannelAccessManager(this);
334 }
335 
336 Time
338 {
339  return m_phy->GetSlot();
340 }
341 
342 Time
344 {
345  return m_phy->GetSifs();
346 }
347 
348 Time
350 {
351  return m_phy->GetSifs() + m_phy->GetAckTxTime();
352 }
353 
354 void
356 {
357  NS_LOG_FUNCTION(this << txop);
358  m_txops.push_back(txop);
359 }
360 
361 void
363 {
364  NS_LOG_FUNCTION(this);
365  Time now = Simulator::Now();
366  m_lastBusyEnd.clear();
367  m_lastPer20MHzBusyEnd.clear();
368  m_lastIdle.clear();
370  m_lastIdle[WIFI_CHANLIST_PRIMARY] = {now, now};
371 
372  if (!m_phy || !m_phy->GetOperatingChannel().IsOfdm())
373  {
374  return;
375  }
376 
377  uint16_t width = m_phy->GetChannelWidth();
378 
379  if (width >= 40)
380  {
382  m_lastIdle[WIFI_CHANLIST_SECONDARY] = {now, now};
383  }
384  if (width >= 80)
385  {
388  }
389  if (width >= 160)
390  {
393  }
394  // TODO Add conditions for new channel widths as they get supported
395 
396  if (m_phy->GetStandard() >= WIFI_STANDARD_80211ax && width > 20)
397  {
398  m_lastPer20MHzBusyEnd.assign(width / 20, now);
399  }
400 }
401 
402 bool
404 {
405  NS_LOG_FUNCTION(this);
406  Time now = Simulator::Now();
407  return (m_lastRx.end > now) // RX
408  || (m_lastTxEnd > now) // TX
409  || (m_lastNavEnd > now) // NAV busy
410  // an EDCA TXOP is obtained based solely on activity of the primary channel
411  // (Sec. 10.23.2.5 of IEEE 802.11-2020)
412  || (m_lastBusyEnd.at(WIFI_CHANLIST_PRIMARY) > now); // CCA busy
413 }
414 
415 bool
417  bool hadFramesToTransmit,
418  bool checkMediumBusy)
419 {
420  NS_LOG_FUNCTION(this << txop << hadFramesToTransmit << checkMediumBusy);
421 
422  // No backoff needed if in sleep mode or off. Checking if m_phy is nullptr is a workaround
423  // needed for EMLSR and may be removed in the future
424  if (m_sleeping || m_off || !m_phy)
425  {
426  return false;
427  }
428 
429  // the Txop might have a stale value of remaining backoff slots
430  UpdateBackoff();
431 
432  /*
433  * From section 10.3.4.2 "Basic access" of IEEE 802.11-2016:
434  *
435  * A STA may transmit an MPDU when it is operating under the DCF access
436  * method, either in the absence of a PC, or in the CP of the PCF access
437  * method, when the STA determines that the medium is idle when a frame is
438  * queued for transmission, and remains idle for a period of a DIFS, or an
439  * EIFS (10.3.2.3.7) from the end of the immediately preceding medium-busy
440  * event, whichever is the greater, and the backoff timer is zero. Otherwise
441  * the random backoff procedure described in 10.3.4.3 shall be followed.
442  *
443  * From section 10.22.2.2 "EDCA backoff procedure" of IEEE 802.11-2016:
444  *
445  * The backoff procedure shall be invoked by an EDCAF when any of the following
446  * events occurs:
447  * a) An MA-UNITDATA.request primitive is received that causes a frame with that AC
448  * to be queued for transmission such that one of the transmit queues associated
449  * with that AC has now become non-empty and any other transmit queues
450  * associated with that AC are empty; the medium is busy on the primary channel
451  */
452  if (!hadFramesToTransmit && txop->HasFramesToTransmit(m_linkId) &&
454  {
455  if (checkMediumBusy && !IsBusy())
456  {
457  // medium idle. If this is a DCF, use immediate access (we can transmit
458  // in a DIFS if the medium remains idle). If this is an EDCAF, update
459  // the backoff start time kept by the EDCAF to the current time in order
460  // to correctly align the backoff start time at the next slot boundary
461  // (performed by the next call to ChannelAccessManager::RequestAccess())
462  Time delay =
463  (txop->IsQosTxop() ? Seconds(0) : GetSifs() + txop->GetAifsn(m_linkId) * GetSlot());
464  txop->UpdateBackoffSlotsNow(0, Simulator::Now() + delay, m_linkId);
465  }
466  else
467  {
468  // medium busy, backoff is needed
469  return true;
470  }
471  }
472  return false;
473 }
474 
475 void
477 {
478  NS_LOG_FUNCTION(this << txop);
479  if (m_phy && txop->HasFramesToTransmit(m_linkId))
480  {
482  }
483  // Deny access if in sleep mode or off. Checking if m_phy is nullptr is a workaround
484  // needed for EMLSR and may be removed in the future
485  if (m_sleeping || m_off || !m_phy)
486  {
487  return;
488  }
489  /*
490  * EDCAF operations shall be performed at slot boundaries (Sec. 10.22.2.4 of 802.11-2016)
491  */
492  Time accessGrantStart = GetAccessGrantStart() + (txop->GetAifsn(m_linkId) * GetSlot());
493 
494  if (txop->IsQosTxop() && txop->GetBackoffStart(m_linkId) > accessGrantStart)
495  {
496  // The backoff start time reported by the EDCAF is more recent than the last
497  // time the medium was busy plus an AIFS, hence we need to align it to the
498  // next slot boundary.
499  Time diff = txop->GetBackoffStart(m_linkId) - accessGrantStart;
500  uint32_t nIntSlots = (diff / GetSlot()).GetHigh() + 1;
501  txop->UpdateBackoffSlotsNow(0, accessGrantStart + (nIntSlots * GetSlot()), m_linkId);
502  }
503 
504  UpdateBackoff();
509 }
510 
511 void
513 {
514  NS_LOG_FUNCTION(this);
515  uint32_t k = 0;
516  Time now = Simulator::Now();
517  for (auto i = m_txops.begin(); i != m_txops.end(); k++)
518  {
519  Ptr<Txop> txop = *i;
520  if (txop->GetAccessStatus(m_linkId) == Txop::REQUESTED &&
521  (!txop->IsQosTxop() || !StaticCast<QosTxop>(txop)->EdcaDisabled(m_linkId)) &&
522  GetBackoffEndFor(txop) <= now)
523  {
528  NS_LOG_DEBUG("dcf " << k << " needs access. backoff expired. access granted. slots="
529  << txop->GetBackoffSlots(m_linkId));
530  i++; // go to the next item in the list.
531  k++;
532  std::vector<Ptr<Txop>> internalCollisionTxops;
533  for (auto j = i; j != m_txops.end(); j++, k++)
534  {
535  Ptr<Txop> otherTxop = *j;
536  if (otherTxop->GetAccessStatus(m_linkId) == Txop::REQUESTED &&
537  GetBackoffEndFor(otherTxop) <= now)
538  {
539  NS_LOG_DEBUG(
540  "dcf " << k << " needs access. backoff expired. internal collision. slots="
541  << otherTxop->GetBackoffSlots(m_linkId));
547  internalCollisionTxops.push_back(otherTxop);
548  }
549  }
550 
560  // If we are operating on an OFDM channel wider than 20 MHz, find the largest
561  // idle primary channel and pass its width to the FrameExchangeManager, so that
562  // the latter can transmit PPDUs of the appropriate width (see Section 10.23.2.5
563  // of IEEE 802.11-2020).
564  auto interval = (m_phy->GetPhyBand() == WIFI_PHY_BAND_2_4GHZ)
565  ? GetSifs() + 2 * GetSlot()
566  : m_phy->GetPifs();
567  auto width = (m_phy->GetOperatingChannel().IsOfdm() && m_phy->GetChannelWidth() > 20)
568  ? GetLargestIdlePrimaryChannel(interval, now)
569  : m_phy->GetChannelWidth();
570  if (m_feManager->StartTransmission(txop, width))
571  {
572  for (auto& collidingTxop : internalCollisionTxops)
573  {
574  m_feManager->NotifyInternalCollision(collidingTxop);
575  }
576  break;
577  }
578  else
579  {
580  // this TXOP did not transmit anything, make sure that backoff counter starts
581  // decreasing in a slot again
582  txop->UpdateBackoffSlotsNow(0, now, m_linkId);
583  // reset the current state to the EDCAF that won the contention
584  // but did not transmit anything
585  i--;
586  k = std::distance(m_txops.begin(), i);
587  }
588  }
589  i++;
590  }
591 }
592 
593 void
595 {
596  NS_LOG_FUNCTION(this);
597  UpdateBackoff();
600 }
601 
602 Time
604 {
605  NS_LOG_FUNCTION(this);
606  const Time& sifs = GetSifs();
607  Time rxAccessStart = m_lastRx.end + sifs;
609  {
610  rxAccessStart += GetEifsNoDifs();
611  }
612  // an EDCA TXOP is obtained based solely on activity of the primary channel
613  // (Sec. 10.23.2.5 of IEEE 802.11-2020)
614  Time busyAccessStart = m_lastBusyEnd.at(WIFI_CHANLIST_PRIMARY) + sifs;
615  Time txAccessStart = m_lastTxEnd + sifs;
616  Time navAccessStart = m_lastNavEnd + sifs;
617  Time ackTimeoutAccessStart = m_lastAckTimeoutEnd + sifs;
618  Time ctsTimeoutAccessStart = m_lastCtsTimeoutEnd + sifs;
619  Time switchingAccessStart = m_lastSwitchingEnd + sifs;
620  Time accessGrantedStart;
621  if (ignoreNav)
622  {
623  accessGrantedStart = std::max({rxAccessStart,
624  busyAccessStart,
625  txAccessStart,
626  ackTimeoutAccessStart,
627  ctsTimeoutAccessStart,
628  switchingAccessStart});
629  }
630  else
631  {
632  accessGrantedStart = std::max({rxAccessStart,
633  busyAccessStart,
634  txAccessStart,
635  navAccessStart,
636  ackTimeoutAccessStart,
637  ctsTimeoutAccessStart,
638  switchingAccessStart});
639  }
640  NS_LOG_INFO("access grant start=" << accessGrantedStart.As(Time::US)
641  << ", rx access start=" << rxAccessStart.As(Time::US)
642  << ", busy access start=" << busyAccessStart.As(Time::US)
643  << ", tx access start=" << txAccessStart.As(Time::US)
644  << ", nav access start=" << navAccessStart.As(Time::US)
645  << ", switching access start="
646  << switchingAccessStart.As(Time::US));
647  return accessGrantedStart;
648 }
649 
650 Time
652 {
653  NS_LOG_FUNCTION(this << txop);
654  Time mostRecentEvent =
656  GetAccessGrantStart() + (txop->GetAifsn(m_linkId) * GetSlot())});
657  NS_LOG_DEBUG("Backoff start for " << txop->GetWifiMacQueue()->GetAc() << ": "
658  << mostRecentEvent.As(Time::US));
659 
660  return mostRecentEvent;
661 }
662 
663 Time
665 {
666  NS_LOG_FUNCTION(this << txop);
667  Time backoffEnd = GetBackoffStartFor(txop) + (txop->GetBackoffSlots(m_linkId) * GetSlot());
668  NS_LOG_DEBUG("Backoff end for " << txop->GetWifiMacQueue()->GetAc() << ": "
669  << backoffEnd.As(Time::US));
670 
671  return backoffEnd;
672 }
673 
674 void
676 {
677  NS_LOG_FUNCTION(this);
678  uint32_t k = 0;
679  for (auto txop : m_txops)
680  {
681  Time backoffStart = GetBackoffStartFor(txop);
682  if (backoffStart <= Simulator::Now())
683  {
684  uint32_t nIntSlots = ((Simulator::Now() - backoffStart) / GetSlot()).GetHigh();
685  /*
686  * EDCA behaves slightly different to DCA. For EDCA we
687  * decrement once at the slot boundary at the end of AIFS as
688  * well as once at the end of each clear slot
689  * thereafter. For DCA we only decrement at the end of each
690  * clear slot after DIFS. We account for the extra backoff
691  * by incrementing the slot count here in the case of
692  * EDCA. The if statement whose body we are in has confirmed
693  * that a minimum of AIFS has elapsed since last busy
694  * medium.
695  */
696  if (txop->IsQosTxop())
697  {
698  nIntSlots++;
699  }
700  uint32_t n = std::min(nIntSlots, txop->GetBackoffSlots(m_linkId));
701  NS_LOG_DEBUG("dcf " << k << " dec backoff slots=" << n);
702  Time backoffUpdateBound = backoffStart + (n * GetSlot());
703  txop->UpdateBackoffSlotsNow(n, backoffUpdateBound, m_linkId);
704  }
705  ++k;
706  }
707 }
708 
709 void
711 {
712  NS_LOG_FUNCTION(this);
717  bool accessTimeoutNeeded = false;
718  Time expectedBackoffEnd = Simulator::GetMaximumSimulationTime();
719  for (auto txop : m_txops)
720  {
721  if (txop->GetAccessStatus(m_linkId) == Txop::REQUESTED)
722  {
723  Time tmp = GetBackoffEndFor(txop);
724  if (tmp > Simulator::Now())
725  {
726  accessTimeoutNeeded = true;
727  expectedBackoffEnd = std::min(expectedBackoffEnd, tmp);
728  }
729  }
730  }
731  NS_LOG_DEBUG("Access timeout needed: " << accessTimeoutNeeded);
732  if (accessTimeoutNeeded)
733  {
734  NS_LOG_DEBUG("expected backoff end=" << expectedBackoffEnd);
735  Time expectedBackoffDelay = expectedBackoffEnd - Simulator::Now();
736  if (m_accessTimeout.IsRunning() &&
737  Simulator::GetDelayLeft(m_accessTimeout) > expectedBackoffDelay)
738  {
740  }
742  {
743  m_accessTimeout = Simulator::Schedule(expectedBackoffDelay,
745  this);
746  }
747  }
748 }
749 
750 uint16_t
752 {
753  NS_LOG_FUNCTION(this << interval.As(Time::US) << end.As(Time::S));
754 
755  // If the medium is busy or it just became idle, UpdateLastIdlePeriod does
756  // nothing. This allows us to call this method, e.g., at the end of a frame
757  // reception and check the busy/idle status of the channel before the start
758  // of the frame reception (last idle period was last updated at the start of
759  // the frame reception).
760  // If the medium has been idle for some time, UpdateLastIdlePeriod updates
761  // the last idle period. This is normally what we want because this method may
762  // also be called before starting a TXOP gained through EDCA.
764 
765  uint16_t width = 0;
766 
767  // we iterate over the different types of channels in the same order as they
768  // are listed in WifiChannelListType
769  for (const auto& lastIdle : m_lastIdle)
770  {
771  if (lastIdle.second.start <= end - interval && lastIdle.second.end >= end)
772  {
773  // channel idle, update width
774  width = (width == 0) ? 20 : (2 * width);
775  }
776  else
777  {
778  break;
779  }
780  }
781  return width;
782 }
783 
784 bool
785 ChannelAccessManager::GetPer20MHzBusy(const std::set<uint8_t>& indices) const
786 {
787  const auto now = Simulator::Now();
788 
789  if (m_phy->GetChannelWidth() < 40)
790  {
791  NS_ASSERT_MSG(indices.size() == 1 && *indices.cbegin() == 0,
792  "Index 0 only can be specified if the channel width is less than 40 MHz");
793  return m_lastBusyEnd.at(WIFI_CHANLIST_PRIMARY) > now;
794  }
795 
796  for (const auto index : indices)
797  {
798  NS_ASSERT(index < m_lastPer20MHzBusyEnd.size());
799  if (m_lastPer20MHzBusyEnd.at(index) > now)
800  {
801  NS_LOG_DEBUG("20 MHz channel with index " << +index << " is busy");
802  return true;
803  }
804  }
805  return false;
806 }
807 
808 void
810 {
811  NS_LOG_FUNCTION(this << qosTxop << duration);
812  NS_ASSERT(qosTxop->IsQosTxop());
813  UpdateBackoff();
814  Time resume = Simulator::Now() + duration;
815  NS_LOG_DEBUG("Backoff will resume at time " << resume << " with "
816  << qosTxop->GetBackoffSlots(m_linkId)
817  << " remaining slot(s)");
818  qosTxop->UpdateBackoffSlotsNow(0, resume, m_linkId);
820 }
821 
822 void
824 {
825  NS_LOG_FUNCTION(this << enable);
826  m_generateBackoffOnNoTx = enable;
827 }
828 
829 bool
831 {
833 }
834 
835 void
837 {
838  NS_LOG_FUNCTION(this << duration);
839  NS_LOG_DEBUG("rx start for=" << duration);
840  UpdateBackoff();
843  m_lastRx.end = m_lastRx.start + duration;
844  m_lastRxReceivedOk = true;
845 }
846 
847 void
849 {
850  NS_LOG_FUNCTION(this);
851  NS_LOG_DEBUG("rx end ok");
853  m_lastRxReceivedOk = true;
854 }
855 
856 void
858 {
859  NS_LOG_FUNCTION(this);
860  NS_LOG_DEBUG("rx end error");
861  // we expect the PHY to notify us of the start of a CCA busy period, if needed
863  m_lastRxReceivedOk = false;
864 }
865 
866 void
868 {
869  NS_LOG_FUNCTION(this << duration);
870  m_lastRxReceivedOk = true;
871  Time now = Simulator::Now();
872  if (m_lastRx.end > now)
873  {
874  // this may be caused only if PHY has started to receive a packet
875  // inside SIFS, so, we check that lastRxStart was maximum a SIFS ago
876  NS_ASSERT(now - m_lastRx.start <= GetSifs());
877  m_lastRx.end = now;
878  }
879  else
880  {
882  }
883  NS_LOG_DEBUG("tx start for " << duration);
884  UpdateBackoff();
885  m_lastTxEnd = now + duration;
886 }
887 
888 void
890  WifiChannelListType channelType,
891  const std::vector<Time>& per20MhzDurations)
892 {
893  NS_LOG_FUNCTION(this << duration << channelType);
894  UpdateBackoff();
896  auto lastBusyEndIt = m_lastBusyEnd.find(channelType);
897  NS_ASSERT(lastBusyEndIt != m_lastBusyEnd.end());
898  Time now = Simulator::Now();
899  lastBusyEndIt->second = now + duration;
900  NS_ASSERT_MSG(per20MhzDurations.size() == m_lastPer20MHzBusyEnd.size(),
901  "Size of received vector (" << per20MhzDurations.size()
902  << ") differs from the expected size ("
903  << m_lastPer20MHzBusyEnd.size() << ")");
904  for (std::size_t chIdx = 0; chIdx < per20MhzDurations.size(); ++chIdx)
905  {
906  if (per20MhzDurations[chIdx].IsStrictlyPositive())
907  {
908  m_lastPer20MHzBusyEnd[chIdx] = now + per20MhzDurations[chIdx];
909  }
910  }
911 }
912 
913 void
915 {
916  NS_LOG_FUNCTION(this << phyListener << duration);
917 
918  Time now = Simulator::Now();
919  NS_ASSERT(m_lastTxEnd <= now);
921 
922  if (phyListener) // to make tests happy
923  {
924  // check if the PHY switched channel to operate on another EMLSR link
925 
926  for (const auto& [phyRef, listener] : m_phyListeners)
927  {
928  Ptr<WifiPhy> phy = phyRef;
929  auto emlsrInfoIt = m_switchingEmlsrLinks.find(phy);
930 
931  if (listener.get() == phyListener && emlsrInfoIt != m_switchingEmlsrLinks.cend() &&
932  phy->GetOperatingChannel() == emlsrInfoIt->second.channel)
933  {
934  // the PHY associated with the given PHY listener switched channel to
935  // operate on another EMLSR link as expected. We don't need this listener
936  // anymore. The MAC will connect a new listener to the ChannelAccessManager
937  // instance associated with the link the PHY is now operating on
939  auto ehtFem = DynamicCast<EhtFrameExchangeManager>(m_feManager);
940  NS_ASSERT(ehtFem);
941  ehtFem->NotifySwitchingEmlsrLink(phy, emlsrInfoIt->second.linkId, duration);
942  m_switchingEmlsrLinks.erase(emlsrInfoIt);
943  return;
944  }
945  }
946  }
947 
948  ResetState();
949 
950  // Reset backoffs
951  for (const auto& txop : m_txops)
952  {
953  ResetBackoff(txop);
954  }
955 
956  // Notify the FEM, which will in turn notify the MAC
957  m_feManager->NotifySwitchingStartNow(duration);
958 
959  NS_LOG_DEBUG("switching start for " << duration);
960  m_lastSwitchingEnd = now + duration;
961 }
962 
963 void
965 {
966  NS_LOG_FUNCTION(this);
967 
968  Time now = Simulator::Now();
969  m_lastRxReceivedOk = true;
971  m_lastRx.end = std::min(m_lastRx.end, now);
975 
977 
978  // Cancel timeout
980  {
982  }
983 }
984 
985 void
987 {
988  NS_LOG_FUNCTION(this << txop);
989 
990  uint32_t remainingSlots = txop->GetBackoffSlots(m_linkId);
991  if (remainingSlots > 0)
992  {
993  txop->UpdateBackoffSlotsNow(remainingSlots, Simulator::Now(), m_linkId);
994  NS_ASSERT(txop->GetBackoffSlots(m_linkId) == 0);
995  }
996  txop->ResetCw(m_linkId);
998 }
999 
1000 void
1002 {
1003  NS_LOG_FUNCTION(this);
1004 
1005  for (const auto& txop : m_txops)
1006  {
1007  ResetBackoff(txop);
1008  }
1010 }
1011 
1012 void
1014 {
1015  NS_LOG_FUNCTION(this);
1016  m_sleeping = true;
1017  // Cancel timeout
1018  if (m_accessTimeout.IsRunning())
1019  {
1021  }
1022 
1023  // Reset backoffs
1024  for (auto txop : m_txops)
1025  {
1026  txop->NotifySleep(m_linkId);
1027  }
1028 }
1029 
1030 void
1032 {
1033  NS_LOG_FUNCTION(this);
1034  m_off = true;
1035  // Cancel timeout
1036  if (m_accessTimeout.IsRunning())
1037  {
1039  }
1040 
1041  // Reset backoffs
1042  for (auto txop : m_txops)
1043  {
1044  txop->NotifyOff();
1045  }
1046 }
1047 
1048 void
1050 {
1051  NS_LOG_FUNCTION(this);
1052  m_sleeping = false;
1053  for (auto txop : m_txops)
1054  {
1055  ResetBackoff(txop);
1056  txop->NotifyWakeUp(m_linkId);
1057  }
1058 }
1059 
1060 void
1062 {
1063  NS_LOG_FUNCTION(this);
1064  m_off = false;
1065  for (auto txop : m_txops)
1066  {
1067  ResetBackoff(txop);
1068  txop->NotifyOn();
1069  }
1070 }
1071 
1072 void
1074 {
1075  NS_LOG_FUNCTION(this << duration);
1076 
1077  if (!m_phy)
1078  {
1079  NS_LOG_DEBUG("Do not reset NAV, CTS may have been missed due to the main PHY switching "
1080  "to another link to take over a TXOP while receiving the CTS");
1081  return;
1082  }
1083 
1084  NS_LOG_DEBUG("nav reset for=" << duration);
1085  UpdateBackoff();
1086  m_lastNavEnd = Simulator::Now() + duration;
1094 }
1095 
1096 void
1098 {
1099  NS_LOG_FUNCTION(this << duration);
1100  NS_LOG_DEBUG("nav start for=" << duration);
1101  UpdateBackoff();
1103 }
1104 
1105 void
1107 {
1108  NS_LOG_FUNCTION(this << duration);
1110  m_lastAckTimeoutEnd = Simulator::Now() + duration;
1111 }
1112 
1113 void
1115 {
1116  NS_LOG_FUNCTION(this);
1119 }
1120 
1121 void
1123 {
1124  NS_LOG_FUNCTION(this << duration);
1125  m_lastCtsTimeoutEnd = Simulator::Now() + duration;
1126 }
1127 
1128 void
1130 {
1131  NS_LOG_FUNCTION(this);
1134 }
1135 
1136 void
1138 {
1139  NS_LOG_FUNCTION(this);
1140  m_usingOtherEmlsrLink = true;
1141 }
1142 
1143 void
1145 {
1146  NS_LOG_FUNCTION(this);
1147  m_usingOtherEmlsrLink = false;
1148 }
1149 
1150 void
1152 {
1153  NS_LOG_FUNCTION(this);
1155  Time now = Simulator::Now();
1156 
1157  if (idleStart >= now)
1158  {
1159  // No new idle period
1160  return;
1161  }
1162 
1163  for (const auto& busyEnd : m_lastBusyEnd)
1164  {
1165  if (busyEnd.second < now)
1166  {
1167  auto lastIdleIt = m_lastIdle.find(busyEnd.first);
1168  NS_ASSERT(lastIdleIt != m_lastIdle.end());
1169  lastIdleIt->second = {std::max(idleStart, busyEnd.second), now};
1170  NS_LOG_DEBUG("New idle period (" << lastIdleIt->second.start.As(Time::S) << ", "
1171  << lastIdleIt->second.end.As(Time::S)
1172  << ") on channel " << lastIdleIt->first);
1173  }
1174  }
1175 }
1176 
1177 } // namespace ns3
#define min(a, b)
Definition: 80211b.c:41
#define max(a, b)
Definition: 80211b.c:42
Manage a set of ns3::Txop.
uint16_t GetLargestIdlePrimaryChannel(Time interval, Time end)
Return the width of the largest primary channel that has been idle for the given time interval before...
std::vector< Time > m_lastPer20MHzBusyEnd
the last busy end time per 20 MHz channel (HE stations and channel width > 20 MHz only)
bool IsBusy() const
Check if the device is busy sending or receiving, or NAV or CCA busy.
void ResetBackoff(Ptr< Txop > txop)
Reset the backoff for the given DCF/EDCAF.
void NotifyRxEndErrorNow()
Notify the Txop that a packet reception was just completed unsuccessfuly.
bool m_off
flag whether it is in off state
void NotifyRxStartNow(Time duration)
void NotifySwitchingStartNow(PhyListener *phyListener, Time duration)
Time GetBackoffEndFor(Ptr< Txop > txop)
Return the time when the backoff procedure ended (or will ended) for the given Txop.
void ResetState()
Reset the state variables of this channel access manager.
void NotifySwitchingEmlsrLink(Ptr< WifiPhy > phy, const WifiPhyOperatingChannel &channel, uint8_t linkId)
Notify that the given PHY is about to switch to the given operating channel, which is used by the giv...
void NotifyStopUsingOtherEmlsrLink()
Notify that another EMLSR link is no longer being used, hence medium access can be resumed.
void ResetAllBackoffs()
Reset the backoff for all the DCF/EDCAF.
void NotifyWakeupNow()
Notify the Txop that the device has been resumed from sleep mode.
bool m_lastRxReceivedOk
the last receive OK
std::unordered_map< Ptr< WifiPhy >, EmlsrLinkSwitchInfo > m_switchingEmlsrLinks
Store information about the PHY objects that are going to operate on another EMLSR link.
std::map< WifiChannelListType, Timespan > m_lastIdle
the last idle start and end time for each channel type
Ptr< WifiPhy > m_phy
pointer to the unique active PHY
void NotifyAckTimeoutResetNow()
Notify that ack timer has reset.
void SetGenerateBackoffOnNoTx(bool enable)
Set the member variable indicating whether the backoff should be invoked when an AC gains the right t...
void NotifyTxStartNow(Time duration)
void NotifyRxEndOkNow()
Notify the Txop that a packet reception was just completed successfully.
virtual Time GetEifsNoDifs() const
Return the EIFS duration minus a DIFS.
uint8_t m_linkId
the ID of the link this object is associated with
void NotifyCcaBusyStartNow(Time duration, WifiChannelListType channelType, const std::vector< Time > &per20MhzDurations)
Time m_lastAckTimeoutEnd
the last Ack timeout end time
virtual Time GetSlot() const
Return the slot duration for this PHY.
void NotifyAckTimeoutStartNow(Time duration)
Notify that ack timer has started for the given duration.
void AccessTimeout()
Called when access timeout should occur (e.g.
void UpdateBackoff()
Update backoff slots for all Txops.
void DeactivatePhyListener(Ptr< WifiPhy > phy)
Deactivate current registered listener for PHY events on the given PHY.
bool m_sleeping
flag whether it is in sleeping state
void SetLinkId(uint8_t linkId)
Set the ID of the link this Channel Access Manager is associated with.
void SetupFrameExchangeManager(Ptr< FrameExchangeManager > feManager)
Set up the Frame Exchange Manager.
bool NeedBackoffUponAccess(Ptr< Txop > txop, bool hadFramesToTransmit, bool checkMediumBusy)
Determine if a new backoff needs to be generated as per letter a) of Section 10.23....
void NotifyCtsTimeoutStartNow(Time duration)
Notify that CTS timer has started for the given duration.
void RequestAccess(Ptr< Txop > txop)
Time m_lastSwitchingEnd
the last switching end time
Timespan m_lastRx
the last receive start and end time
std::map< WifiChannelListType, Time > m_lastBusyEnd
the last busy end time for each channel type
void RemovePhyListener(Ptr< WifiPhy > phy)
Remove current registered listener for PHY events on the given PHY.
bool m_generateBackoffOnNoTx
whether the backoff should be invoked when the AC gains the right to start a TXOP but it does not tra...
Time m_lastTxEnd
the last transmit end time
void SetupPhyListener(Ptr< WifiPhy > phy)
Set up (or reactivate) listener for PHY events on the given PHY.
void NotifyStartUsingOtherEmlsrLink()
Notify that another EMLSR link is being used, hence medium access should be disabled.
Time m_lastCtsTimeoutEnd
the last CTS timeout end time
void DoDispose() override
Destructor implementation.
void NotifySleepNow()
Notify the Txop that the device has been put in sleep mode.
Ptr< FrameExchangeManager > m_feManager
pointer to the Frame Exchange Manager
bool m_usingOtherEmlsrLink
whether another EMLSR link is being used
void UpdateLastIdlePeriod()
This method determines whether the medium has been idle during a period (of non-null duration) immedi...
void DisableEdcaFor(Ptr< Txop > qosTxop, Time duration)
void DoInitialize() override
Initialize() implementation.
Txops m_txops
the vector of managed Txops
bool GetPer20MHzBusy(const std::set< uint8_t > &indices) const
static TypeId GetTypeId()
Get the type ID.
void DoGrantDcfAccess()
Grant access to Txop using DCF/EDCF contention rules.
std::shared_ptr< PhyListener > GetPhyListener(Ptr< WifiPhy > phy) const
Get current registered listener for PHY events on the given PHY.
Time m_lastNavEnd
the last NAV end time
void NotifyCtsTimeoutResetNow()
Notify that CTS timer has reset.
void NotifyOffNow()
Notify the Txop that the device has been put in off mode.
Time GetAccessGrantStart(bool ignoreNav=false) const
Access will never be granted to the medium before the time returned by this method.
void NotifyOnNow()
Notify the Txop that the device has been resumed from off mode.
Time GetBackoffStartFor(Ptr< Txop > txop)
Return the time when the backoff procedure started for the given Txop.
PhyListenerMap m_phyListeners
the PHY listeners
virtual Time GetSifs() const
Return the Short Interframe Space (SIFS) for this PHY.
EventId m_accessTimeout
the access timeout ID
void InitLastBusyStructs()
Initialize the structures holding busy end times per channel type (primary, secondary,...
void Cancel()
This method is syntactic sugar for the ns3::Simulator::Cancel method.
Definition: event-id.cc:55
bool IsExpired() const
This method is syntactic sugar for the ns3::Simulator::IsExpired method.
Definition: event-id.cc:69
bool IsRunning() const
This method is syntactic sugar for !IsExpired().
Definition: event-id.cc:76
A base class which provides memory management and object aggregation.
Definition: object.h:89
Listener for PHY events.
bool m_active
whether this PHY listener is active
PhyListener(ns3::ChannelAccessManager *cam)
Create a PhyListener for the given ChannelAccessManager.
void NotifyOff() override
Notify listeners that we went to switch off.
void NotifySleep() override
Notify listeners that we went to sleep.
ns3::ChannelAccessManager * m_cam
ChannelAccessManager to forward events to.
void NotifyTxStart(Time duration, double txPowerDbm) override
void NotifyRxStart(Time duration) override
void NotifyRxEndError() override
We have received the last bit of a packet for which NotifyRxStart was invoked first and,...
void NotifyOn() override
Notify listeners that we went to switch on.
void NotifySwitchingStart(Time duration) override
void SetActive(bool active)
Set this listener to be active or not.
void NotifyRxEndOk() override
We have received the last bit of a packet for which NotifyRxStart was invoked first and,...
void NotifyWakeup() override
Notify listeners that we woke up.
void NotifyCcaBusyStart(Time duration, WifiChannelListType channelType, const std::vector< Time > &per20MhzDurations) override
static EventId Schedule(const Time &delay, FUNC f, Ts &&... args)
Schedule an event to expire after delay.
Definition: simulator.h:571
static Time Now()
Return the current simulation virtual time.
Definition: simulator.cc:208
static Time GetMaximumSimulationTime()
Get the maximum representable simulation time.
Definition: simulator.cc:311
static Time GetDelayLeft(const EventId &id)
Get the remaining time until this event will execute.
Definition: simulator.cc:217
Simulation virtual time values and global simulation resolution.
Definition: nstime.h:105
TimeWithUnit As(const Unit unit=Time::AUTO) const
Attach a unit to a Time, to facilitate output in a specific unit.
Definition: time.cc:415
@ US
microsecond
Definition: nstime.h:118
@ S
second
Definition: nstime.h:116
virtual ChannelAccessStatus GetAccessStatus(uint8_t linkId) const
Definition: txop.cc:604
virtual bool HasFramesToTransmit(uint8_t linkId)
Check if the Txop has frames to transmit over the given link.
Definition: txop.cc:514
@ GRANTED
Definition: txop.h:105
@ NOT_REQUESTED
Definition: txop.h:103
@ REQUESTED
Definition: txop.h:104
Ptr< WifiMacQueue > GetWifiMacQueue() const
Return the packet queue associated with this Txop.
Definition: txop.cc:231
void ResetCw(uint8_t linkId)
Update the value of the CW variable for the given link to take into account a transmission success or...
Definition: txop.cc:303
LinkEntity & GetLink(uint8_t linkId) const
Get a reference to the link associated with the given ID.
Definition: txop.cc:170
virtual bool IsQosTxop() const
Check for QoS TXOP.
Definition: txop.cc:687
void UpdateBackoffSlotsNow(uint32_t nSlots, Time backoffUpdateBound, uint8_t linkId)
Update backoff slots for the given link that nSlots has passed.
Definition: txop.cc:336
Time GetBackoffStart(uint8_t linkId) const
Return the time when the backoff procedure started on the given link.
Definition: txop.cc:330
virtual void NotifyAccessRequested(uint8_t linkId)
Notify that access request has been received for the given link.
Definition: txop.cc:610
uint8_t GetAifsn() const
Return the number of slots that make up an AIFS.
Definition: txop.cc:466
uint32_t GetBackoffSlots(uint8_t linkId) const
Return the current number of backoff slots on the given link.
Definition: txop.cc:324
a unique identifier for an interface.
Definition: type-id.h:59
TypeId SetParent(TypeId tid)
Set the parent TypeId.
Definition: type-id.cc:931
Time GetSlot() const
Return the slot duration for this PHY.
Definition: wifi-phy.cc:793
uint16_t GetChannelWidth() const
Definition: wifi-phy.cc:1051
Time GetSifs() const
Return the Short Interframe Space (SIFS) for this PHY.
Definition: wifi-phy.cc:781
WifiPhyBand GetPhyBand() const
Get the configured Wi-Fi band.
Definition: wifi-phy.cc:1021
Time GetPifs() const
Return the PCF Interframe Space (PIFS) for this PHY.
Definition: wifi-phy.cc:805
WifiStandard GetStandard() const
Get the configured Wi-Fi standard.
Definition: wifi-phy.cc:1027
void NotifyChannelAccessRequested()
Notify the PHY that an access to the channel was requested.
Definition: wifi-phy.cc:1907
Time GetAckTxTime() const
Return the estimated Ack TX time for this PHY.
Definition: wifi-phy.cc:811
const WifiPhyOperatingChannel & GetOperatingChannel() const
Get a const reference to the operating channel.
Definition: wifi-phy.cc:1033
receive notifications about PHY events.
Class that keeps track of all information about the current PHY operating channel.
bool IsOfdm() const
Return whether the operating channel is an OFDM channel.
#define NS_ASSERT(condition)
At runtime, in debugging builds, if this condition is not true, the program prints the source file,...
Definition: assert.h:66
#define NS_ASSERT_MSG(condition, message)
At runtime, in debugging builds, if this condition is not true, the program prints the message to out...
Definition: assert.h:86
#define NS_LOG_COMPONENT_DEFINE(name)
Define a Log component with a specific name.
Definition: log.h:202
#define NS_LOG_DEBUG(msg)
Use NS_LOG to output a message of level LOG_DEBUG.
Definition: log.h:268
#define NS_LOG_FUNCTION(parameters)
If log level LOG_FUNCTION is enabled, this macro will output all input parameters separated by ",...
#define NS_LOG_INFO(msg)
Use NS_LOG to output a message of level LOG_INFO.
Definition: log.h:275
#define NS_OBJECT_ENSURE_REGISTERED(type)
Register an Object subclass with the TypeId system.
Definition: object-base.h:46
Time MicroSeconds(uint64_t value)
Construct a Time in the indicated unit.
Definition: nstime.h:1350
Time Seconds(double value)
Construct a Time in the indicated unit.
Definition: nstime.h:1326
WifiChannelListType
Enumeration of the possible channel-list parameter elements defined in Table 8-5 of IEEE 802....
@ WIFI_STANDARD_80211ax
@ WIFI_PHY_BAND_2_4GHZ
The 2.4 GHz band.
Definition: wifi-phy-band.h:35
@ WIFI_CHANLIST_PRIMARY
@ WIFI_CHANLIST_SECONDARY40
@ WIFI_CHANLIST_SECONDARY
@ WIFI_CHANLIST_SECONDARY80
Every class exported by the ns3 library is enclosed in the ns3 namespace.
Ptr< const AttributeChecker > MakeBooleanChecker()
Definition: boolean.cc:124
Ptr< const AttributeAccessor > MakeBooleanAccessor(T1 a1)
Definition: boolean.h:86
channel
Definition: third.py:88
phy
Definition: third.py:89