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 .AddAttribute("NSlotsLeft",
190 "Fire the NSlotsLeftAlert trace source when the backoff counter with "
191 "the minimum value among all ACs reaches this value or it is started "
192 "with a value less than this attribute. If this value is zero, the "
193 "trace source is never fired.",
194 UintegerValue(0),
197 .AddTraceSource("NSlotsLeftAlert",
198 "The backoff counter of the AC with the given index reached the "
199 "threshold set through the NSlotsLeft attribute.",
201 "ns3::ChannelAccessManager::NSlotsLeftCallback");
202 return tid;
203}
204
206 : m_lastAckTimeoutEnd(0),
207 m_lastCtsTimeoutEnd(0),
208 m_lastNavEnd(0),
209 m_lastRx({MicroSeconds(0), MicroSeconds(0)}),
210 m_lastRxReceivedOk(true),
211 m_lastTxEnd(0),
212 m_lastSwitchingEnd(0),
213 m_sleeping(false),
214 m_off(false),
215 m_linkId(0)
216{
217 NS_LOG_FUNCTION(this);
218 InitLastBusyStructs();
219}
220
225
226void
232
233void
235{
236 NS_LOG_FUNCTION(this);
237 for (Ptr<Txop> i : m_txops)
238 {
239 i->Dispose();
240 i = nullptr;
241 }
242 m_phy = nullptr;
243 m_feManager = nullptr;
244 m_phyListeners.clear();
245}
246
247std::shared_ptr<PhyListener>
249{
250 if (auto listenerIt = m_phyListeners.find(phy); listenerIt != m_phyListeners.end())
251 {
252 return listenerIt->second;
253 }
254 return nullptr;
255}
256
257void
259{
260 NS_LOG_FUNCTION(this << phy);
261
262 auto phyListener = GetPhyListener(phy);
263
264 if (phyListener)
265 {
266 // a PHY listener for the given PHY already exists, it must be inactive
267 NS_ASSERT_MSG(!phyListener->IsActive(),
268 "There is already an active listener registered for given PHY");
269 NS_ASSERT_MSG(!m_phy, "Cannot reactivate a listener if another PHY is active");
270 phyListener->SetActive(true);
271 // if a PHY listener already exists, the PHY was disconnected and now reconnected to the
272 // channel access manager; unregister the listener and register again (below) to get
273 // updated CCA busy information
274 phy->UnregisterListener(phyListener);
275 }
276 else
277 {
278 phyListener = std::make_shared<PhyListener>(this);
279 m_phyListeners.emplace(phy, phyListener);
280 if (!m_phy)
281 {
282 // no PHY operating on this link and no previous PHY listener to reactivate
284 }
285 }
286 if (m_phy)
287 {
289 }
290 m_phy = phy; // this is the new active PHY
292 phy->RegisterListener(phyListener);
293}
294
295void
297{
298 NS_LOG_FUNCTION(this << phy);
299 if (auto phyListener = GetPhyListener(phy))
300 {
301 phy->UnregisterListener(phyListener);
302 m_phyListeners.erase(phy);
303 // reset m_phy if we are removing listener registered for the active PHY
304 if (m_phy == phy)
305 {
306 m_phy = nullptr;
307 }
308 }
309}
310
311void
313{
314 NS_LOG_FUNCTION(this << phy);
315 if (auto listener = GetPhyListener(phy))
316 {
317 listener->SetActive(false);
318 }
319 if (m_phy == phy)
320 {
321 m_phy = nullptr;
322 }
323}
324
325void
327 const WifiPhyOperatingChannel& channel,
328 uint8_t linkId)
329{
330 NS_LOG_FUNCTION(this << phy << channel << linkId);
332 "The given PHY is already expected to switch channel");
333 m_switchingEmlsrLinks.emplace(phy, EmlsrLinkSwitchInfo{channel, linkId});
334}
335
336void
338{
339 NS_LOG_FUNCTION(this << +linkId);
340 m_linkId = linkId;
341}
342
343void
345{
346 NS_LOG_FUNCTION(this << feManager);
347 m_feManager = feManager;
348 m_feManager->SetChannelAccessManager(this);
349}
350
351Time
353{
354 return m_phy->GetSlot();
355}
356
357Time
359{
360 return m_phy->GetSifs();
361}
362
363Time
368
369void
371{
372 NS_LOG_FUNCTION(this << txop);
373 m_txops.push_back(txop);
374}
375
376void
378{
379 NS_LOG_FUNCTION(this);
380 const auto now = Simulator::Now();
381
383 m_lastIdle.emplace(WIFI_CHANLIST_PRIMARY, Timespan{now, now});
384
385 const auto width = m_phy ? m_phy->GetChannelWidth() : MHz_u{0};
386 std::size_t size = (width > MHz_u{20} && m_phy->GetStandard() >= WIFI_STANDARD_80211ax)
387 ? Count20MHzSubchannels(width)
388 : 0;
389 m_lastPer20MHzBusyEnd.resize(size, now);
390
392 {
393 return;
394 }
395
396 if (width >= MHz_u{40})
397 {
399 m_lastIdle.emplace(WIFI_CHANLIST_SECONDARY, Timespan{now, now});
400 }
401 else
402 {
405 }
406
407 if (width >= MHz_u{80})
408 {
411 }
412 else
413 {
416 }
417
418 if (width >= MHz_u{160})
419 {
422 }
423 else
424 {
427 }
428
429 // TODO Add conditions for new channel widths as they get supported
430}
431
432void
434{
435 NS_LOG_FUNCTION(this);
436 Time now = Simulator::Now();
437
439
440 // reset all values
441 for (auto& [chType, time] : m_lastBusyEnd)
442 {
443 time = now;
444 }
445
446 for (auto& [chType, timeSpan] : m_lastIdle)
447 {
448 timeSpan = Timespan{now, now};
449 }
450
451 for (auto& time : m_lastPer20MHzBusyEnd)
452 {
453 time = now;
454 }
455}
456
457bool
459{
460 NS_LOG_FUNCTION(this);
461 Time now = Simulator::Now();
462 return (m_lastRx.end > now) // RX
463 || (m_lastTxEnd > now) // TX
464 || (m_lastNavEnd > now) // NAV busy
465 // an EDCA TXOP is obtained based solely on activity of the primary channel
466 // (Sec. 10.23.2.5 of IEEE 802.11-2020)
467 || (m_lastBusyEnd.at(WIFI_CHANLIST_PRIMARY) > now); // CCA busy
468}
469
470bool
472 bool hadFramesToTransmit,
473 bool checkMediumBusy)
474{
475 NS_LOG_FUNCTION(this << txop << hadFramesToTransmit << checkMediumBusy);
476
477 // No backoff needed if in sleep mode or off. Checking if m_phy is nullptr is a workaround
478 // needed for EMLSR and may be removed in the future
479 if (m_sleeping || m_off || !m_phy)
480 {
481 return false;
482 }
483
484 // the Txop might have a stale value of remaining backoff slots
486
487 /*
488 * From section 10.3.4.2 "Basic access" of IEEE 802.11-2016:
489 *
490 * A STA may transmit an MPDU when it is operating under the DCF access
491 * method, either in the absence of a PC, or in the CP of the PCF access
492 * method, when the STA determines that the medium is idle when a frame is
493 * queued for transmission, and remains idle for a period of a DIFS, or an
494 * EIFS (10.3.2.3.7) from the end of the immediately preceding medium-busy
495 * event, whichever is the greater, and the backoff timer is zero. Otherwise
496 * the random backoff procedure described in 10.3.4.3 shall be followed.
497 *
498 * From section 10.22.2.2 "EDCA backoff procedure" of IEEE 802.11-2016:
499 *
500 * The backoff procedure shall be invoked by an EDCAF when any of the following
501 * events occurs:
502 * a) An MA-UNITDATA.request primitive is received that causes a frame with that AC
503 * to be queued for transmission such that one of the transmit queues associated
504 * with that AC has now become non-empty and any other transmit queues
505 * associated with that AC are empty; the medium is busy on the primary channel
506 */
507 if (!hadFramesToTransmit && txop->HasFramesToTransmit(m_linkId) &&
508 txop->GetAccessStatus(m_linkId) != Txop::GRANTED && txop->GetBackoffSlots(m_linkId) == 0)
509 {
510 if (checkMediumBusy && !IsBusy())
511 {
512 // medium idle. If this is a DCF, use immediate access (we can transmit
513 // in a DIFS if the medium remains idle). If this is an EDCAF, update
514 // the backoff start time kept by the EDCAF to the current time in order
515 // to correctly align the backoff start time at the next slot boundary
516 // (performed by the next call to ChannelAccessManager::RequestAccess())
517 Time delay =
518 (txop->IsQosTxop() ? Seconds(0) : GetSifs() + txop->GetAifsn(m_linkId) * GetSlot());
519 txop->UpdateBackoffSlotsNow(0, Simulator::Now() + delay, m_linkId);
520 }
521 else
522 {
523 // medium busy, backoff is needed
524 return true;
525 }
526 }
527 return false;
528}
529
530void
532{
533 NS_LOG_FUNCTION(this << txop);
534 if (m_phy && txop->HasFramesToTransmit(m_linkId))
535 {
537 }
538 // Deny access if in sleep mode or off. Checking if m_phy is nullptr is a workaround
539 // needed for EMLSR and may be removed in the future
540 if (m_sleeping || m_off || !m_phy)
541 {
542 return;
543 }
544 /*
545 * EDCAF operations shall be performed at slot boundaries (Sec. 10.22.2.4 of 802.11-2016)
546 */
547 Time accessGrantStart = GetAccessGrantStart() + (txop->GetAifsn(m_linkId) * GetSlot());
548
549 if (txop->IsQosTxop() && txop->GetBackoffStart(m_linkId) > accessGrantStart)
550 {
551 // The backoff start time reported by the EDCAF is more recent than the last
552 // time the medium was busy plus an AIFS, hence we need to align it to the
553 // next slot boundary.
554 Time diff = txop->GetBackoffStart(m_linkId) - accessGrantStart;
555 uint32_t nIntSlots = (diff / GetSlot()).GetHigh() + 1;
556 txop->UpdateBackoffSlotsNow(0, accessGrantStart + (nIntSlots * GetSlot()), m_linkId);
557 }
558
560 NS_ASSERT(txop->GetAccessStatus(m_linkId) != Txop::REQUESTED);
561 txop->NotifyAccessRequested(m_linkId);
564}
565
566void
568{
569 NS_LOG_FUNCTION(this);
570 uint32_t k = 0;
571 const auto now = Simulator::Now();
572 const auto accessGrantStart = GetAccessGrantStart();
573 for (auto i = m_txops.begin(); i != m_txops.end(); k++)
574 {
575 Ptr<Txop> txop = *i;
576 if (txop->GetAccessStatus(m_linkId) == Txop::REQUESTED &&
577 (!txop->IsQosTxop() || !StaticCast<QosTxop>(txop)->EdcaDisabled(m_linkId)) &&
578 GetBackoffEndFor(txop, accessGrantStart) <= now)
579 {
580 /**
581 * This is the first Txop we find with an expired backoff and which
582 * needs access to the medium. i.e., it has data to send.
583 */
584 NS_LOG_DEBUG("dcf " << k << " needs access. backoff expired. access granted. slots="
585 << txop->GetBackoffSlots(m_linkId));
586 i++; // go to the next item in the list.
587 k++;
588 std::vector<Ptr<Txop>> internalCollisionTxops;
589 for (auto j = i; j != m_txops.end(); j++, k++)
590 {
591 Ptr<Txop> otherTxop = *j;
592 if (otherTxop->GetAccessStatus(m_linkId) == Txop::REQUESTED &&
593 GetBackoffEndFor(otherTxop, accessGrantStart) <= now)
594 {
596 "dcf " << k << " needs access. backoff expired. internal collision. slots="
597 << otherTxop->GetBackoffSlots(m_linkId));
598 /**
599 * all other Txops with a lower priority whose backoff
600 * has expired and which needed access to the medium
601 * must be notified that we did get an internal collision.
602 */
603 internalCollisionTxops.push_back(otherTxop);
604 }
605 }
606
607 /**
608 * Now, we notify all of these changes in one go if the EDCAF winning
609 * the contention actually transmitted a frame. It is necessary to
610 * perform first the calculations of which Txops are colliding and then
611 * only apply the changes because applying the changes through notification
612 * could change the global state of the manager, and, thus, could change
613 * the result of the calculations.
614 */
616 // If we are operating on an OFDM channel wider than 20 MHz, find the largest
617 // idle primary channel and pass its width to the FrameExchangeManager, so that
618 // the latter can transmit PPDUs of the appropriate width (see Section 10.23.2.5
619 // of IEEE 802.11-2020).
620 auto interval = (m_phy->GetPhyBand() == WIFI_PHY_BAND_2_4GHZ)
621 ? GetSifs() + 2 * GetSlot()
622 : m_phy->GetPifs();
623 auto width =
625 ? GetLargestIdlePrimaryChannel(interval, now)
627 if (m_feManager->StartTransmission(txop, width))
628 {
629 for (auto& collidingTxop : internalCollisionTxops)
630 {
631 m_feManager->NotifyInternalCollision(collidingTxop);
632 }
633 break;
634 }
635 else
636 {
637 // this TXOP did not transmit anything, make sure that backoff counter starts
638 // decreasing in a slot again
639 txop->UpdateBackoffSlotsNow(0, now, m_linkId);
640 // reset the current state to the EDCAF that won the contention
641 // but did not transmit anything
642 i--;
643 k = std::distance(m_txops.begin(), i);
644 }
645 }
646 i++;
647 }
648}
649
650void
658
659Time
661{
662 NS_LOG_FUNCTION(this << ignoreNav);
663 auto rxAccessStart = m_lastRx.end;
665 {
666 rxAccessStart += GetEifsNoDifs();
667 }
668 // an EDCA TXOP is obtained based solely on activity of the primary channel
669 // (Sec. 10.23.2.5 of IEEE 802.11-2020)
670 const auto busyAccessStart = m_lastBusyEnd.at(WIFI_CHANLIST_PRIMARY);
671 const auto navAccessStart = ignoreNav ? Time{0} : m_lastNavEnd;
672
673 const auto accessGrantedStart = std::max({rxAccessStart,
674 busyAccessStart,
676 navAccessStart,
680
681 NS_LOG_INFO("access grant start="
682 << accessGrantedStart.As(Time::US)
683 << ", rx access start=" << rxAccessStart.As(Time::US) << ", busy access start="
684 << busyAccessStart.As(Time::US) << ", tx access start=" << m_lastTxEnd.As(Time::US)
685 << ", nav access start=" << navAccessStart.As(Time::US)
686 << ", switching access start=" << m_lastSwitchingEnd.As(Time::US));
687 return accessGrantedStart + GetSifs();
688}
689
690Time
695
696Time
698{
699 NS_LOG_FUNCTION(this << txop << accessGrantStart.As(Time::S));
700 const auto mostRecentEvent =
701 std::max({txop->GetBackoffStart(m_linkId),
702 accessGrantStart + (txop->GetAifsn(m_linkId) * GetSlot())});
703 NS_LOG_DEBUG("Backoff start for " << txop->GetWifiMacQueue()->GetAc() << ": "
704 << mostRecentEvent.As(Time::US));
705
706 return mostRecentEvent;
707}
708
709Time
714
715Time
717{
718 NS_LOG_FUNCTION(this << txop);
719 Time backoffEnd =
720 GetBackoffStartFor(txop, accessGrantStart) + (txop->GetBackoffSlots(m_linkId) * GetSlot());
721 NS_LOG_DEBUG("Backoff end for " << txop->GetWifiMacQueue()->GetAc() << ": "
722 << backoffEnd.As(Time::US));
723
724 return backoffEnd;
725}
726
727Time
729{
730 return m_lastNavEnd;
731}
732
733void
735{
736 NS_LOG_FUNCTION(this);
737 uint32_t k = 0;
738 const auto accessGrantStart = GetAccessGrantStart();
739 for (auto txop : m_txops)
740 {
741 Time backoffStart = GetBackoffStartFor(txop, accessGrantStart);
742 if (backoffStart <= Simulator::Now())
743 {
744 uint32_t nIntSlots = ((Simulator::Now() - backoffStart) / GetSlot()).GetHigh();
745 /*
746 * EDCA behaves slightly different to DCA. For EDCA we
747 * decrement once at the slot boundary at the end of AIFS as
748 * well as once at the end of each clear slot
749 * thereafter. For DCA we only decrement at the end of each
750 * clear slot after DIFS. We account for the extra backoff
751 * by incrementing the slot count here in the case of
752 * EDCA. The if statement whose body we are in has confirmed
753 * that a minimum of AIFS has elapsed since last busy
754 * medium.
755 */
756 if (txop->IsQosTxop())
757 {
758 nIntSlots++;
759 }
760 uint32_t n = std::min(nIntSlots, txop->GetBackoffSlots(m_linkId));
761 NS_LOG_DEBUG("dcf " << k << " dec backoff slots=" << n);
762 Time backoffUpdateBound = backoffStart + (n * GetSlot());
763 txop->UpdateBackoffSlotsNow(n, backoffUpdateBound, m_linkId);
764 }
765 ++k;
766 }
767}
768
769void
771{
772 NS_LOG_FUNCTION(this);
773 /**
774 * Is there a Txop which needs to access the medium, and,
775 * if there is one, how many slots for AIFS+backoff does it require ?
776 */
777 Ptr<Txop> nextTxop;
778 auto expectedBackoffEnd = Simulator::GetMaximumSimulationTime();
779 const auto accessGrantStart = GetAccessGrantStart();
780 const auto now = Simulator::Now();
781 for (auto txop : m_txops)
782 {
783 if (txop->GetAccessStatus(m_linkId) == Txop::REQUESTED)
784 {
785 if (auto backoffEnd = GetBackoffEndFor(txop, accessGrantStart);
786 backoffEnd > now && backoffEnd < expectedBackoffEnd)
787 {
788 expectedBackoffEnd = backoffEnd;
789 nextTxop = txop;
790 }
791 }
792 }
793 NS_LOG_DEBUG("Access timeout needed: " << (nextTxop != nullptr));
794 if (nextTxop)
795 {
796 const auto aci = nextTxop->GetWifiMacQueue()->GetAc();
797 NS_LOG_DEBUG("expected backoff end=" << expectedBackoffEnd << " by " << aci);
798 auto expectedBackoffDelay = expectedBackoffEnd - now;
799
800 if (m_nSlotsLeft > 0)
801 {
802 if (const auto slots = m_nSlotsLeft * GetSlot(); expectedBackoffDelay > slots)
803 {
804 // make the timer expire when the specified number of slots are left
805 expectedBackoffDelay -= slots;
806 }
807 else
808 {
809 // notify that a number of slots less than or equal to the specified value are left
810 m_nSlotsLeftCallback(m_linkId, aci, expectedBackoffDelay);
811 }
812 }
813
815 Simulator::GetDelayLeft(m_accessTimeout) > expectedBackoffDelay)
816 {
818 }
820 {
821 m_accessTimeout = Simulator::Schedule(expectedBackoffDelay,
823 this);
824 }
825 }
826}
827
828MHz_u
830{
831 NS_LOG_FUNCTION(this << interval.As(Time::US) << end.As(Time::S));
832
833 // If the medium is busy or it just became idle, UpdateLastIdlePeriod does
834 // nothing. This allows us to call this method, e.g., at the end of a frame
835 // reception and check the busy/idle status of the channel before the start
836 // of the frame reception (last idle period was last updated at the start of
837 // the frame reception).
838 // If the medium has been idle for some time, UpdateLastIdlePeriod updates
839 // the last idle period. This is normally what we want because this method may
840 // also be called before starting a TXOP gained through EDCA.
842
843 MHz_u width{0};
844
845 // we iterate over the different types of channels in the same order as they
846 // are listed in WifiChannelListType
847 for (const auto& lastIdle : m_lastIdle)
848 {
849 if (lastIdle.second.start <= end - interval && lastIdle.second.end >= end)
850 {
851 // channel idle, update width
852 width = (width == MHz_u{0}) ? MHz_u{20} : (2 * width);
853 }
854 else
855 {
856 break;
857 }
858 }
859 return width;
860}
861
862bool
863ChannelAccessManager::GetPer20MHzBusy(const std::set<uint8_t>& indices) const
864{
865 const auto now = Simulator::Now();
866
867 if (m_phy->GetChannelWidth() < MHz_u{40})
868 {
869 NS_ASSERT_MSG(indices.size() == 1 && *indices.cbegin() == 0,
870 "Index 0 only can be specified if the channel width is less than 40 MHz");
871 return m_lastBusyEnd.at(WIFI_CHANLIST_PRIMARY) > now;
872 }
873
874 for (const auto index : indices)
875 {
876 NS_ASSERT(index < m_lastPer20MHzBusyEnd.size());
877 if (m_lastPer20MHzBusyEnd.at(index) > now)
878 {
879 NS_LOG_DEBUG("20 MHz channel with index " << +index << " is busy");
880 return true;
881 }
882 }
883 return false;
884}
885
886void
888{
889 NS_LOG_FUNCTION(this << qosTxop << duration);
890 NS_ASSERT(qosTxop->IsQosTxop());
892 Time resume = Simulator::Now() + duration;
893 NS_LOG_DEBUG("Backoff will resume at time " << resume << " with "
894 << qosTxop->GetBackoffSlots(m_linkId)
895 << " remaining slot(s)");
896 qosTxop->UpdateBackoffSlotsNow(0, resume, m_linkId);
898}
899
900void
902{
903 NS_LOG_FUNCTION(this << enable);
905}
906
907bool
912
913void
915{
916 NS_LOG_FUNCTION(this << duration);
917 NS_LOG_DEBUG("rx start for=" << duration);
921 m_lastRx.end = m_lastRx.start + duration;
922 m_lastRxReceivedOk = true;
923}
924
925void
933
934void
936{
937 NS_LOG_FUNCTION(this);
938 NS_LOG_DEBUG("rx end error");
939 // we expect the PHY to notify us of the start of a CCA busy period, if needed
941 m_lastRxReceivedOk = false;
942}
943
944void
946{
947 NS_LOG_FUNCTION(this << duration);
948 m_lastRxReceivedOk = true;
949 Time now = Simulator::Now();
950 if (m_lastRx.end > now)
951 {
952 // this may be caused only if PHY has started to receive a packet
953 // inside SIFS, so, we check that lastRxStart was maximum a SIFS ago
954 NS_ASSERT(now - m_lastRx.start <= GetSifs());
955 m_lastRx.end = now;
956 }
957 else
958 {
960 }
961 NS_LOG_DEBUG("tx start for " << duration);
963 m_lastTxEnd = now + duration;
964}
965
966void
968 WifiChannelListType channelType,
969 const std::vector<Time>& per20MhzDurations)
970{
971 NS_LOG_FUNCTION(this << duration << channelType);
974 auto lastBusyEndIt = m_lastBusyEnd.find(channelType);
975 NS_ASSERT(lastBusyEndIt != m_lastBusyEnd.end());
976 Time now = Simulator::Now();
977 lastBusyEndIt->second = now + duration;
978 NS_ASSERT_MSG(per20MhzDurations.size() == m_lastPer20MHzBusyEnd.size(),
979 "Size of received vector (" << per20MhzDurations.size()
980 << ") differs from the expected size ("
981 << m_lastPer20MHzBusyEnd.size() << ")");
982 for (std::size_t chIdx = 0; chIdx < per20MhzDurations.size(); ++chIdx)
983 {
984 if (per20MhzDurations[chIdx].IsStrictlyPositive())
985 {
986 m_lastPer20MHzBusyEnd[chIdx] = now + per20MhzDurations[chIdx];
987 }
988 }
989
991 {
992 // have all EDCAFs that are not carrying out a TXOP and have the backoff counter set to
993 // zero proactively generate a new backoff value
994 for (auto txop : m_txops)
995 {
996 if (txop->GetAccessStatus(m_linkId) != Txop::GRANTED &&
997 txop->GetBackoffSlots(m_linkId) == 0)
998 {
999 NS_LOG_DEBUG("Generate backoff for " << txop->GetWifiMacQueue()->GetAc());
1000 txop->GenerateBackoff(m_linkId);
1001 }
1002 }
1003 }
1004}
1005
1006void
1008{
1009 NS_LOG_FUNCTION(this << phyListener << duration);
1010
1011 Time now = Simulator::Now();
1012 NS_ASSERT(m_lastTxEnd <= now);
1013
1014 if (phyListener) // to make tests happy
1015 {
1016 // check if the PHY switched channel to operate on another EMLSR link
1017
1018 for (const auto& [phyRef, listener] : m_phyListeners)
1019 {
1020 Ptr<WifiPhy> phy = phyRef;
1021 auto emlsrInfoIt = m_switchingEmlsrLinks.find(phy);
1022
1023 if (listener.get() == phyListener && emlsrInfoIt != m_switchingEmlsrLinks.cend() &&
1024 phy->GetOperatingChannel() == emlsrInfoIt->second.channel)
1025 {
1026 // the PHY associated with the given PHY listener switched channel to
1027 // operate on another EMLSR link as expected. We don't need this listener
1028 // anymore. The MAC will connect a new listener to the ChannelAccessManager
1029 // instance associated with the link the PHY is now operating on
1030 RemovePhyListener(phy);
1032 NS_ASSERT(ehtFem);
1033 ehtFem->NotifySwitchingEmlsrLink(phy, emlsrInfoIt->second.linkId, duration);
1034 m_switchingEmlsrLinks.erase(emlsrInfoIt);
1035 return;
1036 }
1037 }
1038 }
1039
1040 ResetState();
1041
1042 // Cancel timeout
1044 {
1046 }
1047
1048 // Reset backoffs
1049 for (const auto& txop : m_txops)
1050 {
1051 ResetBackoff(txop);
1052 }
1053
1054 // Notify the FEM, which will in turn notify the MAC
1055 m_feManager->NotifySwitchingStartNow(duration);
1056
1057 NS_LOG_DEBUG("switching start for " << duration);
1058 m_lastSwitchingEnd = now + duration;
1059}
1060
1061void
1063{
1064 NS_LOG_FUNCTION(this);
1065
1066 Time now = Simulator::Now();
1067 m_lastRxReceivedOk = true;
1069 m_lastRx.end = std::min(m_lastRx.end, now);
1070 m_lastNavEnd = std::min(m_lastNavEnd, now);
1073
1075}
1076
1077void
1079{
1080 NS_LOG_FUNCTION(this << txop);
1081
1082 uint32_t remainingSlots = txop->GetBackoffSlots(m_linkId);
1083 if (remainingSlots > 0)
1084 {
1085 txop->UpdateBackoffSlotsNow(remainingSlots, Simulator::Now(), m_linkId);
1086 NS_ASSERT(txop->GetBackoffSlots(m_linkId) == 0);
1087 }
1088 txop->ResetCw(m_linkId);
1089 txop->GetLink(m_linkId).access = Txop::NOT_REQUESTED;
1090}
1091
1092void
1094{
1095 NS_LOG_FUNCTION(this);
1096
1097 for (const auto& txop : m_txops)
1098 {
1099 ResetBackoff(txop);
1100 }
1102}
1103
1104void
1106{
1107 NS_LOG_FUNCTION(this);
1108 m_sleeping = true;
1109 // Reset backoffs
1111 m_feManager->NotifySleepNow();
1112 for (auto txop : m_txops)
1113 {
1114 txop->NotifySleep(m_linkId);
1115 }
1116}
1117
1118void
1120{
1121 NS_LOG_FUNCTION(this);
1122 m_off = true;
1123 // Cancel timeout
1125 {
1127 }
1128
1129 // Reset backoffs
1130 for (auto txop : m_txops)
1131 {
1132 txop->NotifyOff();
1133 }
1134}
1135
1136void
1138{
1139 NS_LOG_FUNCTION(this);
1140 m_sleeping = false;
1141 for (auto txop : m_txops)
1142 {
1143 ResetBackoff(txop);
1144 txop->NotifyWakeUp(m_linkId);
1145 }
1146}
1147
1148void
1150{
1151 NS_LOG_FUNCTION(this);
1152 m_off = false;
1153 for (auto txop : m_txops)
1154 {
1155 ResetBackoff(txop);
1156 txop->NotifyOn();
1157 }
1158}
1159
1160void
1162{
1163 NS_LOG_FUNCTION(this << duration);
1164
1165 if (!m_phy)
1166 {
1167 NS_LOG_DEBUG("Do not reset NAV, CTS may have been missed due to the main PHY switching "
1168 "to another link to take over a TXOP while receiving the CTS");
1169 return;
1170 }
1171
1172 NS_LOG_DEBUG("nav reset for=" << duration);
1173 UpdateBackoff();
1174 m_lastNavEnd = Simulator::Now() + duration;
1175 /**
1176 * If the NAV reset indicates an end-of-NAV which is earlier
1177 * than the previous end-of-NAV, the expected end of backoff
1178 * might be later than previously thought so, we might need
1179 * to restart a new access timeout.
1180 */
1182}
1183
1184void
1186{
1187 NS_LOG_FUNCTION(this << duration);
1188 NS_LOG_DEBUG("nav start for=" << duration);
1189 UpdateBackoff();
1190 m_lastNavEnd = std::max(m_lastNavEnd, Simulator::Now() + duration);
1191}
1192
1193void
1200
1201void
1208
1209void
1211{
1212 NS_LOG_FUNCTION(this << duration);
1213 m_lastCtsTimeoutEnd = Simulator::Now() + duration;
1214}
1215
1216void
1223
1224void
1226{
1227 NS_LOG_FUNCTION(this);
1228 Time idleStart = std::max({m_lastTxEnd, m_lastRx.end, m_lastSwitchingEnd});
1229 Time now = Simulator::Now();
1230
1231 if (idleStart >= now)
1232 {
1233 // No new idle period
1234 return;
1235 }
1236
1237 for (const auto& busyEnd : m_lastBusyEnd)
1238 {
1239 if (busyEnd.second < now)
1240 {
1241 auto lastIdleIt = m_lastIdle.find(busyEnd.first);
1242 NS_ASSERT(lastIdleIt != m_lastIdle.end());
1243 lastIdleIt->second = {std::max(idleStart, busyEnd.second), now};
1244 NS_LOG_DEBUG("New idle period (" << lastIdleIt->second.start.As(Time::S) << ", "
1245 << lastIdleIt->second.end.As(Time::S)
1246 << ") on channel " << lastIdleIt->first);
1247 }
1248 }
1249}
1250
1251} // namespace ns3
AttributeValue implementation for Boolean.
Definition boolean.h:26
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)
NSlotsLeftTracedCallback m_nSlotsLeftCallback
traced callback for NSlotsLeft alerts
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
uint8_t m_nSlotsLeft
fire the NSlotsLeftAlert trace source when the backoff counter with the minimum value among all ACs r...
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
Hold an unsigned integer type.
Definition uinteger.h:34
Time GetSlot() const
Return the slot duration for this PHY.
Definition wifi-phy.cc:841
Time GetSifs() const
Return the Short Interframe Space (SIFS) for this PHY.
Definition wifi-phy.cc:829
WifiPhyBand GetPhyBand() const
Get the configured Wi-Fi band.
Definition wifi-phy.cc:1069
Time GetPifs() const
Return the PCF Interframe Space (PIFS) for this PHY.
Definition wifi-phy.cc:853
MHz_u GetChannelWidth() const
Definition wifi-phy.cc:1099
WifiStandard GetStandard() const
Get the configured Wi-Fi standard.
Definition wifi-phy.cc:1075
void NotifyChannelAccessRequested()
Notify the PHY that an access to the channel was requested.
Definition wifi-phy.cc:2017
Time GetAckTxTime() const
Return the estimated Ack TX time for this PHY.
Definition wifi-phy.cc:859
const WifiPhyOperatingChannel & GetOperatingChannel() const
Get a const reference to the operating channel.
Definition wifi-phy.cc:1081
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
Ptr< const AttributeChecker > MakeBooleanChecker()
Definition boolean.cc:113
Ptr< const AttributeAccessor > MakeBooleanAccessor(T1 a1)
Create an AttributeAccessor for a class data member, or a lone class get functor or set method.
Definition boolean.h:70
Ptr< const AttributeChecker > MakeUintegerChecker()
Definition uinteger.h:85
Ptr< const AttributeAccessor > MakeUintegerAccessor(T1 a1)
Create an AttributeAccessor for a class data member, or a lone class get functor or set method.
Definition uinteger.h:35
#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:1368
Time Seconds(double value)
Construct a Time in the indicated unit.
Definition nstime.h:1344
Ptr< const TraceSourceAccessor > MakeTraceSourceAccessor(T a)
Create a TraceSourceAccessor which will control access to the underlying trace source.
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.
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
std::size_t Count20MHzSubchannels(MHz_u channelWidth)
Return the number of 20 MHz subchannels covering the channel width.
Definition wifi-utils.h:134
Ptr< T1 > StaticCast(const Ptr< T2 > &p)
Cast a Ptr.
Definition ptr.h:587
Structure defining start time and end time for a given state.