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 * 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
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
36namespace ns3
37{
38
39NS_LOG_COMPONENT_DEFINE("ChannelAccessManager");
40
41NS_OBJECT_ENSURE_REGISTERED(ChannelAccessManager);
42
43/**
44 * Listener for PHY events. Forwards to ChannelAccessManager.
45 * The ChannelAccessManager may handle multiple PHY listeners connected to distinct PHYs,
46 * but only one listener at a time can be active. Notifications from inactive listeners are
47 * ignored by the ChannelAccessManager, except for the channel switch notification.
48 * Inactive PHY listeners are typically configured by 11be EMLSR clients.
49 */
51{
52 public:
53 /**
54 * Create a PhyListener for the given ChannelAccessManager.
55 *
56 * \param cam the ChannelAccessManager
57 */
59 : m_cam(cam),
60 m_active(true)
61 {
62 }
63
64 ~PhyListener() override
65 {
66 }
67
68 /**
69 * Set this listener to be active or not.
70 *
71 * \param active whether this listener is active or not
72 */
73 void SetActive(bool active)
74 {
75 m_active = active;
76 }
77
78 /**
79 * \return whether this listener is active or not
80 */
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
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 {
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 {
162 }
163 }
164
165 private:
166 ns3::ChannelAccessManager* m_cam; //!< ChannelAccessManager to forward events to
167 bool m_active; //!< whether this PHY listener is active
168};
169
170/****************************************************************
171 * Implement the channel access manager of all Txop holders
172 ****************************************************************/
173
174TypeId
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
216void
218{
219 NS_LOG_FUNCTION(this);
221}
222
223void
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
237std::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
247void
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
280void
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
296void
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
310void
312 const WifiPhyOperatingChannel& channel,
313 uint8_t linkId)
314{
315 NS_LOG_FUNCTION(this << phy << channel << linkId);
317 "The given PHY is already expected to switch channel");
318 m_switchingEmlsrLinks.emplace(phy, EmlsrLinkSwitchInfo{channel, linkId});
319}
320
321void
323{
324 NS_LOG_FUNCTION(this << +linkId);
325 m_linkId = linkId;
326}
327
328void
330{
331 NS_LOG_FUNCTION(this << feManager);
332 m_feManager = feManager;
333 m_feManager->SetChannelAccessManager(this);
334}
335
336Time
338{
339 return m_phy->GetSlot();
340}
341
342Time
344{
345 return m_phy->GetSifs();
346}
347
348Time
350{
351 return m_phy->GetSifs() + m_phy->GetAckTxTime();
352}
353
354void
356{
357 NS_LOG_FUNCTION(this << txop);
358 m_txops.push_back(txop);
359}
360
361void
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
373 {
374 return;
375 }
376
377 uint16_t width = m_phy->GetChannelWidth();
378
379 if (width >= 40)
380 {
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
402bool
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
415bool
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
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) &&
453 txop->GetAccessStatus(m_linkId) != Txop::GRANTED && txop->GetBackoffSlots(m_linkId) == 0)
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
475void
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
505 NS_ASSERT(txop->GetAccessStatus(m_linkId) != Txop::REQUESTED);
506 txop->NotifyAccessRequested(m_linkId);
509}
510
511void
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 {
524 /**
525 * This is the first Txop we find with an expired backoff and which
526 * needs access to the medium. i.e., it has data to send.
527 */
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 {
540 "dcf " << k << " needs access. backoff expired. internal collision. slots="
541 << otherTxop->GetBackoffSlots(m_linkId));
542 /**
543 * all other Txops with a lower priority whose backoff
544 * has expired and which needed access to the medium
545 * must be notified that we did get an internal collision.
546 */
547 internalCollisionTxops.push_back(otherTxop);
548 }
549 }
550
551 /**
552 * Now, we notify all of these changes in one go if the EDCAF winning
553 * the contention actually transmitted a frame. It is necessary to
554 * perform first the calculations of which Txops are colliding and then
555 * only apply the changes because applying the changes through notification
556 * could change the global state of the manager, and, thus, could change
557 * the result of the calculations.
558 */
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)
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
593void
595{
596 NS_LOG_FUNCTION(this);
600}
601
602Time
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
650Time
652{
653 NS_LOG_FUNCTION(this << txop);
654 Time mostRecentEvent =
655 std::max({txop->GetBackoffStart(m_linkId),
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
663Time
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
674void
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
709void
711{
712 NS_LOG_FUNCTION(this);
713 /**
714 * Is there a Txop which needs to access the medium, and,
715 * if there is one, how many slots for AIFS+backoff does it require ?
716 */
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();
737 Simulator::GetDelayLeft(m_accessTimeout) > expectedBackoffDelay)
738 {
740 }
742 {
743 m_accessTimeout = Simulator::Schedule(expectedBackoffDelay,
745 this);
746 }
747 }
748}
749
750uint16_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
784bool
785ChannelAccessManager::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
808void
810{
811 NS_LOG_FUNCTION(this << qosTxop << duration);
812 NS_ASSERT(qosTxop->IsQosTxop());
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
822void
824{
825 NS_LOG_FUNCTION(this << enable);
827}
828
829bool
831{
833}
834
835void
837{
838 NS_LOG_FUNCTION(this << duration);
839 NS_LOG_DEBUG("rx start for=" << duration);
843 m_lastRx.end = m_lastRx.start + duration;
844 m_lastRxReceivedOk = true;
845}
846
847void
849{
850 NS_LOG_FUNCTION(this);
851 NS_LOG_DEBUG("rx end ok");
853 m_lastRxReceivedOk = true;
854}
855
856void
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
866void
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);
885 m_lastTxEnd = now + duration;
886}
887
888void
890 WifiChannelListType channelType,
891 const std::vector<Time>& per20MhzDurations)
892{
893 NS_LOG_FUNCTION(this << duration << channelType);
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
913void
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
963void
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);
972 m_lastNavEnd = std::min(m_lastNavEnd, now);
975
977
978 // Cancel timeout
980 {
982 }
983}
984
985void
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);
997 txop->GetLink(m_linkId).access = Txop::NOT_REQUESTED;
998}
999
1000void
1002{
1003 NS_LOG_FUNCTION(this);
1004
1005 for (const auto& txop : m_txops)
1006 {
1007 ResetBackoff(txop);
1008 }
1010}
1011
1012void
1014{
1015 NS_LOG_FUNCTION(this);
1016 m_sleeping = true;
1017 // Cancel timeout
1019 {
1021 }
1022
1023 // Reset backoffs
1024 for (auto txop : m_txops)
1025 {
1026 txop->NotifySleep(m_linkId);
1027 }
1028}
1029
1030void
1032{
1033 NS_LOG_FUNCTION(this);
1034 m_off = true;
1035 // Cancel timeout
1037 {
1039 }
1040
1041 // Reset backoffs
1042 for (auto txop : m_txops)
1043 {
1044 txop->NotifyOff();
1045 }
1046}
1047
1048void
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
1060void
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
1072void
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;
1087 /**
1088 * If the NAV reset indicates an end-of-NAV which is earlier
1089 * than the previous end-of-NAV, the expected end of backoff
1090 * might be later than previously thought so, we might need
1091 * to restart a new access timeout.
1092 */
1094}
1095
1096void
1098{
1099 NS_LOG_FUNCTION(this << duration);
1100 NS_LOG_DEBUG("nav start for=" << duration);
1101 UpdateBackoff();
1102 m_lastNavEnd = std::max(m_lastNavEnd, Simulator::Now() + duration);
1103}
1104
1105void
1107{
1108 NS_LOG_FUNCTION(this << duration);
1110 m_lastAckTimeoutEnd = Simulator::Now() + duration;
1111}
1112
1113void
1115{
1116 NS_LOG_FUNCTION(this);
1119}
1120
1121void
1123{
1124 NS_LOG_FUNCTION(this << duration);
1125 m_lastCtsTimeoutEnd = Simulator::Now() + duration;
1126}
1127
1128void
1130{
1131 NS_LOG_FUNCTION(this);
1134}
1135
1136void
1138{
1139 NS_LOG_FUNCTION(this);
1140 m_usingOtherEmlsrLink = true;
1141}
1142
1143void
1145{
1146 NS_LOG_FUNCTION(this);
1147 m_usingOtherEmlsrLink = false;
1148}
1149
1150void
1152{
1153 NS_LOG_FUNCTION(this);
1154 Time idleStart = std::max({m_lastTxEnd, m_lastRx.end, m_lastSwitchingEnd});
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
AttributeValue implementation for Boolean.
Definition: boolean.h:37
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
Smart pointer class similar to boost::intrusive_ptr.
Definition: ptr.h:77
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
@ GRANTED
Definition: txop.h:112
@ NOT_REQUESTED
Definition: txop.h:110
@ REQUESTED
Definition: txop.h:111
a unique identifier for an interface.
Definition: type-id.h:59
TypeId SetParent(TypeId tid)
Set the parent TypeId.
Definition: type-id.cc:932
Time GetSlot() const
Return the slot duration for this PHY.
Definition: wifi-phy.cc:815
uint16_t GetChannelWidth() const
Definition: wifi-phy.cc:1073
Time GetSifs() const
Return the Short Interframe Space (SIFS) for this PHY.
Definition: wifi-phy.cc:803
WifiPhyBand GetPhyBand() const
Get the configured Wi-Fi band.
Definition: wifi-phy.cc:1043
Time GetPifs() const
Return the PCF Interframe Space (PIFS) for this PHY.
Definition: wifi-phy.cc:827
WifiStandard GetStandard() const
Get the configured Wi-Fi standard.
Definition: wifi-phy.cc:1049
void NotifyChannelAccessRequested()
Notify the PHY that an access to the channel was requested.
Definition: wifi-phy.cc:1939
Time GetAckTxTime() const
Return the estimated Ack TX time for this PHY.
Definition: wifi-phy.cc:833
const WifiPhyOperatingChannel & GetOperatingChannel() const
Get a const reference to the operating channel.
Definition: wifi-phy.cc:1055
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
Ptr< const AttributeAccessor > MakeBooleanAccessor(T1 a1)
Definition: boolean.h:81
Ptr< const AttributeChecker > MakeBooleanChecker()
Definition: boolean.cc:124
#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:1343
Time Seconds(double value)
Construct a Time in the indicated unit.
Definition: nstime.h:1319
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.