A Discrete-Event Network Simulator
API
Loading...
Searching...
No Matches
channel-access-manager.cc
Go to the documentation of this file.
1/*
2 * Copyright (c) 2005,2006 INRIA
3 *
4 * SPDX-License-Identifier: GPL-2.0-only
5 *
6 * Author: Mathieu Lacage <mathieu.lacage@sophia.inria.fr>
7 */
8
10
11#include "txop.h"
12#include "wifi-mac-queue.h"
13#include "wifi-phy-listener.h"
14#include "wifi-phy.h"
15
16#include "ns3/eht-frame-exchange-manager.h"
17#include "ns3/log.h"
18#include "ns3/simulator.h"
19
20#include <sstream>
21
22#undef NS_LOG_APPEND_CONTEXT
23#define NS_LOG_APPEND_CONTEXT std::clog << "[link=" << +m_linkId << "] "
24
25namespace ns3
26{
27
28NS_LOG_COMPONENT_DEFINE("ChannelAccessManager");
29
30NS_OBJECT_ENSURE_REGISTERED(ChannelAccessManager);
31
32/**
33 * Listener for PHY events. Forwards to ChannelAccessManager.
34 * The ChannelAccessManager may handle multiple PHY listeners connected to distinct PHYs,
35 * but only one listener at a time can be active. Notifications from inactive listeners are
36 * ignored by the ChannelAccessManager, except for the channel switch notification.
37 * Inactive PHY listeners are typically configured by 11be EMLSR clients.
38 */
40{
41 public:
42 /**
43 * Create a PhyListener for the given ChannelAccessManager.
44 *
45 * \param cam the ChannelAccessManager
46 */
48 : m_cam(cam),
49 m_active(true)
50 {
51 }
52
53 ~PhyListener() override
54 {
55 }
56
57 /**
58 * Set this listener to be active or not.
59 *
60 * \param active whether this listener is active or not
61 */
62 void SetActive(bool active)
63 {
64 m_active = active;
65 }
66
67 /**
68 * \return whether this listener is active or not
69 */
70 bool IsActive() const
71 {
72 return m_active;
73 }
74
75 void NotifyRxStart(Time duration) override
76 {
77 if (m_active)
78 {
79 m_cam->NotifyRxStartNow(duration);
80 }
81 }
82
83 void NotifyRxEndOk() override
84 {
85 if (m_active)
86 {
88 }
89 }
90
91 void NotifyRxEndError() override
92 {
93 if (m_active)
94 {
96 }
97 }
98
99 void NotifyTxStart(Time duration, dBm_u txPower) override
100 {
101 if (m_active)
102 {
103 m_cam->NotifyTxStartNow(duration);
104 }
105 }
106
108 WifiChannelListType channelType,
109 const std::vector<Time>& per20MhzDurations) override
110 {
111 if (m_active)
112 {
113 m_cam->NotifyCcaBusyStartNow(duration, channelType, per20MhzDurations);
114 }
115 }
116
117 void NotifySwitchingStart(Time duration) override
118 {
119 m_cam->NotifySwitchingStartNow(this, duration);
120 }
121
122 void NotifySleep() override
123 {
124 if (m_active)
125 {
127 }
128 }
129
130 void NotifyOff() override
131 {
132 if (m_active)
133 {
135 }
136 }
137
138 void NotifyWakeup() override
139 {
140 if (m_active)
141 {
143 }
144 }
145
146 void NotifyOn() override
147 {
148 if (m_active)
149 {
151 }
152 }
153
154 private:
155 ns3::ChannelAccessManager* m_cam; //!< ChannelAccessManager to forward events to
156 bool m_active; //!< whether this PHY listener is active
157};
158
159/****************************************************************
160 * Implement the channel access manager of all Txop holders
161 ****************************************************************/
162
163TypeId
165{
166 static TypeId tid =
167 TypeId("ns3::ChannelAccessManager")
169 .SetGroupName("Wifi")
170 .AddConstructor<ChannelAccessManager>()
171 .AddAttribute("GenerateBackoffIfTxopWithoutTx",
172 "Specify whether the backoff should be invoked when the AC gains the "
173 "right to start a TXOP but it does not transmit any frame "
174 "(e.g., due to constraints associated with EMLSR operations), "
175 "provided that the queue is not actually empty.",
176 BooleanValue(false),
180 .AddAttribute("ProactiveBackoff",
181 "Specify whether a new backoff value is generated when a CCA busy "
182 "period starts, the backoff counter is zero and the station is not a "
183 "TXOP holder. This is useful to generate a new backoff value when, "
184 "e.g., the backoff counter reaches zero, the station does not transmit "
185 "and subsequently the medium becomes busy.",
186 BooleanValue(false),
189 return tid;
190}
191
193 : m_lastAckTimeoutEnd(0),
194 m_lastCtsTimeoutEnd(0),
195 m_lastNavEnd(0),
196 m_lastRx({MicroSeconds(0), MicroSeconds(0)}),
197 m_lastRxReceivedOk(true),
198 m_lastTxEnd(0),
199 m_lastSwitchingEnd(0),
200 m_sleeping(false),
201 m_off(false),
202 m_linkId(0)
203{
204 NS_LOG_FUNCTION(this);
205 InitLastBusyStructs();
206}
207
212
213void
219
220void
222{
223 NS_LOG_FUNCTION(this);
224 for (Ptr<Txop> i : m_txops)
225 {
226 i->Dispose();
227 i = nullptr;
228 }
229 m_phy = nullptr;
230 m_feManager = nullptr;
231 m_phyListeners.clear();
232}
233
234std::shared_ptr<PhyListener>
236{
237 if (auto listenerIt = m_phyListeners.find(phy); listenerIt != m_phyListeners.end())
238 {
239 return listenerIt->second;
240 }
241 return nullptr;
242}
243
244void
246{
247 NS_LOG_FUNCTION(this << phy);
248
249 auto phyListener = GetPhyListener(phy);
250
251 if (phyListener)
252 {
253 // a PHY listener for the given PHY already exists, it must be inactive
254 NS_ASSERT_MSG(!phyListener->IsActive(),
255 "There is already an active listener registered for given PHY");
256 NS_ASSERT_MSG(!m_phy, "Cannot reactivate a listener if another PHY is active");
257 phyListener->SetActive(true);
258 // if a PHY listener already exists, the PHY was disconnected and now reconnected to the
259 // channel access manager; unregister the listener and register again (below) to get
260 // updated CCA busy information
261 phy->UnregisterListener(phyListener);
262 }
263 else
264 {
265 phyListener = std::make_shared<PhyListener>(this);
266 m_phyListeners.emplace(phy, phyListener);
267 }
268 if (m_phy)
269 {
271 }
272 m_phy = phy; // this is the new active PHY
274 phy->RegisterListener(phyListener);
275 if (phy->IsStateSwitching())
276 {
277 auto duration = phy->GetDelayUntilIdle();
278 NS_LOG_DEBUG("switching start for " << duration);
279 m_lastSwitchingEnd = Simulator::Now() + duration;
280 }
281}
282
283void
285{
286 NS_LOG_FUNCTION(this << phy);
287 if (auto phyListener = GetPhyListener(phy))
288 {
289 phy->UnregisterListener(phyListener);
290 m_phyListeners.erase(phy);
291 // reset m_phy if we are removing listener registered for the active PHY
292 if (m_phy == phy)
293 {
294 m_phy = nullptr;
295 }
296 }
297}
298
299void
301{
302 NS_LOG_FUNCTION(this << phy);
303 if (auto listener = GetPhyListener(phy))
304 {
305 listener->SetActive(false);
306 }
307 if (m_phy == phy)
308 {
309 m_phy = nullptr;
310 }
311}
312
313void
315 const WifiPhyOperatingChannel& channel,
316 uint8_t linkId)
317{
318 NS_LOG_FUNCTION(this << phy << channel << linkId);
320 "The given PHY is already expected to switch channel");
321 m_switchingEmlsrLinks.emplace(phy, EmlsrLinkSwitchInfo{channel, linkId});
322}
323
324void
326{
327 NS_LOG_FUNCTION(this << +linkId);
328 m_linkId = linkId;
329}
330
331void
333{
334 NS_LOG_FUNCTION(this << feManager);
335 m_feManager = feManager;
336 m_feManager->SetChannelAccessManager(this);
337}
338
339Time
341{
342 return m_phy->GetSlot();
343}
344
345Time
347{
348 return m_phy->GetSifs();
349}
350
351Time
356
357void
359{
360 NS_LOG_FUNCTION(this << txop);
361 m_txops.push_back(txop);
362}
363
364void
366{
367 NS_LOG_FUNCTION(this);
368 const auto now = Simulator::Now();
369
371 m_lastIdle.emplace(WIFI_CHANLIST_PRIMARY, Timespan{now, now});
372
373 const auto width = m_phy ? m_phy->GetChannelWidth() : 0;
374 std::size_t size =
375 (width > 20 && m_phy->GetStandard() >= WIFI_STANDARD_80211ax) ? width / 20 : 0;
376 m_lastPer20MHzBusyEnd.resize(size, now);
377
379 {
380 return;
381 }
382
383 if (width >= 40)
384 {
386 m_lastIdle.emplace(WIFI_CHANLIST_SECONDARY, Timespan{now, now});
387 }
388 else
389 {
392 }
393
394 if (width >= 80)
395 {
398 }
399 else
400 {
403 }
404
405 if (width >= 160)
406 {
409 }
410 else
411 {
414 }
415
416 // TODO Add conditions for new channel widths as they get supported
417}
418
419void
421{
422 NS_LOG_FUNCTION(this);
423 Time now = Simulator::Now();
424
426
427 // reset all values
428 for (auto& [chType, time] : m_lastBusyEnd)
429 {
430 time = now;
431 }
432
433 for (auto& [chType, timeSpan] : m_lastIdle)
434 {
435 timeSpan = Timespan{now, now};
436 }
437
438 for (auto& time : m_lastPer20MHzBusyEnd)
439 {
440 time = now;
441 }
442}
443
444bool
446{
447 NS_LOG_FUNCTION(this);
448 Time now = Simulator::Now();
449 return (m_lastRx.end > now) // RX
450 || (m_lastTxEnd > now) // TX
451 || (m_lastNavEnd > now) // NAV busy
452 // an EDCA TXOP is obtained based solely on activity of the primary channel
453 // (Sec. 10.23.2.5 of IEEE 802.11-2020)
454 || (m_lastBusyEnd.at(WIFI_CHANLIST_PRIMARY) > now); // CCA busy
455}
456
457bool
459 bool hadFramesToTransmit,
460 bool checkMediumBusy)
461{
462 NS_LOG_FUNCTION(this << txop << hadFramesToTransmit << checkMediumBusy);
463
464 // No backoff needed if in sleep mode or off. Checking if m_phy is nullptr is a workaround
465 // needed for EMLSR and may be removed in the future
466 if (m_sleeping || m_off || !m_phy)
467 {
468 return false;
469 }
470
471 // the Txop might have a stale value of remaining backoff slots
473
474 /*
475 * From section 10.3.4.2 "Basic access" of IEEE 802.11-2016:
476 *
477 * A STA may transmit an MPDU when it is operating under the DCF access
478 * method, either in the absence of a PC, or in the CP of the PCF access
479 * method, when the STA determines that the medium is idle when a frame is
480 * queued for transmission, and remains idle for a period of a DIFS, or an
481 * EIFS (10.3.2.3.7) from the end of the immediately preceding medium-busy
482 * event, whichever is the greater, and the backoff timer is zero. Otherwise
483 * the random backoff procedure described in 10.3.4.3 shall be followed.
484 *
485 * From section 10.22.2.2 "EDCA backoff procedure" of IEEE 802.11-2016:
486 *
487 * The backoff procedure shall be invoked by an EDCAF when any of the following
488 * events occurs:
489 * a) An MA-UNITDATA.request primitive is received that causes a frame with that AC
490 * to be queued for transmission such that one of the transmit queues associated
491 * with that AC has now become non-empty and any other transmit queues
492 * associated with that AC are empty; the medium is busy on the primary channel
493 */
494 if (!hadFramesToTransmit && txop->HasFramesToTransmit(m_linkId) &&
495 txop->GetAccessStatus(m_linkId) != Txop::GRANTED && txop->GetBackoffSlots(m_linkId) == 0)
496 {
497 if (checkMediumBusy && !IsBusy())
498 {
499 // medium idle. If this is a DCF, use immediate access (we can transmit
500 // in a DIFS if the medium remains idle). If this is an EDCAF, update
501 // the backoff start time kept by the EDCAF to the current time in order
502 // to correctly align the backoff start time at the next slot boundary
503 // (performed by the next call to ChannelAccessManager::RequestAccess())
504 Time delay =
505 (txop->IsQosTxop() ? Seconds(0) : GetSifs() + txop->GetAifsn(m_linkId) * GetSlot());
506 txop->UpdateBackoffSlotsNow(0, Simulator::Now() + delay, m_linkId);
507 }
508 else
509 {
510 // medium busy, backoff is needed
511 return true;
512 }
513 }
514 return false;
515}
516
517void
519{
520 NS_LOG_FUNCTION(this << txop);
521 if (m_phy && txop->HasFramesToTransmit(m_linkId))
522 {
524 }
525 // Deny access if in sleep mode or off. Checking if m_phy is nullptr is a workaround
526 // needed for EMLSR and may be removed in the future
527 if (m_sleeping || m_off || !m_phy)
528 {
529 return;
530 }
531 /*
532 * EDCAF operations shall be performed at slot boundaries (Sec. 10.22.2.4 of 802.11-2016)
533 */
534 Time accessGrantStart = GetAccessGrantStart() + (txop->GetAifsn(m_linkId) * GetSlot());
535
536 if (txop->IsQosTxop() && txop->GetBackoffStart(m_linkId) > accessGrantStart)
537 {
538 // The backoff start time reported by the EDCAF is more recent than the last
539 // time the medium was busy plus an AIFS, hence we need to align it to the
540 // next slot boundary.
541 Time diff = txop->GetBackoffStart(m_linkId) - accessGrantStart;
542 uint32_t nIntSlots = (diff / GetSlot()).GetHigh() + 1;
543 txop->UpdateBackoffSlotsNow(0, accessGrantStart + (nIntSlots * GetSlot()), m_linkId);
544 }
545
547 NS_ASSERT(txop->GetAccessStatus(m_linkId) != Txop::REQUESTED);
548 txop->NotifyAccessRequested(m_linkId);
551}
552
553void
555{
556 NS_LOG_FUNCTION(this);
557 uint32_t k = 0;
558 const auto now = Simulator::Now();
559 const auto accessGrantStart = GetAccessGrantStart();
560 for (auto i = m_txops.begin(); i != m_txops.end(); k++)
561 {
562 Ptr<Txop> txop = *i;
563 if (txop->GetAccessStatus(m_linkId) == Txop::REQUESTED &&
564 (!txop->IsQosTxop() || !StaticCast<QosTxop>(txop)->EdcaDisabled(m_linkId)) &&
565 GetBackoffEndFor(txop, accessGrantStart) <= now)
566 {
567 /**
568 * This is the first Txop we find with an expired backoff and which
569 * needs access to the medium. i.e., it has data to send.
570 */
571 NS_LOG_DEBUG("dcf " << k << " needs access. backoff expired. access granted. slots="
572 << txop->GetBackoffSlots(m_linkId));
573 i++; // go to the next item in the list.
574 k++;
575 std::vector<Ptr<Txop>> internalCollisionTxops;
576 for (auto j = i; j != m_txops.end(); j++, k++)
577 {
578 Ptr<Txop> otherTxop = *j;
579 if (otherTxop->GetAccessStatus(m_linkId) == Txop::REQUESTED &&
580 GetBackoffEndFor(otherTxop, accessGrantStart) <= now)
581 {
583 "dcf " << k << " needs access. backoff expired. internal collision. slots="
584 << otherTxop->GetBackoffSlots(m_linkId));
585 /**
586 * all other Txops with a lower priority whose backoff
587 * has expired and which needed access to the medium
588 * must be notified that we did get an internal collision.
589 */
590 internalCollisionTxops.push_back(otherTxop);
591 }
592 }
593
594 /**
595 * Now, we notify all of these changes in one go if the EDCAF winning
596 * the contention actually transmitted a frame. It is necessary to
597 * perform first the calculations of which Txops are colliding and then
598 * only apply the changes because applying the changes through notification
599 * could change the global state of the manager, and, thus, could change
600 * the result of the calculations.
601 */
603 // If we are operating on an OFDM channel wider than 20 MHz, find the largest
604 // idle primary channel and pass its width to the FrameExchangeManager, so that
605 // the latter can transmit PPDUs of the appropriate width (see Section 10.23.2.5
606 // of IEEE 802.11-2020).
607 auto interval = (m_phy->GetPhyBand() == WIFI_PHY_BAND_2_4GHZ)
608 ? GetSifs() + 2 * GetSlot()
609 : m_phy->GetPifs();
610 auto width = (m_phy->GetOperatingChannel().IsOfdm() && m_phy->GetChannelWidth() > 20)
611 ? GetLargestIdlePrimaryChannel(interval, now)
613 if (m_feManager->StartTransmission(txop, width))
614 {
615 for (auto& collidingTxop : internalCollisionTxops)
616 {
617 m_feManager->NotifyInternalCollision(collidingTxop);
618 }
619 break;
620 }
621 else
622 {
623 // this TXOP did not transmit anything, make sure that backoff counter starts
624 // decreasing in a slot again
625 txop->UpdateBackoffSlotsNow(0, now, m_linkId);
626 // reset the current state to the EDCAF that won the contention
627 // but did not transmit anything
628 i--;
629 k = std::distance(m_txops.begin(), i);
630 }
631 }
632 i++;
633 }
634}
635
636void
644
645Time
647{
648 NS_LOG_FUNCTION(this << ignoreNav);
649 auto rxAccessStart = m_lastRx.end;
651 {
652 rxAccessStart += GetEifsNoDifs();
653 }
654 // an EDCA TXOP is obtained based solely on activity of the primary channel
655 // (Sec. 10.23.2.5 of IEEE 802.11-2020)
656 const auto busyAccessStart = m_lastBusyEnd.at(WIFI_CHANLIST_PRIMARY);
657 const auto navAccessStart = ignoreNav ? Time{0} : m_lastNavEnd;
658
659 const auto accessGrantedStart = std::max({rxAccessStart,
660 busyAccessStart,
662 navAccessStart,
666
667 NS_LOG_INFO("access grant start="
668 << accessGrantedStart.As(Time::US)
669 << ", rx access start=" << rxAccessStart.As(Time::US) << ", busy access start="
670 << busyAccessStart.As(Time::US) << ", tx access start=" << m_lastTxEnd.As(Time::US)
671 << ", nav access start=" << navAccessStart.As(Time::US)
672 << ", switching access start=" << m_lastSwitchingEnd.As(Time::US));
673 return accessGrantedStart + GetSifs();
674}
675
676Time
681
682Time
684{
685 NS_LOG_FUNCTION(this << txop << accessGrantStart.As(Time::S));
686 const auto mostRecentEvent =
687 std::max({txop->GetBackoffStart(m_linkId),
688 accessGrantStart + (txop->GetAifsn(m_linkId) * GetSlot())});
689 NS_LOG_DEBUG("Backoff start for " << txop->GetWifiMacQueue()->GetAc() << ": "
690 << mostRecentEvent.As(Time::US));
691
692 return mostRecentEvent;
693}
694
695Time
700
701Time
703{
704 NS_LOG_FUNCTION(this << txop);
705 Time backoffEnd =
706 GetBackoffStartFor(txop, accessGrantStart) + (txop->GetBackoffSlots(m_linkId) * GetSlot());
707 NS_LOG_DEBUG("Backoff end for " << txop->GetWifiMacQueue()->GetAc() << ": "
708 << backoffEnd.As(Time::US));
709
710 return backoffEnd;
711}
712
713void
715{
716 NS_LOG_FUNCTION(this);
717 uint32_t k = 0;
718 const auto accessGrantStart = GetAccessGrantStart();
719 for (auto txop : m_txops)
720 {
721 Time backoffStart = GetBackoffStartFor(txop, accessGrantStart);
722 if (backoffStart <= Simulator::Now())
723 {
724 uint32_t nIntSlots = ((Simulator::Now() - backoffStart) / GetSlot()).GetHigh();
725 /*
726 * EDCA behaves slightly different to DCA. For EDCA we
727 * decrement once at the slot boundary at the end of AIFS as
728 * well as once at the end of each clear slot
729 * thereafter. For DCA we only decrement at the end of each
730 * clear slot after DIFS. We account for the extra backoff
731 * by incrementing the slot count here in the case of
732 * EDCA. The if statement whose body we are in has confirmed
733 * that a minimum of AIFS has elapsed since last busy
734 * medium.
735 */
736 if (txop->IsQosTxop())
737 {
738 nIntSlots++;
739 }
740 uint32_t n = std::min(nIntSlots, txop->GetBackoffSlots(m_linkId));
741 NS_LOG_DEBUG("dcf " << k << " dec backoff slots=" << n);
742 Time backoffUpdateBound = backoffStart + (n * GetSlot());
743 txop->UpdateBackoffSlotsNow(n, backoffUpdateBound, m_linkId);
744 }
745 ++k;
746 }
747}
748
749void
751{
752 NS_LOG_FUNCTION(this);
753 /**
754 * Is there a Txop which needs to access the medium, and,
755 * if there is one, how many slots for AIFS+backoff does it require ?
756 */
757 bool accessTimeoutNeeded = false;
758 auto expectedBackoffEnd = Simulator::GetMaximumSimulationTime();
759 const auto accessGrantStart = GetAccessGrantStart();
760 for (auto txop : m_txops)
761 {
762 if (txop->GetAccessStatus(m_linkId) == Txop::REQUESTED)
763 {
764 Time tmp = GetBackoffEndFor(txop, accessGrantStart);
765 if (tmp > Simulator::Now())
766 {
767 accessTimeoutNeeded = true;
768 expectedBackoffEnd = std::min(expectedBackoffEnd, tmp);
769 }
770 }
771 }
772 NS_LOG_DEBUG("Access timeout needed: " << accessTimeoutNeeded);
773 if (accessTimeoutNeeded)
774 {
775 NS_LOG_DEBUG("expected backoff end=" << expectedBackoffEnd);
776 Time expectedBackoffDelay = expectedBackoffEnd - Simulator::Now();
778 Simulator::GetDelayLeft(m_accessTimeout) > expectedBackoffDelay)
779 {
781 }
783 {
784 m_accessTimeout = Simulator::Schedule(expectedBackoffDelay,
786 this);
787 }
788 }
789}
790
791MHz_u
793{
794 NS_LOG_FUNCTION(this << interval.As(Time::US) << end.As(Time::S));
795
796 // If the medium is busy or it just became idle, UpdateLastIdlePeriod does
797 // nothing. This allows us to call this method, e.g., at the end of a frame
798 // reception and check the busy/idle status of the channel before the start
799 // of the frame reception (last idle period was last updated at the start of
800 // the frame reception).
801 // If the medium has been idle for some time, UpdateLastIdlePeriod updates
802 // the last idle period. This is normally what we want because this method may
803 // also be called before starting a TXOP gained through EDCA.
805
806 MHz_u width = 0;
807
808 // we iterate over the different types of channels in the same order as they
809 // are listed in WifiChannelListType
810 for (const auto& lastIdle : m_lastIdle)
811 {
812 if (lastIdle.second.start <= end - interval && lastIdle.second.end >= end)
813 {
814 // channel idle, update width
815 width = (width == 0) ? 20 : (2 * width);
816 }
817 else
818 {
819 break;
820 }
821 }
822 return width;
823}
824
825bool
826ChannelAccessManager::GetPer20MHzBusy(const std::set<uint8_t>& indices) const
827{
828 const auto now = Simulator::Now();
829
830 if (m_phy->GetChannelWidth() < 40)
831 {
832 NS_ASSERT_MSG(indices.size() == 1 && *indices.cbegin() == 0,
833 "Index 0 only can be specified if the channel width is less than 40 MHz");
834 return m_lastBusyEnd.at(WIFI_CHANLIST_PRIMARY) > now;
835 }
836
837 for (const auto index : indices)
838 {
839 NS_ASSERT(index < m_lastPer20MHzBusyEnd.size());
840 if (m_lastPer20MHzBusyEnd.at(index) > now)
841 {
842 NS_LOG_DEBUG("20 MHz channel with index " << +index << " is busy");
843 return true;
844 }
845 }
846 return false;
847}
848
849void
851{
852 NS_LOG_FUNCTION(this << qosTxop << duration);
853 NS_ASSERT(qosTxop->IsQosTxop());
855 Time resume = Simulator::Now() + duration;
856 NS_LOG_DEBUG("Backoff will resume at time " << resume << " with "
857 << qosTxop->GetBackoffSlots(m_linkId)
858 << " remaining slot(s)");
859 qosTxop->UpdateBackoffSlotsNow(0, resume, m_linkId);
861}
862
863void
865{
866 NS_LOG_FUNCTION(this << enable);
868}
869
870bool
875
876void
878{
879 NS_LOG_FUNCTION(this << duration);
880 NS_LOG_DEBUG("rx start for=" << duration);
884 m_lastRx.end = m_lastRx.start + duration;
885 m_lastRxReceivedOk = true;
886}
887
888void
896
897void
899{
900 NS_LOG_FUNCTION(this);
901 NS_LOG_DEBUG("rx end error");
902 // we expect the PHY to notify us of the start of a CCA busy period, if needed
904 m_lastRxReceivedOk = false;
905}
906
907void
909{
910 NS_LOG_FUNCTION(this << duration);
911 m_lastRxReceivedOk = true;
912 Time now = Simulator::Now();
913 if (m_lastRx.end > now)
914 {
915 // this may be caused only if PHY has started to receive a packet
916 // inside SIFS, so, we check that lastRxStart was maximum a SIFS ago
917 NS_ASSERT(now - m_lastRx.start <= GetSifs());
918 m_lastRx.end = now;
919 }
920 else
921 {
923 }
924 NS_LOG_DEBUG("tx start for " << duration);
926 m_lastTxEnd = now + duration;
927}
928
929void
931 WifiChannelListType channelType,
932 const std::vector<Time>& per20MhzDurations)
933{
934 NS_LOG_FUNCTION(this << duration << channelType);
937 auto lastBusyEndIt = m_lastBusyEnd.find(channelType);
938 NS_ASSERT(lastBusyEndIt != m_lastBusyEnd.end());
939 Time now = Simulator::Now();
940 lastBusyEndIt->second = now + duration;
941 NS_ASSERT_MSG(per20MhzDurations.size() == m_lastPer20MHzBusyEnd.size(),
942 "Size of received vector (" << per20MhzDurations.size()
943 << ") differs from the expected size ("
944 << m_lastPer20MHzBusyEnd.size() << ")");
945 for (std::size_t chIdx = 0; chIdx < per20MhzDurations.size(); ++chIdx)
946 {
947 if (per20MhzDurations[chIdx].IsStrictlyPositive())
948 {
949 m_lastPer20MHzBusyEnd[chIdx] = now + per20MhzDurations[chIdx];
950 }
951 }
952
954 {
955 // have all EDCAFs that are not carrying out a TXOP and have the backoff counter set to
956 // zero proactively generate a new backoff value
957 for (auto txop : m_txops)
958 {
959 if (txop->GetAccessStatus(m_linkId) != Txop::GRANTED &&
960 txop->GetBackoffSlots(m_linkId) == 0)
961 {
962 NS_LOG_DEBUG("Generate backoff for " << txop->GetWifiMacQueue()->GetAc());
963 txop->GenerateBackoff(m_linkId);
964 }
965 }
966 }
967}
968
969void
971{
972 NS_LOG_FUNCTION(this << phyListener << duration);
973
974 Time now = Simulator::Now();
975 NS_ASSERT(m_lastTxEnd <= now);
976
977 if (phyListener) // to make tests happy
978 {
979 // check if the PHY switched channel to operate on another EMLSR link
980
981 for (const auto& [phyRef, listener] : m_phyListeners)
982 {
983 Ptr<WifiPhy> phy = phyRef;
984 auto emlsrInfoIt = m_switchingEmlsrLinks.find(phy);
985
986 if (listener.get() == phyListener && emlsrInfoIt != m_switchingEmlsrLinks.cend() &&
987 phy->GetOperatingChannel() == emlsrInfoIt->second.channel)
988 {
989 // the PHY associated with the given PHY listener switched channel to
990 // operate on another EMLSR link as expected. We don't need this listener
991 // anymore. The MAC will connect a new listener to the ChannelAccessManager
992 // instance associated with the link the PHY is now operating on
995 NS_ASSERT(ehtFem);
996 ehtFem->NotifySwitchingEmlsrLink(phy, emlsrInfoIt->second.linkId, duration);
997 m_switchingEmlsrLinks.erase(emlsrInfoIt);
998 return;
999 }
1000 }
1001 }
1002
1003 ResetState();
1004
1005 // Cancel timeout
1007 {
1009 }
1010
1011 // Reset backoffs
1012 for (const auto& txop : m_txops)
1013 {
1014 ResetBackoff(txop);
1015 }
1016
1017 // Notify the FEM, which will in turn notify the MAC
1018 m_feManager->NotifySwitchingStartNow(duration);
1019
1020 NS_LOG_DEBUG("switching start for " << duration);
1021 m_lastSwitchingEnd = now + duration;
1022}
1023
1024void
1026{
1027 NS_LOG_FUNCTION(this);
1028
1029 Time now = Simulator::Now();
1030 m_lastRxReceivedOk = true;
1032 m_lastRx.end = std::min(m_lastRx.end, now);
1033 m_lastNavEnd = std::min(m_lastNavEnd, now);
1036
1038}
1039
1040void
1042{
1043 NS_LOG_FUNCTION(this << txop);
1044
1045 uint32_t remainingSlots = txop->GetBackoffSlots(m_linkId);
1046 if (remainingSlots > 0)
1047 {
1048 txop->UpdateBackoffSlotsNow(remainingSlots, Simulator::Now(), m_linkId);
1049 NS_ASSERT(txop->GetBackoffSlots(m_linkId) == 0);
1050 }
1051 txop->ResetCw(m_linkId);
1052 txop->GetLink(m_linkId).access = Txop::NOT_REQUESTED;
1053}
1054
1055void
1057{
1058 NS_LOG_FUNCTION(this);
1059
1060 for (const auto& txop : m_txops)
1061 {
1062 ResetBackoff(txop);
1063 }
1065}
1066
1067void
1069{
1070 NS_LOG_FUNCTION(this);
1071 m_sleeping = true;
1072 // Reset backoffs
1074 m_feManager->NotifySleepNow();
1075 for (auto txop : m_txops)
1076 {
1077 txop->NotifySleep(m_linkId);
1078 }
1079}
1080
1081void
1083{
1084 NS_LOG_FUNCTION(this);
1085 m_off = true;
1086 // Cancel timeout
1088 {
1090 }
1091
1092 // Reset backoffs
1093 for (auto txop : m_txops)
1094 {
1095 txop->NotifyOff();
1096 }
1097}
1098
1099void
1101{
1102 NS_LOG_FUNCTION(this);
1103 m_sleeping = false;
1104 for (auto txop : m_txops)
1105 {
1106 ResetBackoff(txop);
1107 txop->NotifyWakeUp(m_linkId);
1108 }
1109}
1110
1111void
1113{
1114 NS_LOG_FUNCTION(this);
1115 m_off = false;
1116 for (auto txop : m_txops)
1117 {
1118 ResetBackoff(txop);
1119 txop->NotifyOn();
1120 }
1121}
1122
1123void
1125{
1126 NS_LOG_FUNCTION(this << duration);
1127
1128 if (!m_phy)
1129 {
1130 NS_LOG_DEBUG("Do not reset NAV, CTS may have been missed due to the main PHY switching "
1131 "to another link to take over a TXOP while receiving the CTS");
1132 return;
1133 }
1134
1135 NS_LOG_DEBUG("nav reset for=" << duration);
1136 UpdateBackoff();
1137 m_lastNavEnd = Simulator::Now() + duration;
1138 /**
1139 * If the NAV reset indicates an end-of-NAV which is earlier
1140 * than the previous end-of-NAV, the expected end of backoff
1141 * might be later than previously thought so, we might need
1142 * to restart a new access timeout.
1143 */
1145}
1146
1147void
1149{
1150 NS_LOG_FUNCTION(this << duration);
1151 NS_LOG_DEBUG("nav start for=" << duration);
1152 UpdateBackoff();
1153 m_lastNavEnd = std::max(m_lastNavEnd, Simulator::Now() + duration);
1154}
1155
1156void
1163
1164void
1171
1172void
1174{
1175 NS_LOG_FUNCTION(this << duration);
1176 m_lastCtsTimeoutEnd = Simulator::Now() + duration;
1177}
1178
1179void
1186
1187void
1189{
1190 NS_LOG_FUNCTION(this);
1191 Time idleStart = std::max({m_lastTxEnd, m_lastRx.end, m_lastSwitchingEnd});
1192 Time now = Simulator::Now();
1193
1194 if (idleStart >= now)
1195 {
1196 // No new idle period
1197 return;
1198 }
1199
1200 for (const auto& busyEnd : m_lastBusyEnd)
1201 {
1202 if (busyEnd.second < now)
1203 {
1204 auto lastIdleIt = m_lastIdle.find(busyEnd.first);
1205 NS_ASSERT(lastIdleIt != m_lastIdle.end());
1206 lastIdleIt->second = {std::max(idleStart, busyEnd.second), now};
1207 NS_LOG_DEBUG("New idle period (" << lastIdleIt->second.start.As(Time::S) << ", "
1208 << lastIdleIt->second.end.As(Time::S)
1209 << ") on channel " << lastIdleIt->first);
1210 }
1211 }
1212}
1213
1214} // namespace ns3
Manage a set of ns3::Txop.
bool m_proactiveBackoff
whether a new backoff value is generated when a CCA busy period starts and the backoff counter is zer...
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 NotifySwitchingStartNow(PhyListener *phyListener, Time duration)
Time GetBackoffStartFor(Ptr< Txop > txop) const
Return the time when the backoff procedure started 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 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 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.
Time GetBackoffEndFor(Ptr< Txop > txop) const
Return the time when the backoff procedure ended (or will end) for the given Txop.
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.
Time m_lastCtsTimeoutEnd
the last CTS timeout end time
MHz_u GetLargestIdlePrimaryChannel(Time interval, Time end)
Return the width of the largest primary channel that has been idle for the given time interval before...
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
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.
void ResizeLastBusyStructs()
Resize the structures holding busy end times per channel type (primary, secondary,...
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.
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:44
bool IsPending() const
This method is syntactic sugar for !IsExpired().
Definition event-id.cc:65
bool IsExpired() const
This method is syntactic sugar for the ns3::Simulator::IsExpired method.
Definition event-id.cc:58
A base class which provides memory management and object aggregation.
Definition object.h:78
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 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
void NotifyTxStart(Time duration, dBm_u txPower) override
Smart pointer class similar to boost::intrusive_ptr.
static EventId Schedule(const Time &delay, FUNC f, Ts &&... args)
Schedule an event to expire after delay.
Definition simulator.h:560
static Time Now()
Return the current simulation virtual time.
Definition simulator.cc:197
static Time GetMaximumSimulationTime()
Get the maximum representable simulation time.
Definition simulator.cc:300
static Time GetDelayLeft(const EventId &id)
Get the remaining time until this event will execute.
Definition simulator.cc:206
Simulation virtual time values and global simulation resolution.
Definition nstime.h:94
TimeWithUnit As(const Unit unit=Time::AUTO) const
Attach a unit to a Time, to facilitate output in a specific unit.
Definition time.cc:404
@ US
microsecond
Definition nstime.h:107
@ S
second
Definition nstime.h:105
@ GRANTED
Definition txop.h:79
@ NOT_REQUESTED
Definition txop.h:77
@ REQUESTED
Definition txop.h:78
a unique identifier for an interface.
Definition type-id.h:48
TypeId SetParent(TypeId tid)
Set the parent TypeId.
Definition type-id.cc:1001
Time GetSlot() const
Return the slot duration for this PHY.
Definition wifi-phy.cc:835
Time GetSifs() const
Return the Short Interframe Space (SIFS) for this PHY.
Definition wifi-phy.cc:823
WifiPhyBand GetPhyBand() const
Get the configured Wi-Fi band.
Definition wifi-phy.cc:1063
Time GetPifs() const
Return the PCF Interframe Space (PIFS) for this PHY.
Definition wifi-phy.cc:847
MHz_u GetChannelWidth() const
Definition wifi-phy.cc:1093
WifiStandard GetStandard() const
Get the configured Wi-Fi standard.
Definition wifi-phy.cc:1069
void NotifyChannelAccessRequested()
Notify the PHY that an access to the channel was requested.
Definition wifi-phy.cc:2002
Time GetAckTxTime() const
Return the estimated Ack TX time for this PHY.
Definition wifi-phy.cc:853
const WifiPhyOperatingChannel & GetOperatingChannel() const
Get a const reference to the operating channel.
Definition wifi-phy.cc:1075
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:55
#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:75
#define NS_LOG_COMPONENT_DEFINE(name)
Define a Log component with a specific name.
Definition log.h:191
#define NS_LOG_DEBUG(msg)
Use NS_LOG to output a message of level LOG_DEBUG.
Definition log.h:257
#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:264
#define NS_OBJECT_ENSURE_REGISTERED(type)
Register an Object subclass with the TypeId system.
Definition object-base.h:35
Time MicroSeconds(uint64_t value)
Construct a Time in the indicated unit.
Definition nstime.h:1332
Time Seconds(double value)
Construct a Time in the indicated unit.
Definition nstime.h:1308
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.
@ 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:113
double MHz_u
MHz weak type.
Definition wifi-units.h:31
Ptr< T1 > DynamicCast(const Ptr< T2 > &p)
Cast a Ptr.
Definition ptr.h:580
Ptr< T1 > StaticCast(const Ptr< T2 > &p)
Cast a Ptr.
Definition ptr.h:587
Ptr< const AttributeAccessor > MakeBooleanAccessor(T1 a1)
Definition boolean.h:70
Structure defining start time and end time for a given state.