diff --git a/WHATSNEW b/WHATSNEW
index bdad647e6ad..8cebaec1905 100644
--- a/WHATSNEW
+++ b/WHATSNEW
@@ -428,6 +428,72 @@ Notable backward incompatible changes are the following:
The new parameter urgentOutOfBand, which LinuxTcp sets, gives the receive side of urgent data of Linux without SO_OOBINLINE: the last byte of urgent data leaves the stream, and a read stops before it. INET does not deliver that byte, which Linux gives to recv(MSG_OOB), and it sends no urgent data. Tcp keeps treating URG-marked bytes as ordinary data. Simulations change only where a peer sends urgent data to LinuxTcp.
+55. IEEE 802.11 Block Ack agreement lifecycle
+
+ HCF now dispatches accepted Block Ack Actions once after duplicate checks. Setup correlates the peer, TID, and dialog token. The recipient response determines the accepted buffer size and inactivity timeout. Both negotiated flags control A-MSDU use.
+
+ HCF defers new DATA extraction during setup and recovery. The first DATA under an accepted agreement retains the request sequence number. The response timeout starts after the request ACK, with a configurable default of 100 ms. Uncertain setup requires DELBA before surviving unsent DATA can resume. Failed DELBA discards the current deferred batch. Later DATA can start a fresh recovery exchange.
+
+ If an internal collision discards the first DATA before transmission, HCF retires the agreement and queues DELBA. The unsent successor remains deferred until recovery resolves. Lifetime expiry and RTS failure use the same retirement mechanism.
+
+ Directional teardown preserves the opposite agreement. The originator discards transmitted, unacknowledged DATA, even when its lifetime remains valid at inactivity expiration. It also discards unsent fragments through the highest transmitted allocation index. This local index records allocation order before fragmentation. This prevents transmission of an incomplete unit outside the agreement. Later intact DATA remains eligible, including across sequence wraparound.
+
+ Originator retirement now uses local allocation indices to preserve this boundary across large sequence gaps. A distance of exactly 2048 between DATA transmissions no longer lets unsent fragments survive teardown. The wire sequence numbers remain unchanged.
+
+ The recipient delivers complete buffered units in cyclic order across receive gaps before resource release. Association reset instead discards old buffered units in both directions. With Block Ack enabled, reset also discards queued and in-progress QoS DATA for the old peer, including unsent Normal Ack DATA. These changes can alter delivery, disposal, frame exchanges, and fingerprints.
+
+ Custom agreement handlers, recipient services, and management implementations require the changes in the migration guide. External queues need the optional selected-extraction contract only when deferred DATA hides eligible work. Built-in queues preserve ordinary pull accounting for that extraction.
+
+51. IEEE 802.11 Basic Block Ack transfer completion
+
+ Callers must supply agreement arguments explicitly for BAR selection and transmitted-BAR processing. An omitted argument now fails compilation instead of silently selecting behavior without agreement state.
+
+ Basic BAR selection now separates each peer and TID. Short bursts and small negotiated windows can cause BAR below the configured threshold. BAR can obtain channel access without queued DATA. After final completion or final DATA expiration, the originator sends DELBA even with zero inactivity timeout. Later bursts can require another ADDBA exchange. These changes affect exchange order, airtime, simulation results, and fingerprints for Basic Block Ack configurations.
+
+ Basic single protection now reserves only an already prepared successor. Further DATA extraction starts a new TXOP and can require more contention. Final DATA in a zero-limit TXOP reserves only its immediate response. These corrections can change Duration fields, airtime, and fingerprints.
+
+ Custom QoS ACK policies must accept the agreement-handler argument in isBlockAckReqNeeded() and computeBlockAckReqParameters(). Custom in-progress queues must preserve the extract argument in getPendingFrameFor(). QoS data services must implement IOriginatorQosMacDataService for candidate preview without extraction.
+
+52. IEEE 802.11 immediate Compressed Block Ack
+
+ HCF defaults to immediate Compressed Block Ack. Eligible HT infrastructure links and the supported ac path use accepted agreements and 32-byte Compressed responses with FCS. Links without usable local or peer HT state retain Normal Ack. Select blockAckVariant = "basic" at both endpoints for historical Basic compatibility. An HT PHY mode alone does not supply peer capability state.
+
+ Both endpoints must select the same blockAckVariant because ADDBA does not negotiate it. A valid addressed BAR with the other profile stops the simulation before agreement updates. The diagnostic identifies the peer, TID, and profiles. Compressed rejects delayedAckPolicySupported = true in either agreement policy at initialization.
+
+ Compressed receipt state retains positive bits after delivery and reports zero for absent receipts. HCF recognizes the required SIFS or RIFS relation from actual physical endpoints. It can release individual MPDUs under that relation despite a receive gap. Compressed agreements suppress fragmentation for their peer and TID; Basic retains its fragment bitmap.
+
+ Compressed BAR uses a legal non-HT rate. Initial and later BAR consume their applicable BSS and peer rate sets. A configured controlFrameBitrate must satisfy the applicable rule. Response duration, protection, and timeout calculations use the 32-byte response length. Basic retains its separate 152-byte response.
+
+ Active Compressed agreements check complete DATA exchange cost before destructive aggregation. If an aggregate cannot qualify, HCF evaluates its first ordinary MSDU with the same selected mode. Refusal preserves original queue frames, deadlines, and sequence state. Single protection reserves only the current exchange; a later BAR can require fresh contention. These changes can alter airtime, contention, delivery times, and simulation results. Basic and traffic outside active Compressed agreements retain their existing admission limits.
+
+ Before DATA commitment, failed RTS updates access-category retry state without a provisional sequence counter. Original DATA lifetimes bound repeated attempts. This changes the scoped path's former discard after a fixed number of RTS failures.
+
+ Existing demonstrations retain explicit Basic settings, configurations, and result rows. IEEE Std 802.11e-2005 introduced Basic; IEEE Std 802.11-2024 reserves its Type 0 value. Retention does not establish complete historical conformance. A-MPDU requirements for n and ac remain deferred. Multi-TID Block Ack and the existing delayed Basic requirement also remain deferred. Compressed support does not establish complete VHT transfer support or MAC stop/start recovery.
+
+ Custom QoS rate selectors, recipient ACK policies, recipient data services, and frame sequences require the interface changes in the migration guide. Custom radio media, communication caches, and IEEE 802.11 modes also require the new timing queries. Basic classes and methods remain available.
+
+53. Radio medium timing queries
+
+ Radio media now expose copied transmission endpoints and cached arrival endpoints. Consumers can preserve physical timing evidence after cache expiry without retained transmission pointers or new propagation calculations. Receivers attach the transmission identifier to SignalTimeInd so later frame delivery can identify that evidence. Custom radio media and communication caches must implement the new query methods described in the migration guide.
+
+54. IEEE 802.11 signal extension
+
+ HT duration at 2.4 GHz now includes its six-microsecond signal extension. Previously, that waveform ended early, which could change interframe recognition and airtime. Custom IEEE 802.11 modes must supply the new nominal-extension query. A transmission query supplies the actual extension from its waveform so consumers do not count it twice.
+
+55. IEEE 802.11 Block Ack wire types
+
+ The serializer supports the complete four-bit Type field for Basic compatibility and Compressed frames. Compressed responses contain a 64-bit bitmap and occupy 32 bytes with FCS. Both BAR profiles occupy 24 bytes with FCS. Basic producers must keep reserved bits that overlap Type zero. Invalid Types and malformed fragment fields now fail validation instead of ambiguous decoding.
+
+56. IEEE 802.11 RIFS permission
+
+ HT Operation now preserves RIFS Mode through management conversion and equality checks. The MIB permits RIFS under unprotected HT operation and clears the permission for VHT. A recipient can therefore check the actual advertised permission before it recognizes a RIFS relation. This change adds no RIFS transmission scheduler.
+
+57. IEEE 802.11 reversible DATA preparation
+
+ QoS data services now supply an owned DATA preview before queue extraction or sequence assignment. A successful commit transfers that same preview to the caller. A refused commit preserves the preview and original queue frames. Custom services must implement the preparation contract in the migration guide. The existing extraction path remains available.
+
+ Commitment of ordinary DATA now preserves normal queue service effects. Queue output statistics count the departure, and the retained preview includes elapsed queueing time and queue events. This also applies when a deferred head causes selection of a later eligible frame.
+
Notable backward compatible changes are the following:
1. IEEE 802.11 per-station rate statistics
@@ -504,12 +570,16 @@ Notable backward compatible changes are the following:
14. Explicit historical Basic Block Ack selection
- HCF adds blockAckVariant = "basic" for historical compatibility. Existing Block Ack, TXOP, QoS, and fragmentation demonstrations select it explicitly. Basic remains the default. HCF rejects unknown values and the reserved "compressed" value during initialization until Compressed exchanges exist. These checks also apply when Block Ack support is disabled. Basic originated in IEEE Std 802.11e-2005; IEEE Std 802.11-2024 reserves its type value.
+ HCF retains blockAckVariant = "basic" for historical compatibility. Existing Block Ack, TXOP, QoS, and fragmentation demonstrations select it explicitly. HCF rejects unknown selector values during initialization, also when Block Ack support is disabled. Basic originated in IEEE Std 802.11e-2005; IEEE Std 802.11-2024 reserves its type value.
15. IEEE 802.11 retry counters for each receiver
QoS recovery now keeps separate retry counters for each receiver. Frames with equal TID, sequence number, and fragment number no longer share a counter across receivers. An acknowledgment or retry-limit disposal for one receiver preserves the other receiver's counter. This repair applies before DATA lifetime expiration.
+16. Optional selected extraction from packet queues
+
+ PacketQueue, CompoundPacketQueueBase, and PriorityScheduler now support the optional ISelectivePacketCollection contract. A consumer can find an eligible packet without a state change. It can extract that packet with ordinary pull events, residence metadata, and ownership transfer. Ordinary head selection remains unchanged. Providers without this capability report UnsupportedPacketSelection before extraction.
+
Notable bug fixes are the following:
1. IEEE 802.11 HT modes preserve the requested band and preamble
diff --git a/doc/src/migration-guide/index.rst b/doc/src/migration-guide/index.rst
index 6da76178c57..8a171ab7075 100644
--- a/doc/src/migration-guide/index.rst
+++ b/doc/src/migration-guide/index.rst
@@ -14,6 +14,73 @@ retransmissions do (``synRetries``). When they run out, the connection raises th
``TCP_E_TIMEOUT_CONN_ESTAB``, as the timer did before, so a subclass that handles this event needs no
change.
+Radio medium timing queries
+--------------------------
+
+Custom radio media must implement ``getTransmissionTimingSnapshot()`` and ``computePrecedingSignalGap()``. These queries supply copied physical endpoints from actual transmissions and cached arrivals. Unavailable endpoints produce an absent result. The snapshot must not retain transmission, arrival, or radio pointers. Communication caches must implement the const ``findCachedArrival()`` query without insertion or other mutation.
+
+``SignalTimeInd`` now carries the medium's transmission identifier. The receiver supplies this local indication when it accepts a transmission. Consumers can associate later frame delivery with the corresponding physical timing evidence. The identifier is local metadata; it adds no transmitted field.
+
+IEEE 802.11 signal extension
+---------------------------
+
+Custom IEEE 802.11 modes must implement ``getSignalExtensionTime()``. ``Ieee80211ModeBase`` supplies zero. ERP and 2.4 GHz HT modes supply their six-microsecond extension. HT duration now includes that extension, which can change airtime and simulation results. The transmission query derives the actual extension from its waveform, so consumers must not add the nominal value again.
+
+IEEE 802.11 RIFS permission
+--------------------------
+
+HT Operation now preserves RIFS Mode through management conversion and equality checks. VHT operation clears that permission. The change preserves the received permission for a consumer that checks RIFS relations; it adds no RIFS transmission scheduler.
+
+IEEE 802.11 Block Ack wire producers
+-----------------------------------
+
+The serializer decodes the complete four-bit Type field. Type 0 retains Basic compatibility; Type 2 selects Compressed. Other Types produce invalid input. Basic producers must keep the two low bits of their reserved field zero because those bits overlap Type. The serializer rejects inconsistent Basic objects. Compressed reserved fields write zero; received reserved values do not affect the decoded result.
+
+Compressed producers must supply Fragment Number zero and a 64-bit bitmap. A Compressed Block Ack header chunk contains 28 bytes before FCS. Both BAR profiles occupy 24 bytes with FCS. Keep the corresponding header chunk and trailer lengths consistent. The serializer rejects malformed wire fields before runtime processing.
+
+IEEE 802.11 reversible DATA preparation
+-------------------------------------
+
+Custom QoS data services must implement the reversible preparation operations in ``IOriginatorQosMacDataService``. ``prepareFrame()`` creates an owned preview and retains original MSDUs in their queue. ``isPreparationValid()`` checks current source identity, eligibility, lifetime, sequence state, and aggregation permission. ``commitPreparedFrame()`` transfers the same preview pointer only after successful validation. Failure must preserve the preview and all original queue frames. ``InProgressFrames::adoptPreparedFrame()`` accepts a committed frame without another extraction.
+
+IEEE 802.11 Compressed Block Ack extension points
+------------------------------------------------
+
+HCF defaults to ``mac.hcf.blockAckVariant = "compressed"``. Both endpoints must select the same profile. ADDBA does not negotiate this selector. A valid addressed BAR with the other profile stops the simulation before agreement updates. The diagnostic identifies the peer, TID, local profile, and received profile. Configure the same selectors before a new run.
+
+Compressed agreements require usable local HT support and directional peer HT state. An HT PHY mode alone does not supply that state. Without it, HCF retains Normal Ack DATA. Explicit ``blockAckVariant = "basic"`` retains historical Basic compatibility without that HT requirement. Compressed agreements suppress fragmentation for their peer and TID. Fragmentation outside those agreements retains its existing threshold.
+
+Compressed supports immediate Block Ack only. Keep ``delayedAckPolicySupported = false`` in both agreement policies. HCF rejects either true value during initialization when Compressed support is enabled. Basic retains its existing delayed-policy configuration contract.
+
+Custom QoS rate selectors must implement ``computeCompressedBlockAckReqMode(packet, header, startsTxop)``. HCF supplies the request's actual position in the TXOP. Select a legal non-HT mode from the applicable BSS or peer rates. A configured ``controlFrameBitrate`` must satisfy that rule. The existing Basic rate-selection methods remain available.
+
+Custom recipient ACK policies must implement ``computeCompressedBlockAckDurationField()``. This operation subtracts SIFS and the actual Compressed response airtime from the request reservation. Compressed Block Ack contains 32 bytes with FCS; Basic contains 152 bytes. Retain the existing Basic operation.
+
+Custom recipient data services must accept ``bool interframe = false`` in ``dataFrameReceived()``. The value states whether HCF established the required SIFS or RIFS relation. A false value supplies no interval evidence. Preserve that value through the recipient reorder path. Custom callers that omit it retain false.
+
+Custom frame sequences must supply HCF's resolved profile to ``QoSContext``. Its constructor retains Basic for callers that omit the profile. Custom agreement creation must set the recipient agreement's variant before use. An agreement must retain that variant throughout its lifetime.
+
+Custom frame sequences must preserve BAR arbitration before DATA preparation. The HCF preparation callback distinguishes the legacy path, accepted preparation, and admission refusal. Refusal must end the sequence without a DATA failure. Keep the selected DATA mode through RTS/CTS. Validate that decision without another adaptive rate query. Admit the complete exchange before source removal.
+
+The active Compressed path reserves only the current exchange under single protection. A later BAR can require fresh contention. Before DATA commitment, failed RTS updates QSRC and the contention window without a provisional sequence counter. QSRC is the short retry counter for an access category. QSRC rollover does not discard queued DATA. Original lifetimes bound repeated RTS attempts.
+
+Basic and outside-agreement fallback retain their existing preparation and recovery paths.
+
+IEEE 802.11 Basic Block Ack extension points
+------------------------------------------
+
+HCF previews the next eligible DATA before BAR selection. Preview must preserve queue ownership and the next sequence number. Custom QoS data services must implement ``IOriginatorQosMacDataService``. This contract extends ``IOriginatorMacDataService`` with ``peekEligibleFrame()``, ``getNextSequenceNumber()``, and ``hasAMsduAggregation()``.
+
+Basic single protection reserves only an already prepared successor. HCF ends the TXOP before further DATA extraction. A queued unit can change size or require agreement setup before transmission. Custom frame sequences must preserve this boundary when they use the same forecast.
+
+Custom QoS ACK policies must add ``IOriginatorBlockAckAgreementHandler *agreements`` to ``isBlockAckReqNeeded()`` and ``computeBlockAckReqParameters()``. The argument supplies current agreement windows. Policies must keep peer and TID identities separate. The supplied policy can request BAR without a DATA candidate.
+
+Callers must supply the agreement handler explicitly through both the interface and concrete policy types. An omitted argument fails compilation. Pass ``nullptr`` explicitly when no agreement handler is available; the supplied policy then requests no BAR. Custom overrides must also require the argument.
+
+Calls to ``QosAckHandler::processTransmittedBlockAckReq()`` must supply the originator agreement explicitly. The agreement restricts processing to the negotiated window. An explicit ``nullptr`` retains bitmap-only coverage without that restriction. Production HCF always supplies the active agreement.
+
+Custom ``InProgressFrames`` classes must add ``bool extract = true`` to their ``getPendingFrameFor()`` override. When ``extract`` is false, the method must return a borrowed candidate without extraction or sequence assignment. The default value preserves ordinary extraction. Custom candidate sources must also implement ``peekFrameToTransmit()`` without extraction.
+
TCP Algorithms
--------------
@@ -158,6 +225,51 @@ The protected ``getEarliestCompleteMsduOrAMsduIfExists()`` helper remains availa
No configuration change is necessary. Gap release, window movement, and BAR before DATA can change delivery times and fingerprints in affected Basic Block Ack scenarios. These repairs do not enable Compressed Block Ack.
+IEEE 802.11 Block Ack Agreement Lifecycle
+------------------------------------------
+
+HCF now defers new DATA extraction for a peer and TID during Block Ack setup or recovery. Already numbered DATA resolves before HCF creates ADDBA. The first DATA under an accepted agreement therefore retains the request sequence number. Both supported profiles use this lifecycle contract.
+
+The recipient response now determines the buffer size and inactivity timeout. The built-in recipient selects the smaller of 64 and its configured maximum. A request size of zero supplies no size guidance. The response copies the request dialog token. Both negotiated A-MSDU flags must permit aggregation under an active agreement.
+
+The originator starts ``mac.hcf.originatorBlockAckAgreementPolicy.addbaFailureTimeout`` after the request receives its ACK. Its default is 100 ms. The value must be positive and finite.
+
+If the response does not arrive, HCF sends DELBA before it releases surviving unsent DATA. DATA lifetime separately limits retained DATA. Failed DELBA disposes of the current deferred batch. Later DATA starts one fresh recovery exchange.
+
+At ordinary teardown, the originator discards every transmitted, unacknowledged MPDU for the selected agreement. This includes DATA whose lifetime remains valid when inactivity expires. It also discards unsent fragments through the highest transmitted allocation index, even if an earlier fragment received an ACK. This local index records allocation order before fragmentation. Later unsent DATA retains sequence continuity.
+
+For example, fragment 0 of sequence 4095 enters transmission before teardown. Teardown discards its unsent fragments and preserves the intact successor with sequence 0. The recipient delivers complete buffered units in cyclic order before it releases resources. It discards incomplete units. For example, complete units 101 and 102 can reach the upper process after teardown across absent unit 100.
+
+Custom originator code must call ``recordTransmittedData(allocationIndex)`` instead of ``markFirstDataTransmitted()`` or the former cyclic-sequence overload. The QoS data service assigns an increasing local index before fragmentation. All fragments retain this index in ``Ieee80211DataSequenceTag``. Custom QoS data services must supply the same metadata. The agreement exposes its highest transmitted index through ``getHighestTransmittedDataIndex()``, which replaces ``getHighestTransmittedSequence()``. HCF records this index when the transmitter accepts DATA.
+
+The index remains available after ACK processing removes a frame. Teardown compares allocation indices to remove unsent fragments of a partially transmitted unit. This comparison remains valid across sequence gaps and wraparound. For example, transmission of sequences 0 and 2048 updates the boundary correctly when intervening DATA receives allocation indices but never transmits. Retransmission of older DATA cannot move the boundary backward.
+
+Association reset instead discards all buffered units from the old association. Management calls ``IBlockAckAgreementLifecycle::resetPeerAgreements()`` with the old peer address before replacement. The MAC implements this contract and forwards the reset to HCF. Custom management implementations must supply this handoff for association replacement and loss. A rate-state notification alone cannot identify replacement with identical capabilities.
+
+With Block Ack support enabled, peer reset also discards queued and in-progress QoS DATA addressed to the old peer. This includes unsent Normal Ack DATA without an agreement. Accepted physical exchanges retain their context until safe completion. Reset preserves other peers' queued DATA and lifetime records.
+
+Custom MAC implementations that use these management modules must implement ``IBlockAckAgreementLifecycle``. The reset must complete both agreement roles' cleanup before it reports deletion. It must preserve an unrelated management exchange that already holds an accepted transmission.
+
+``blockAckAgreementDeleted`` now supplies a nonnull agreement after removal from lookup and completion of the required cleanup. The payload identifies the selected role through its agreement class. The payload remains valid only during the synchronous callback. Listeners must not retain or modify it. Association reset removes both roles before the first callback. Ordinary directional teardown preserves the opposite role.
+
+Custom QoS recipient services must return accepted, complete Actions from ``managementFrameReceived()``. HCF owns their agreement dispatch after duplicate checks. Services must implement directional retirement and peer cleanup through the recipient-service contract. Custom agreement policies must implement ``isProfileAllowed()`` with the supplied typed profile and usable capability inputs. Basic admission must preserve its historical non-HT compatibility.
+
+Custom agreement handlers must implement the new procedure and retirement operations in their handler contracts. The originator handler owns setup intent, request identity, response deadlines, teardown, and recovery state. Its ``noteEligibleDataTransmission(peer, tid)`` operation records ``WAITING_FOR_SUCCESSOR`` after an eligible DATA transmission. This phase permits ordinary DATA access and has no request or deadline. A later queued successor promotes the phase to setup intent before extraction. Peer reset clears the phase.
+
+The recipient handler owns its active agreements and pending teardown. Detachment removes an agreement from lookup and transfers it to the caller. The caller retains the object through its deletion callback. Const observations expose current state without a second agreement store.
+
+Custom implementations of ``IBlockAckAgreementHandlerCallback`` must implement ``scheduleInactivityTimer()`` without a deadline argument. Agreement handlers request recomputation after their state changes. HCF queries both roles and schedules their earliest absolute deadline. It cancels the timer when neither role has a finite deadline. Custom callers must remove the old deadline argument.
+
+Custom subclasses of ``OriginatorQosMacDataService`` must adapt the protected ``aMsduAggregateIfNeeded(queue, selected)`` helper. The selected packet is the first eligible packet in service order. An aggregate candidate list must start with that packet. The service validates every candidate before source removal and calls the aggregation policy once.
+
+Custom queues need ``inet::queueing::ISelectivePacketCollection`` only when a deferred next frame hides eligible work. HCF retains ordinary dequeue when the advertised next frame is eligible. It waits when every frame is deferred. It reports the missing capability before mutation when eligible work requires a bypass.
+
+The optional ``peekEligiblePacket(predicate)`` operation returns a borrowed packet in configured service order without mutation. ``extractPacket(packet)`` transfers that exact packet to its caller. Implementations must preserve their ordinary pull effects, including ownership, residence metadata, events, statistics, and applicable animation. Built-in simple queues, priority schedulers, and compound queues support this contract. Custom subclasses that change pull effects must adapt selected extraction. The optional contract supports complete packets only.
+
+Providers that cannot perform a selected operation must throw ``inet::queueing::UnsupportedPacketSelection``. This exception permits ordinary service and collection inspection without the optional capability. Genuine queue and predicate errors must retain their original type.
+
+External frame-sequence handlers must implement ``interruptFrameSequence()``. The handler owns the interruption outcome after recipient processing. It suppresses failure feedback when the transmitted DATA or management frame no longer belongs to the in-progress queue. Other exchanges retain ordinary failure feedback. Cleanup must destroy the exchange context before it deletes deferred frame copies. These repairs do not add MAC stop/start recovery or final-transfer teardown.
+
IEEE 802.11 Response Query Roles
--------------------------------
diff --git a/doc/src/users-guide/ch-80211.rst b/doc/src/users-guide/ch-80211.rst
index f4569882f96..fa35d64e63b 100644
--- a/doc/src/users-guide/ch-80211.rst
+++ b/doc/src/users-guide/ch-80211.rst
@@ -533,7 +533,7 @@ The first exchange proceeds even when its cost exceeds a positive TXOP limit. Th
A zero limit permits the initial exchange and its existing RTS/CTS and immediate response. Each later fragment or unrelated unit needs a fresh TXOP. Prepared fragments and A-MSDUs use the same complete-cost check under positive limits.
-``isBlockAckSupported = true`` disables complete-cost continuation admission, even without a Block Ack agreement. Multiple protection also retains its separate admission policy. Both paths use shared TXNAV. Basic Block Ack admission, BAR forecasts, zero-limit companion rules, and optional overrun exceptions remain outside this repair.
+With Block Ack support enabled, active Compressed agreements use the admission described in :ref:`ug:sec:80211:blockack`. Basic and traffic outside those agreements retain their existing admission policy. Those retained paths can exceed DATA exchange bounds. All paths use shared TXNAV. Basic Block Ack admission and BAR forecasts remain outside the Compressed correction.
All single protection paths classify group frames before the current-frame ACK equation. With Block Ack support disabled, the current one-frame group sequence advertises zero. With support enabled, its legacy forecast can reserve a queued successor that the sequence cannot transmit. This limit applies to group management and non-QoS DATA too.
@@ -607,13 +607,9 @@ The standard separately requires AIFSN of at least 1 for APs and at least 2 for
example, a non-AP station with AIFSN 1 meets the ownership condition but violates the standard's
minimum.
-Multiple protection does not check a complete exchange against the remaining budget.
-For example, its admission policy can start a 120 us exchange with only
-100 us left. The reference scope assumes whole-microsecond positive limits, integral initial PPDU
-airtime, and integral legacy RTS airtime. It adds no A-MPDU, Compressed Block Ack, new
-fragmentation, CTS-to-self, dual CTS, PIFS recovery, or TXOP sharing. It also adds no reverse
-direction, beamforming, S1G, multi-user, or general MAC lifecycle support. It does not repair
-general rate selection, recipient response timing, or TXOP overruns.
+Outside active Compressed agreements, multiple protection does not check complete exchange cost against the remaining budget. For example, that retained policy can start a 120 us exchange with only 100 us left. Active Compressed agreements use their separate complete-cost admission. The reference scope assumes whole-microsecond positive limits, integral initial PPDU airtime, and integral legacy RTS airtime.
+
+Multiple protection adds no A-MPDU, new fragmentation, CTS-to-self, dual CTS, PIFS recovery, or TXOP sharing. Reverse direction, beamforming, S1G, multi-user operation, and general MAC lifecycle support remain outside this implementation. General rate selection and recipient response timing retain their separate limits.
.. _ug:sec:80211:fragmentation:
@@ -700,16 +696,35 @@ station then sets up an agreement with its peer automatically (with ADDBA
frames) when it sends a QoS data frame shorter than
:par:`maxBlockAckPolicyFrameLength` (default 1000 bytes). After that, the
frames of that traffic class go out without an immediate acknowledgement,
-and the station sends a Block Ack Request after :par:`blockAckReqThreshold`
-(default 5) unacknowledged frames. Both parameters are on
-``mac.hcf.originatorAckPolicy``.
+and the station normally sends BAR after :par:`blockAckReqThreshold` unacknowledged frames. The default threshold is five. Both parameters are on ``mac.hcf.originatorAckPolicy``.
+
+The station applies the threshold separately to each peer and TID. It sends BAR below the threshold when the next eligible DATA belongs to another agreement or cannot fit the transmit window. The station also sends BAR when no eligible DATA remains. For example, a burst of four frames ends with BAR even with the default threshold of five. A negotiated buffer size of one requires BAR before the second sequence number.
+
+BAR can obtain channel access without a queued DATA frame. A later BAR within a TXOP must fit the applicable TXOP and TXNAV bounds with its response. If BAR cannot fit, the station retains that work for another contention.
+
+After the final exchange, the originator sends DELBA when its agreement has no queued DATA, in-progress DATA, or active BAR exchange. A zero inactivity timeout does not disable this transfer-completion rule. DATA that arrives after this boundary waits for DELBA completion. A later transfer can require another ADDBA exchange. If the last unresolved DATA expires, the originator uses the same teardown path. Ordinary recipient teardown releases complete units behind a receive gap before it releases agreement resources.
+
+The agreement policies, ``mac.hcf.originatorBlockAckAgreementPolicy`` and ``mac.hcf.recipientBlockAckAgreementPolicy``, set the buffer size. ``maximumAllowedBufferSize`` defaults to 64. Both negotiated ``aMsduSupported`` flags control A-MSDU use; each defaults to ``true``. ``blockAckTimeoutValue`` defaults to ``0s``, which disables the inactivity timeout. Transfer completion and DATA lifetime expiry retain their separate teardown rules.
+
+HCF defaults to ``mac.hcf.blockAckVariant = "compressed"`` for immediate Compressed Block Ack. Block Ack support itself remains disabled by default. The response contains a 64-bit bitmap and occupies 32 bytes with FCS. Each positive bit acknowledges one unfragmented MPDU. The recipient retains receipt bits after delivery, so a later BAR can still obtain those positive bits. Missing receipt evidence produces zero.
+
+Compressed agreements require usable local HT support and directional peer HT state. Infrastructure association supplies that peer state for eligible HT links and the supported ``ac`` path. An HT radio mode alone does not supply peer capabilities. Without usable state, DATA uses Normal Ack. Local non-HT operation produces a warning when Block Ack support is enabled. HT ad hoc links without peer state retain Normal Ack.
+
+Explicit ``blockAckVariant = "basic"`` selects historical Basic compatibility on both endpoints. Basic retains its 64 fragment bitmap entries and its 152-byte response with FCS. IEEE Std 802.11e-2005 introduced this format. IEEE Std 802.11-2024 reserves its Type 0 value. INET retains Basic for existing studies; this compatibility does not establish complete historical conformance.
+
+Both endpoints must select the same profile because ADDBA does not negotiate it. A valid addressed BAR with the other profile stops the simulation before agreement updates. The diagnostic identifies the peer, TID, and profiles. Configure matching profiles before a new run. Malformed frames follow input rejection instead of this configuration diagnostic.
+
+Compressed agreements suppress fragmentation for their peer and TID. The recipient rejects fragmented DATA under those agreements before receipt or reorder updates. Basic retains fragmentation. Outside a Compressed agreement, the configured fragmentation threshold retains its existing effect.
+
+Keep ``delayedAckPolicySupported = false`` in both agreement policies for Compressed support. HCF rejects true at initialization because the Compressed path supplies immediate responses only. Retained Basic delayed-policy configuration does not establish a complete delayed exchange implementation.
+
+The Compressed originator sends BAR with a non-HT PHY mode. Initial BAR uses the applicable BSS basic or mandatory rate rule. Later BAR uses the peer operational rate rule. A configured ``controlFrameBitrate`` must satisfy that rule. The response selector uses the actual 32-byte response length when a legal alternate rate is available. These rules retain separate Basic, ACK, and CTS contracts.
+
+Active Compressed agreements check the complete DATA exchange before source commitment. If an aggregate cannot qualify, HCF evaluates its first ordinary MSDU with the same selected DATA mode. If neither form qualifies, HCF ends the sequence without queue removal, sequence assignment, or deadline replacement. RTS/CTS retains the selected mode. Expiry or capability changes before DATA cancel invalid preparation.
+
+An initial unaggregated MSDU can use the applicable TXOP overrun exception. A same-size single-MPDU retransmission can also use its exception. That retransmission can carry an A-MSDU. Each exception permits at most one Data or Management frame in that TXOP. A new A-MSDU must fit a positive limit. A zero limit permits one DATA PPDU with one MSDU or A-MSDU, plus its required protection and response.
-The agreement policies, ``mac.hcf.originatorBlockAckAgreementPolicy`` and
-``mac.hcf.recipientBlockAckAgreementPolicy``, set the buffer size
-(:par:`maximumAllowedBufferSize`, default 64), the A-MSDU support
-(:par:`aMsduSupported`, default ``true``) and the inactivity timeout
-(:par:`blockAckTimeoutValue`, default ``0s``, which means no timeout). Only
-the basic Block Ack variant is modeled.
+Single protection reserves only the current Compressed exchange. A later BAR can require fresh contention. This policy can change airtime and delivery times. Before DATA commitment, failed RTS updates QSRC and the contention window without a provisional sequence counter. QSRC is the short retry counter for an access category. Original MSDU lifetimes bound repeated attempts; QSRC rollover does not discard them.
.. code-block:: ini
@@ -1026,9 +1041,9 @@ The model does not contain:
- PCF and HCCA, the polled channel access methods. The module
:ned:`Hcca` exists as a placeholder.
-- A-MPDU aggregation, the MSDU lifetime limit, and self-CTS protection.
+- A-MPDU aggregation and self-CTS protection.
-- Compressed and multi-TID block acknowledgement.
+- Multi-TID block acknowledgement and delayed Compressed Block Ack.
- Operational HT widths above 20 MHz, and a primary/secondary channel relationship for wider VHT modes.
diff --git a/plan/done/ieee80211-compressed-blockack-s3b-execution.md b/plan/done/ieee80211-compressed-blockack-s3b-execution.md
new file mode 100644
index 00000000000..b8076b55859
--- /dev/null
+++ b/plan/done/ieee80211-compressed-blockack-s3b-execution.md
@@ -0,0 +1,279 @@
+# S3b execution record
+
+## Completed S3b
+
+S3b is complete on branch `work/compressed-blockack-s3b` in [PR #1343](https://github.com/inet-framework/inet/pull/1343). The current source revision is `8646a9e6808ea8d21ac28bfc1271537469225fe9`, above PR base `bba1db4c8180a592894e0ab2a2dc056d9cb08082`. The broad verification below covers `8ba7c9e47bc3ded924cfa3917c8b899baccf789d`; a separate record covers the later allocation-index repair. Branch completion does not assert a merge into `master`. S4, A5, and production Compressed exchanges remain outside this completed stage.
+
+The six implementation commits supply optional selected extraction, lifecycle integration, first-DATA collision retirement, frame-sequence interruption ownership, fragment retirement, and allocation-index comparisons. A separate documentation commit records stage completion. The [S3b summary](ieee80211-compressed-blockack-s3b-summary.md) explains the resulting behavior. The [migration guide](../../doc/src/migration-guide/index.rst) defines custom-module changes. Basic remains the default; HCF rejects Compressed selection during initialization.
+
+The latest repair retains the highest transmitted allocation index in the agreement. The QoS data service assigns this local index before fragmentation; all fragments retain it in `Ieee80211DataSequenceTag`. Ordinary teardown discards unsent fragments through that index, even after ACK processing removes an earlier fragment. Older retransmissions cannot move the index backward, and wire sequence numbers remain unchanged.
+
+The fragment fixture transmits DATA with sequence 0. It allocates intervening DATA through the production data service and deletes that DATA without transmission. Fragment 0 of sequence 2048 then transmits. The index advances correctly; teardown removes unsent siblings of sequence 2048 and preserves later intact DATA. This case tests an allocation gap; it does not test lifetime expiry of the intervening DATA.
+
+| Verification at source revision `8ba7c9e47bc3ded924cfa3917c8b899baccf789d` | Result | Saved local artifact |
+| --- | --- | --- |
+| Debug compilation | Pass | `/tmp/pr1343-publish-debug.log` |
+| Release compilation | Pass | `/tmp/pr1343-publish-release.log` |
+| Selected debug module fixtures | 26 pass | `/tmp/pr1343-publish-modules.log` |
+| Functional fingerprint cases, without graphical `tyf` ingredients | 19 pass | `/tmp/pr1343-publish-fingerprints.log` |
+| Fragment reproduction against `53edefe95a6cc9671ef8d4864c913c2a5669306d` | Fails at the retirement assertion | `/tmp/pr1343-fragment-before.err` |
+| Fragment fixture after the repair | Four scenarios pass | `/tmp/pr1343-fragment-after.log` |
+| Commit, classification, include, parameter, and PR-description checks | Pass | `/tmp/pr1343-publish-mechanical.log` |
+| Source-seal check | Pass | `/tmp/pr1343-publish-seals.log` |
+
+The commands for this earlier verification run from the repository root after environment setup:
+
+```sh
+make MODE=debug -j12
+make MODE=release -j12
+inet_run_module_tests -m debug -f '(PacketSelectiveExtraction_1|Ieee80211BlockAck.*_1|Ieee80211EdcaDataLifetime.*|Ieee80211HcfInternalCollision_1)\.test$'
+```
+
+The functional fingerprint command runs from `tests/fingerprint`:
+
+```sh
+./fingerprinttest -d -F tyf -t 4 -m 'showcases/wireless/(blockack|aggregation|fragmentation|qos|txop)/|examples/wireless/qos/.*MacQos|examples/adhoc/qos/.*MacQos' examples.csv showcases.csv
+```
+
+Seven approved functional fingerprint rows belong to the lifecycle implementation. Later repairs retain those values and add no baseline change. Twelve controls retain their functional expectations. Graphical expectations remain unchanged and outside this functional result. The earlier verification at `667ae51dc9fbfbfac9ea68122bf65bc4f2d3e1df` includes 37 module fixtures, six unit fixtures, and 13 Basic demonstrations. Its protocol selection records four passes and four declared expected failures. Those earlier results do not establish final-head coverage for the same selections.
+
+At `8ba7c9e47b`, whole-project architecture and naming checks retain the findings present at the PR base. The interface check retains 15 findings and nine review notes; the two new interfaces pass. The changed source paths are unsealed. Raw `/tmp` artifacts are local evidence; they are not part of the published repository.
+
+### Allocation-index repair at `ea20a2880e`
+
+The series now places the allocation-index repair before the final plan commit. The repair maps from `ea20a2880e0ca607e680def1a875c6702c4ef249` to `8646a9e6808ea8d21ac28bfc1271537469225fe9` without source or test changes. The results below retain their original revision. The history comparison establishes content equivalence; it is not a new behavioral test.
+
+Commit `ea20a2880e0ca607e680def1a875c6702c4ef249` replaces cyclic comparisons with allocation indices. It adds a MSG definition for local metadata, without a wire-format change. Custom QoS data services must supply the tag, as the migration guide requires.
+
+| Recorded verification for the allocation-index repair | Result | Saved local artifact |
+| --- | --- | --- |
+| Debug compilation | Pass | `/tmp/s3b-push-debug.log` |
+| Release compilation | Pass | `/tmp/s3b-push-release.log` |
+| Fragment-retirement, lifecycle, association-reset, and DATA-selection fixtures in debug mode | Four fixtures pass | `/tmp/s3b-push-tests.log` |
+
+The commit records 50 successful simulation runs across these four fixtures. The fragment fixture supplies 20 runs with these parameters:
+
+- The initial sequence is 0 or 4095.
+- The allocation distance is 0, 2047, 2048, 2049, or 4096.
+- Teardown occurs during transmission or after the first fragment receives its Normal Ack.
+
+The fixture supplies accepted agreements and ACKs; production code performs fragmentation, expiry, retirement, and transmission. Later intact DATA and unrelated-peer DATA remain controls.
+
+These archived results do not extend the earlier 26-fixture or 19-fingerprint results to this revision. Release-mode behavioral tests, sanitizers, and other platforms remain unverified at this revision. The later audit checks the repair and documentation claims through source inspection; it is not a full independent correctness audit.
+
+This document correction changes no production source or recorded expectation. It repeats no build or simulation test. The checkout's existing `AGENTS.md`, four pending plans, and planning history remain under their existing owners.
+
+## Earlier execution snapshot
+
+The sections below retain the earlier implementation record, before publication and later repairs. Their candidate numbers and source pins refer to that earlier work. Their statements about absent commits, release checks, or baseline approval apply only to that snapshot. The completed-stage results above supersede those status statements.
+
+The original implementation parent is `41eee1d61978d62cfaf9d0d9c242b9fee590c20e`. The branch is `work/compressed-blockack-s3b`. The working copies of replacement plan revision 39 and lifecycle plan revision 29 define that scope. The user authorized S3b execution on 15 October 2026.
+
+## Implementation contract
+
+[HCF](../../src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc) owns agreement coordination and frame exchange order. The [originator](../../src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.cc) and [recipient](../../src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.cc) handlers own their agreements and transient procedures. The MIB owns capability state. Management supplies an explicit peer reset before association replacement. The [recipient data service](../../src/inet/linklayer/ieee80211/mac/recipient/RecipientQosMacDataService.cc) owns duplicate checks and delivery. Queue components own extraction effects.
+
+The implementation retains Basic compatibility without an HT admission requirement. HCF continues to reject Compressed selection during initialization. Isolated policies verify Compressed admission with explicit capability inputs. S3b does not implement S4 BAR arbitration, A5 transfer completion, or Compressed exchanges.
+
+1. A1 returns complete accepted Block Ack Actions from the recipient data service. HCF dispatches each Action once after duplicate checks. A correlated Action resolves its procedure before obsolete exchange failure.
+2. A2 passes the selected profile to admission policies. The handlers correlate peer, TID, and dialog token. Accepted response values determine both agreement owners' parameters. The data service enforces both A-MSDU flags.
+3. A3a detaches the selected agreement before cleanup and deletion notification. Ordinary recipient teardown delivers complete buffered units in cyclic order. It discards incomplete units. HCF uses one absolute inactivity deadline across both roles.
+4. A3b clears both roles and obsolete peer work before deletion notifications. Management supplies the old peer identity before association replacement. The MIB keeps its boolean notification and coherent committed state.
+5. A3c routes Action disposal to its owner. HCF invalidates obsolete queued and in-progress Actions. An accepted transmission retains its context until safe completion. Valid DATA or BAR without an agreement causes recipient-role DELBA.
+6. A4 defers extraction during setup and recovery. The data service supplies a non-consuming sequence query. Optional selected extraction preserves queue ownership, residence metadata, and pull events. Contention uses the same eligibility condition.
+
+HCF obtains both roles' earliest active absolute deadlines after each agreement mutation. It schedules their minimum. It cancels the timer when neither role has a finite deadline. Expiration detaches every overdue agreement before the next deadline calculation.
+
+The delayed-successor interpretation preserves the existing setup trigger across separate DATA arrivals. A completed eligible Normal Ack transmission supplies the first condition. A later unexpired unnumbered successor supplies the second condition. The handler retains the first condition in its procedure map without an Action or timer. HCF promotes that state to setup intent before successor extraction. A single frame without a successor therefore causes no ADDBA.
+
+The implementation checks peer, TID, direction, modulo-4096 sequence order, and absolute versus relative time at each changed boundary. Duplicate Actions retain immediate ACK behavior. Invalid responses cannot resolve setup. Queue disposal, retry failure, response timeout, inactivity, received DELBA, and association reset have separate terminal paths. No MAC stop/start recovery claim applies.
+
+## Source assessment
+
+The parent recipient data service deletes Actions before it returns its accepted frames. HCF nevertheless dispatches the original management header after that call. Thus, duplicate detection does not control agreement dispatch. During a response wait, HCF passes an addressed Action to the active receive step instead of its agreement handler.
+
+The parent policies accept unchecked setup parameters. The recipient stores request size and timeout before it builds a response. The response omits the request dialog token. The originator policy asserts that an agreement exists but performs no response correlation.
+
+Both handlers supply absolute inactivity deadlines. HCF passes those deadlines to `rescheduleAfter`. The handlers select expiration with equality instead of an overdue comparison. Recipient DATA reception does not refresh the stored deadline. Received recipient DELBA uses the local receiver address for removal.
+
+The parent collision controller tests frame counts. This test does not establish agreement eligibility. A4 requires a production reproduction or a cancellation trace before a shared arbitration change.
+
+## Verification status
+
+The separate lifecycle review accepted the A1–A4 scope against the pinned parent. It required the shared inactivity minimum and explicit peer reset before equal-state reassociation. The review supplied source evidence; it supplied no implementation or runtime verdict.
+
+The parent debug build passed with `make MODE=debug -j12`. The command ran from the repository root and returned zero. Its log is `/tmp/s3b-parent-build.log`.
+
+The parent reproduction command was `inet_run_module_tests -m debug -f 'Ieee80211BlockAck(ActionDelivery|ActionDispatch|Negotiation)_1.test'`. Its log is `/tmp/s3b-parent-reproductions.log`. The Action delivery fixture failed its returned-frame count assertion. The negotiation fixture failed its dialog-token assertion. Both failures reached the production component after test initialization.
+
+The HCF dispatch fixture initially lacked a received-mode indication and a valid request sequence number. Those fixture setup errors supplied no product evidence. After correction, `inet_run_module_tests -m debug -f 'Ieee80211BlockAckActionDispatch_1.test'` reproduced duplicate dispatch. The fixture required one dispatch after an original request and its MAC retry. The parent dispatched both. Its log is `/tmp/s3b-parent-dispatch.log`.
+
+The generated dispatch executable also ran independently with `-u Cmdenv -r 1` from `tests/module/work/Ieee80211BlockAckActionDispatch_1`. At 1 ms, the fixture supplied a 5 ms absolute deadline. The parent scheduled 6 ms. The arrival-time assertion failed with exit status one. Its log is `/tmp/s3b-parent-inactivity.log`.
+
+New fixtures cover directional cleanup, association reset, response-wait order, deferred extraction accounting, and contention. Existing Basic recipient, selector, header, queue-disposal, lifetime, capability, and exchange fixtures remain controls. The candidate results below identify their commands and evidence limits.
+
+The parent control command selected nine module fixtures and passed all nine. Its command was `inet_run_module_tests -m debug -f 'Ieee80211(BlockAck(Reordering|Variant)|HcfHeader|RecipientNav|HcfMultipleProtection_2|SingleProtectionGroup|MgmtApHcfQueueDrop|MgmtRatePublication|MgmtStateCommit|EdcaDataLifetimeArbitration)_.*test'`. Its log is `/tmp/s3b-parent-controls.log`. This expression did not select the multiple-protection fixture because that alternative already contained its suffix.
+
+A second command selected that fixture and the Basic exchange control: `inet_run_module_tests -m debug -f 'Ieee80211(HcfMultipleProtection_2|BasicCompatibility_1)\.test'`. Both fixtures passed. Its log is `/tmp/s3b-parent-basic-controls.log`. Together, these results cover eleven fixtures, including all eight S1 recipient scenarios.
+
+The parent library hash was `ed6af79596a80e6192f4ca1cb8bf91f8a5f682d508eef58e95603796aa384a6e`. The lifecycle and response-wait fixtures also reproduced nine parent failures with that library. Directional teardown emitted a null deletion payload. Absent-agreement DATA reached upper delivery. Absent-agreement BAR caused no DELBA. An incoming Action received no immediate ACK during an active request ACK wait. `/tmp/s3b-regression-parent/` retains the exact executables, inputs, hashes, and individual run logs. Later fixture changes require fresh compilation before candidate verification.
+
+The selected protocol command was `inet_run_protocol_tests -p inet -m debug -w '^tests/protocol/wifi$' -f '(N_BlockAck|N_CompressedBa|N_Amsdu|Ac_BlockAck|WifiQosDelayedBa|Ac_MultiTidBa|N_Ampdu|Ac_Ampdu)\.test$'`. It returned zero with four passes and four declared expected failures. Its log is `/tmp/s3b-parent-protocol.log`. This run used the unchanged parent library while source implementation began. The protocol support library includes no changed agreement or HCF declaration. A new unused timeout parameter declaration was present; this result does not establish candidate behavior.
+
+The delayed-policy fixture failed its immediate-ACK assertion after BAR. The Multi-TID fixture missed its response deadline. Both A-MPDU fixtures missed their subframe-header observation. `/tmp/s3b-parent-reproductions/protocol-failure-causes.json` preserves these first failure causes. These outcomes remain unresolved protocol obligations.
+
+## Additional production reproductions
+
+Candidate 6 exposed two A4 failures before the final contention change. A real VO transmission held an ADDBA Request in its queue. A later VO arrival evicted that Request. HCF cleared setup, but surviving BE DATA obtained no new contention request. Action-disposal case 6 reproduced this failure without another BE arrival. `/tmp/s3b-actiondisposal-build6-reproduction/` retains the evidence.
+
+A separate fixture expired a VI agreement at 33 microseconds. Its queued DATA became ineligible before coincident VI and BE grants at 34 microseconds. VO had its next grant at 70 microseconds. The controller still counted VI and charged BE a collision retry. With retry limit one, BE discarded its valid DATA. `/tmp/s3b-deferred-collision-before-isolated.log` records this isolated failure.
+
+The first contention trace also contained a legitimate VO grant at 34 microseconds. That trace supplies no evidence for the shared correction. The corrected fixture changes only VO AIFSN to six. Its eligible-VI control retains the real collision and retry disposal. Both corrected cases use seed zero and the same binary and library.
+
+HCF now supplies each EDCAF with a query for current frame eligibility. The collision controller uses this query before it selects contenders. The query performs no expiration, extraction, or protocol mutation. HCF retains the existing Basic readiness condition; S4 still owns BAR-only work. After an Action terminal outcome, HCF requests contention for every category with eligible work.
+
+Candidate 7 passes both corrected contention cases. The deferred-VI case transmits BE DATA without a collision or retry disposal. The eligible-VI control retains its collision and BE retry disposal. Late Request eviction and successful DELBA acknowledgment also release BE DATA without another BE arrival. `/tmp/s3b-deferral-selection-collision-final.log` and `/tmp/s3b-regression-build7-pass/` retain these results.
+
+Candidate 7 also reproduces an Action recovery-state defect. An owned ADDBA Request causes a real RTS transmission under a low RTS threshold. The missing CTS charges its short retry counter before any Action transmission. Peer reset then leaves that counter at one because Action-attempt state remains false. `/tmp/s3b-actiondisposal-build7-rts-reproduction/` retains the exact failure. Cleanup must distinguish an untouched Action from an Action whose RTS exchange owns retry state.
+
+The retained `MacQosWithBlockAck` demonstration exposes a separate setup trigger defect. A-MSDU aggregation drains all queued DATA for each AP transmission. The next same-TID arrival occurs after that transmission, so the immediate successor check never starts AP setup. Candidate 7 therefore supplies only the client-to-AP Basic link. The required AP-to-server link remains absent. The delayed-successor state addresses this condition without an empty setup request or changed traffic.
+
+## Controlled setup measurements
+
+The deferral fixture compares response timeouts of 100 ms and 250 ms under equal traffic, rates, seed, and response queue residence. It holds the real recipient response in its queue for approximately 150 ms. It does not inject a response directly. Both cases use three DATA packets and seed zero. DATA uses 6 Mbps; Actions use 24 Mbps.
+
+| Observed interval or count | 100 ms timeout | 250 ms timeout |
+| --- | --- | --- |
+| Request queue admission to PHY start | 34 microseconds | 34 microseconds |
+| Request ACK reception | 0.000684020016 s | 0.000684020016 s |
+| Request ACK to accepted response | No accepted response | 150.070003336 ms |
+| Response queue residence | 100.080006672 ms before invalidation | 150.078003336 ms before transmission |
+| DELBA queue admission to ACK completion | 80.006672 microseconds | No DELBA |
+| Surviving DATA PHY start | 0.100780026688 s | 0.150832023352 s |
+| Recipient MAC receives surviving DATA | 0.100980030024 s | 0.151032026688 s |
+| Transmitted management frames | One Request and one DELBA | One Request and one Response |
+| Management airtime | 72 microseconds | 72 microseconds |
+| ACK airtime for management exchanges | 56 microseconds | 56 microseconds |
+
+The 100 ms deadline starts recovery before the queued response becomes eligible. The recipient invalidates that response when DELBA arrives. DELBA acknowledgment releases surviving DATA under Normal Ack. The 250 ms deadline permits the delayed response and releases DATA under Block Ack. That final DATA has no BAR within the fixture, so MAC reception does not establish upper delivery.
+
+`/tmp/s3b-build11-timing-100ms.log` and `/tmp/s3b-build11-timing-250ms.log` contain the exact events and PHY durations. `/tmp/s3b-build11-setup-timing.json` preserves source lines and event numbers. The fixture does not independently timestamp the transition into setup deferral. These observations establish timeout order and separate exchange costs; they establish no delivery guarantee or preferred timeout.
+
+Application measurements use the unchanged Block Ack showcase, General configuration, run zero, seed zero, and a fixed 0.5-second interval. The source sends 5,000 application packets of 700 bytes at 0.1 ms intervals. The debug parent delivers 330 packets, or 231,000 useful bytes. Final debug candidate 11 delivers 324 packets, or 226,800 useful bytes. Application goodput is therefore 3.696 Mbps and 3.6288 Mbps, respectively.
+
+Both application runs use `inet --debug -u Cmdenv -c General -r 0 --seed-set=0 --sim-time-limit=0.5s --record-vector-results=false --record-scalar-results=true --cmdenv-express-mode=true`. Their result directories are `/tmp/s3b-metric-parent-debug` and `/tmp/s3b-metric-candidate-11`. `opp_scavetool export -F CSV-R` exports both scalar files into `/tmp/s3b-metric-results-final.csv`. Useful bytes come from the destination application's `packetReceived:sum(packetBytes)` scalar. Earlier runs without an explicit mode supply no matched-mode comparison.
+
+The comparison parent checkout is `9b4b22258fff87401943d5046e0d18c18b70a558`. Its source and feature declarations equal the implementation parent. Its ignored build profile differs in unrelated optional features. This one fixed-seed comparison establishes only the observed application counts. It establishes no general performance improvement, optional-retry benefit, or agreement-reuse benefit. S4 retains transfer-completion and idle-gap comparisons.
+
+## Final source and focused verification
+
+Final candidate 11 contains A1, A2, A3a, A3b, A3c, and A4. HCF retains Basic as the default and rejects Compressed during initialization. The source review found no remaining correctness defect or unresolved architecture exception. No commit, publication, recorded baseline change, or seal change forms part of this execution.
+
+The final debug build command is `make MODE=debug -j12`. It returns zero; `/tmp/s3b-candidate-build-11.log` records the build. The library SHA256 is `f1ec379bd582e9c84b87e548059068231f9381d0dbd67bc6c4908f9e174e4608`. `/tmp/s3b-final-source-manifest.json` identifies all 72 changed source, test, and migration files, including untracked headers. Its parent remains `41eee1d61978d62cfaf9d0d9c242b9fee590c20e`.
+
+The final implementation separates Action transmission from ownership of RTS retry state. An RTS failure records ownership of the short retry counter. It does not mark the Action as transmitted. Invalidation and every terminal Action outcome clear that owned counter. A separate control preserves another peer's counter when an untouched Action uses the same Sequence Control field. The existing shared recovery map still uses that field without the peer address; this change does not redesign that map.
+
+The delayed-successor cases now defer later individual DATA and later A-MSDU batches before extraction. Both negotiate starting sequence number one and transmit first DATA with number one. The single-frame controls send no ADDBA. Real AP reassociation removes the waiting state under both legacy and HT configurations. A lone frame in the new association cannot inherit the old trigger.
+
+| Final debug selection | Result | Evidence |
+| --- | --- | --- |
+| Related module fixtures and existing controls | 29 fixtures pass | `/tmp/s3b-final-modules-6.log` |
+| Lifecycle, response wait, association reset, Action disposal, retired DATA | Five fixtures; 50 scenarios pass | `/tmp/s3b-regression-build11-runner.log`, `/tmp/s3b-regression-build11-pass/` |
+| Deferred selection, setup, lifetime, collision controls | Three fixtures; 18 scenarios pass | `/tmp/s3b-build11-deferral-selection-collision.log`, `/tmp/s3b-build11-*` |
+| Related unit fixtures | Six fixtures pass | `/tmp/s3b-final-unit-4.log` |
+| Retained Basic demonstrations | 13 cases pass | `/tmp/s3b-final-demonstrations-11/evidence.json` |
+| Selected protocol requirements | Four pass; four declared expected failures | `/tmp/s3b-final-protocol-4.log` |
+
+The module selections total 37 distinct fixtures. The lifecycle and deferral snapshots retain generated code, configuration, executables, output, and hashes. Their simulation outputs contain no runtime warnings, errors, or undisposed objects. Runner notices about optional Python packages do not describe simulation failures. All cases use debug mode; deterministic new scenarios use seed zero.
+
+The [migration guide](../../doc/src/migration-guide/index.rst) explains the changed custom-module contracts. The [selected-extraction contract](../../src/inet/queueing/contract/ISelectivePacketCollection.h) defines queue ownership and pull effects. The [Action disposal fixture](../../tests/module/Ieee80211BlockAckActionDisposal_1.test) covers queue loss and RTS cleanup. The [deferral fixture](../../tests/module/Ieee80211BlockAckDeferral_1.test) covers timeout order and delayed successors. The [collision fixture](../../tests/module/Ieee80211BlockAckDeferredCollision_1.test) separates deferred contention from a genuine collision.
+
+The exact root module selector is:
+
+```sh
+inet_run_module_tests -m debug -f '(Ieee80211BlockAck(ActionDelivery|ActionDispatch|Admission|Negotiation)_1|PacketSelectiveExtraction_1|Ieee80211(BasicCompatibility_1|BlockAckVariant_1|BlockAckReordering_1|HcfHeader_1|HcfMultipleProtection_[12]|SingleProtectionGroup_1|RecipientNav_1|PreparedCancellation_1|Txnav_1|HcfSingleProtection_1|VhtHtSupport_1|MgmtApHcfQueueDrop_1|MgmtRatePublication_1|MgmtStateCommit_1|EdcaDataLifetime.*))\.test$'
+```
+
+The additional module and unit selectors are:
+
+```sh
+inet_run_module_tests -m debug -f 'Ieee80211BlockAck(Lifecycle|ResponseWait|AssociationReset|ActionDisposal|RetiredData)_1.test'
+inet_run_module_tests -m debug -f 'Ieee80211BlockAck(DataSelection|Deferral|DeferredCollision)_1\.test'
+inet_run_unit_tests -m debug -f 'Ieee80211(HtModeSet_1|VhtHtCapabilities_1|ResponseModeSelection_1|MibPeerTeardown_1|QosDuplicateRemoval_1|BlockAckActionWire_1)\.test$'
+```
+
+All category commands run from the repository root. The Basic observer command is `python3 tests/module/basic-compatibility-evidence.py --output /tmp/s3b-final-demonstrations-11`. It retains the original topologies, traffic, aggregation settings, fragmentation cases, parameter studies, and Basic assertions. The AP-to-server link now supplies real setup, DATA, BAR, and positive receipt bits. This result closes the two-hop regression without a weaker assertion.
+
+The final protocol command repeats the eight-case parent selector exactly. Its runner returns zero with four expected failures. Delayed-policy verification now fails at step one because admission refuses delayed policy. Its parent first failed at the later immediate-ACK requirement. Multi-TID still misses step two; both A-MPDU cases still miss the step-zero subframe-header requirement.
+
+`/tmp/s3b-final-protocol-failure-causes.json` preserves those first failure causes. Their expected-failure markers remain unchanged. These results establish no delayed, Multi-TID, A-MPDU, or Compressed support. The passing `N_CompressedBa` fixture still exercises the explicit Basic compatibility path in this stage.
+
+Scoped architecture checks pass for `src/inet/linklayer/ieee80211` and `src/inet/queueing`. Their logs are `/tmp/s3b-final-architecture-wifi.log` and `/tmp/s3b-final-architecture-queue.log`. `check-source-seals.sh --diff` passes; `/tmp/s3b-final-source-seals.log` records the result. Every changed source target is unsealed. `git diff --check` also passes for the owned changes.
+
+The independent gate comparison uses the actual implementation parent. Wi-Fi naming retains the same 32 findings; queue naming retains the same one finding. Wi-Fi interfaces retain the two existing findings under `AV-CONTRACT-02`; queue interfaces pass. New compliant interfaces increase those counts from 48 to 49 and from 24 to 25. `/tmp/s3b-review-{parent,candidate}-{naming,interfaces}-{0,1}.log` retains both sides; zero denotes Wi-Fi and one denotes queueing. No new mechanical finding or exception-ledger change is required.
+
+The C++ checker could not execute its analysis for either subtree. Both `check-cpp.sh` calls return two because this checkout has no `compile_commands.json`. `/tmp/s3b-final-cpp-wifi.log` and `/tmp/s3b-final-cpp-queue.log` retain that prerequisite error. These calls supply no clang-tidy verdict. This execution supplies debug compilation and source review; release compilation and the full pre-push gates remain outside this unpublished work.
+
+## Queue traversal cost
+
+Candidate 9 exceeded the CPU limit in both ten-second ad hoc fingerprint cases. A bounded Block Ack-disabled comparison reached the same event 104692 at 0.8 seconds. The parent used 3.56 CPU seconds; candidate 9 used 7.56 CPU seconds. Source inspection identified a new membership check that continued after it found each aggregation candidate.
+
+An external counter measured indexed queue reads without a repository edit. The captured production stack reached that membership check through the normal QoS extraction path. Each indexed read starts at the linked-list head. The full scan therefore repeated increasingly long walks for every candidate. This cost also affected traffic with Block Ack disabled.
+
+| Same-event profile | Parent | Candidate 9 | Candidate 10 |
+| --- | --- | --- | --- |
+| Indexed queue reads | 861 | 861,869 | 2,605 |
+| Sum of requested indices | 1,285 | 775,865,982 | 2,142 |
+| Largest requested index | 3 | 3,114 | 3 |
+
+Candidate 10 stops each membership search at its first match. The service still checks every candidate's presence, uniqueness, eligibility, and A-MSDU permission before any source removal. The repair adds no state or interface. The final profile retains event 104692 and removes high-index traversal from this run. Its plain run uses 4.24 CPU seconds; concurrent host load limits interpretation of that timing.
+
+`/tmp/s3b-baoff-profile-parent.log`, `/tmp/s3b-baoff-profile-candidate.log`, and `/tmp/s3b-baoff-profile-candidate10.log` retain the profiles. `/tmp/s3b-queue-profile.cc` supplies the external counter. The source review accepts the bounded early-break correction. Candidate 10 passed its focused checks after that correction. Candidate 11 retains the same early break.
+
+The Block Ack-enabled profile then found another repeated indexed scan in HCF's DATA existence query. At 0.8 seconds and event 102123, candidate 10 made 7,894,493 indexed reads. Candidate 11 makes 12,236 reads at the same event. Time inside high-index traversal falls from 14.37 seconds to 0.061 seconds. `/tmp/s3b-baon-profile-candidate10.log` and `/tmp/s3b-baon-profile-candidate11.log` retain these profiles.
+
+HCF now uses the optional predicate query to find unexpired DATA for a peer and TID. PacketQueue traverses its linked queue with an iterator. A composite reports `UnsupportedPacketSelection` when its provider cannot supply that query. HCF catches only that outcome and retains its collection fallback. Unrelated queue or predicate errors still propagate. This adds no capability cache or protocol state.
+
+The data service first checks the ordinary advertised head at both selection points. An eligible head therefore needs no selected-extraction capability. If all frames defer, the service waits without mutation. If a deferred head hides eligible work, the service requires selected extraction before it changes ownership or sequence state. The three new composite cases verify these distinctions through a provider without the optional interface.
+
+## Accepted exchanges across reset
+
+The final reset checks also repair physical statistics and RTS-protected identity. Candidate 10 omits EDCAF sent or received observations after logical retirement, although accepted DATA and its ACK complete physically. The final HCF emits each physical observation once before the retired-state guard. That guard still suppresses obsolete acknowledgment, recovery, agreement, and rate effects. The three DATA completion cases verify one sent observation and one received ACK observation.
+
+A separate reproduction resets the peer after RTS acceptance and before DATA submission. The old DATA and its lifetime leave active ownership. After CTS, candidate 10 nevertheless accepts that retired DATA because it checks no active-frame membership. Its management sequences instead select again, which can produce null or another peer's frame. `/tmp/s3b-retired-rts-build10-reproduction/` retains six clean failure cases.
+
+Candidate 11 retains the original protected management pointer after CTS, as the DATA sequence already does. HCF refuses DATA or management submission when that packet no longer belongs to active in-progress work. The accepted RTS exchange can finish safely, but it cannot authorize a retired packet or substitute another peer's packet. All six reset-during-RTS cases pass with no post-reset protected submission. They also preserve the other peer's DATA lifetime.
+
+The independent review closes every reported source finding in candidate 11. Its general checklist reports 20 passes and 14 inapplicable items. Its WLAN checklist reports 12 passes and one inapplicable item. Neither checklist retains a flag or question. The review adds no exception-ledger or seal change.
+
+## Final fingerprint comparison
+
+All 19 final debug cases complete without a CPU-limit error. The repository expectations yield one pass and 18 mismatches. The actual parent yields four passes and 15 graphical-only mismatches. Twelve final cases match every calculated parent ingredient. Seven Basic cases change functional ingredients; the table below identifies them.
+
+| Case | Calculated `tplx` | Calculated `~tNl` | Calculated `~tND` | Calculated `tyf` |
+| --- | --- | --- | --- | --- |
+| Ad hoc `MacQos`, run 1 | `5fbb-9fd9` | `c0a4-97f6` | `2373-df01` | `49b6-dbab` |
+| Infrastructure `MacQosWithBlockAck`, run 0 | `f8a4-9474` | `1cba-cccc` | `538e-67df` | `69e0-5af3` |
+| Block Ack `Fragmentation`, run 0 | `021a-d29a` | `1597-ec6a` | `feff-7e88` | Not selected |
+| Block Ack `MixedTraffic`, run 0 | `5b9e-801f` | `8731-2504` | `9724-d1ce` | Not selected |
+| Block Ack `NoFragmentation`, run 0 | `b828-6bd6` | `026f-2c6d` | `2cd4-4795` | Not selected |
+| Fragmentation `HCFfragblockack`, run 0 | `f509-65f1` | `6b3a-0d65` | `c474-7174` | `4cfb-b3c7` |
+| TXOP `General`, run 0 | `f58d-d2fd` | `38f7-f642` | Not selected | `e520-9a34` |
+
+Paired production traces confirm the first changed exchanges. In `NoFragmentation`, both versions transmit DATA 0 at 0.210 ms and complete it at 0.496 ms. Both also number DATA 1 through the existing successor forecast. The parent immediately requests starting number one. The candidate first resolves numbered DATA 1 under Normal Ack, then requests starting number two. This is A4's required drain-before-setup rule.
+
+The TXOP trace likewise changes the request number from two to three after numbered DATA resolves. The fragmentation trace completes the numbered fragmented unit before ADDBA. The parent starts setup after fragment zero. The ad hoc trace changes the request number from two to three after numbered Voice fragments resolve. The infrastructure trace suppresses empty-successor setup after ARP and starts setup when later WWW DATA arrives. The retained demonstrations still show successful Basic exchanges, positive receipt bits, and fragment bits where required.
+
+`/tmp/s3b-fp-first-divergences.json` and `/tmp/s3b-fp-qos-first-divergences.json` retain times, events, commands, fields, and source lines. These paired windows establish the changed setup boundaries. They do not attribute every later counter difference to one mechanism. The final 19 ingredients equal candidate 10 exactly, so the final cost and reset repairs add no further fingerprint change.
+
+The parent command ran from its own `tests/fingerprint` directory after its `setenv` file. It used debug mode, four threads, and the same 19-case selector. `/tmp/s3b-parent-fingerprints-2.log` retains its complete result. `/tmp/s3b-parent-fingerprint-inventory.json` preserves its calculated ingredients before later jobs overwrote shared result paths. Interrupted invocations and overwritten shared outputs supply no comparison evidence.
+
+The final candidate uses private copies of the unchanged input CSV files in `/tmp/s3b-final-fingerprint-inputs-11`. `input-hashes.json` records their byte hashes. Older jobs cannot replace these comparison inputs or outputs. The exact command runs from `tests/fingerprint`:
+
+```sh
+PYTHONUNBUFFERED=1 ./fingerprinttest -d -t 4 -m 'showcases/wireless/(blockack|aggregation|fragmentation|qos|txop)/|examples/wireless/qos/.*MacQos|examples/adhoc/qos/.*MacQos' -l /tmp/s3b-final-fingerprint-inputs-11/runner.log /tmp/s3b-final-fingerprint-inputs-11/examples.csv /tmp/s3b-final-fingerprint-inputs-11/showcases.csv
+```
+
+`/tmp/s3b-candidate-fingerprints-6.log` records all 19 results and the nonzero mismatch status. `/tmp/s3b-fingerprint-comparison-build11.json` records the parent and candidate 10 comparisons. `/tmp/s3b-final-verification.md` supplies the regression analysis. The seven calculated rows above are evidence, not approved baseline values. Every recorded repository expectation remains unchanged.
+
+The separate capability observer also passes all eight inventory cases on candidate 11. Its command is `python3 tests/module/basic-compatibility-evidence.py --protocol-inventory --output /tmp/s3b-final-protocol-inventory-11`. The evidence file records actual HT state independently of the protocol assertions. Passing inventory observations do not convert the four expected protocol failures into supported features.
diff --git a/plan/done/ieee80211-compressed-blockack-s3b-summary.md b/plan/done/ieee80211-compressed-blockack-s3b-summary.md
new file mode 100644
index 00000000000..c09a1e343e9
--- /dev/null
+++ b/plan/done/ieee80211-compressed-blockack-s3b-summary.md
@@ -0,0 +1,251 @@
+# S3b: safe Block Ack agreement setup and teardown
+
+> **Kind:** reference · **Status:** snapshot 2026-10-15 · **Seal:** none · **Owns:** — · **Stands on:** [replacement summary](../pending/ieee80211-compressed-blockack-replacement-summary.md), [lifecycle plan](../pending/ieee80211-blockack-agreement-lifecycle.md), [execution record](ieee80211-compressed-blockack-s3b-execution.md)
+
+S3b repairs the agreement lifecycle that Compressed Block Ack will need. It controls when DATA can use an agreement, how agreement Actions change state, and how cleanup affects queued and received DATA. The immediate production target is Basic Block Ack. Production HCF still rejects Compressed selection at initialization.
+
+S3b is complete on branch `work/compressed-blockack-s3b` in [PR #1343](https://github.com/inet-framework/inet/pull/1343). The current source revision is `8646a9e6808ea8d21ac28bfc1271537469225fe9`, above base `bba1db4c8180a592894e0ab2a2dc056d9cb08082`. The [completion record](ieee80211-compressed-blockack-s3b-execution.md#completed-s3b) separates broad verification at `8ba7c9e47b` from focused verification of the later allocation-index repair. This status records branch completion; it does not assert a merge into `master`. The diagrams explain implemented behavior; they do not represent measured frame traces.
+
+## Why this stage exists
+
+A Block Ack agreement contains the shared parameters that permit Block Ack operation between an originator and a recipient. The originator sends DATA; the recipient receives DATA. ADDBA establishes the agreement through a request and response. DELBA ends the agreement through an Action frame. A BAR is a control frame that requests a Block Ack response.
+
+Safe DATA transmission requires more than a correct Block Ack bitmap. Both stations must agree on the parameters and on whether an agreement exists. Retries, lost responses, queue disposal, inactivity, and association replacement can change that state. S3b gives each event an owner and a defined effect on DATA.
+
+The parent reproductions expose concrete causes. The recipient service deletes accepted Actions, while HCF dispatches the original header without the duplicate result. The recipient response omits the dialog token that identifies its request. HCF also treats an absolute inactivity deadline as a relative delay. The [execution record](ieee80211-compressed-blockack-s3b-execution.md#source-assessment) gives the source assessment and reproduction limits.
+
+```mermaid
+flowchart LR
+ P["S1, S2, R1, S3a
Recipient progress, DATA lifetime,
management rates, profile selection"]
+ L["S3b
Agreement lifecycle
A1 through A4"]
+ B["S4
BAR-only channel access
and transfer completion"]
+ C["S5: C1 through C4
Compressed frame format,
exchanges, timing checks, default"]
+ P --> L --> B --> C
+```
+
+S3b supplies the lifecycle foundation. S4 supplies the final BAR exchange and transfer-completion behavior. S5 supplies complete Compressed exchanges and their verification. The [replacement summary](../pending/ieee80211-compressed-blockack-replacement-summary.md) owns this stage order. The replacement and lifecycle plans remain pending because their S4 and S5 work remains incomplete.
+
+## Who owns the state
+
+Agreement identity includes the local role, peer, and traffic identifier (TID). One station can hold both roles for the same peer and TID. These records describe opposite DATA directions. Teardown of one role must preserve the other role.
+
+| Component | Responsibility in S3b |
+| --- | --- |
+| HCF, the hybrid coordination function | Coordinates agreement Actions, frame exchanges, timers, DATA eligibility, and cleanup order. |
+| Originator agreement handler | Owns originator agreements, setup procedures, response deadlines, and recovery state. |
+| Recipient agreement handler | Owns recipient agreements and recipient teardown procedures. |
+| Agreement policies | Check setup parameters and profile admission. |
+| Originator QoS data service | Selects eligible DATA, checks A-MSDU permission, and assigns sequence numbers after extraction. |
+| Recipient QoS data service | Checks duplicates, returns accepted Actions, and prepares received DATA for delivery or disposal. |
+| Reorder owner | Holds received DATA behind sequence gaps and supplies ordered complete units at teardown. |
+| Queue components | Own extraction, ownership transfer, queue residence metadata, and pull events. |
+| Management and the MIB | Management identifies association reset. The management information base (MIB) owns capability and rate state. |
+
+The [execution contract](ieee80211-compressed-blockack-s3b-execution.md#implementation-contract) defines these responsibilities. HCF coordinates the existing owners; queues do not keep a second copy of agreement state.
+
+## A1: apply each accepted Action once
+
+HCF must distinguish reception of an Action from acceptance of that Action for agreement processing. A valid MAC retry still needs its immediate ACK. It must not create another agreement or apply DELBA twice. The recipient data service therefore returns only complete, nonduplicate Actions for HCF dispatch.
+
+```mermaid
+flowchart TD
+ RX["Addressed ADDBA or DELBA arrives"] --> ACK["Retain immediate ACK behavior
for valid received frames"]
+ ACK --> CHECK["Recipient data service checks
complete-frame state and duplicates"]
+ CHECK -->|Duplicate| STOP["No agreement dispatch"]
+ CHECK -->|Accepted complete Action| RETURN["Return Action to HCF"]
+ RETURN --> OWNER["Agreement owner checks identity
and applies the Action once"]
+```
+
+This diagram separates decisions; it does not prescribe ACK timing relative to dispatch. An Action during an active response wait needs an additional order rule. HCF first lets the agreement owner determine whether that Action resolves the pending procedure. HCF then ends the obsolete exchange without an obsolete terminal failure. The retained valid Action still receives its ACK.
+
+Hypothetical example: the peer receives ADDBA Request, but the request ACK is lost. A matched successful ADDBA Response arrives during the request's final ACK wait. The originator accepts the response before request-failure notification. Cleanup must not turn that success into teardown, DATA disposal, or another request retry. An unrelated Action does not suppress the interrupted exchange's ordinary failure result.
+
+The [Action contract](../pending/ieee80211-blockack-agreement-lifecycle.md#3-agreement-behavior) owns this order. The [HCF source](../../src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc) contains dispatch and response-wait integration. The frame-sequence handler owns the interruption outcome after recipient processing. It suppresses obsolete failure feedback for retired frames. An Action attempt that Tx already accepts retains its context until safe completion; logical retirement does not permit immediate destruction of that context.
+
+## A2: use the parameters that both stations accept
+
+An ACK for ADDBA Request confirms request reception. It does not establish that the originator accepted an agreement. The originator activates its agreement only after a matched successful ADDBA Response passes validation. The response must match the pending peer, TID, and dialog token. It must also specify immediate policy and supported parameters.
+
+```mermaid
+sequenceDiagram
+ participant O as Originator
+ participant R as Recipient
+ O->>R: ADDBA Request: TID, token, first sequence number
+ R-->>O: Immediate ACK for request
+ Note over R: Validate request and build accepted response
+ Note over R: Create recipient agreement from response values
+ R->>O: ADDBA Response: same token, accepted parameters
+ Note over O: Match peer, TID, token, and parameters
+ Note over O: Activate originator agreement
+ O-->>R: Immediate ACK for response
+ O->>R: DATA under the accepted agreement
+```
+
+The built-in recipient policy chooses `min(64, maximumAllowedBufferSize)` as the accepted buffer size. The request size gives guidance; it does not cap that choice. Both agreement owners use the response timeout value for inactivity. A zero response timeout disables inactivity expiration. The recipient keeps the request's first sequence number for its reorder state.
+
+Hypothetical example: the request suggests eight positions, but the recipient configuration permits 64. The response advertises 64, so both agreement owners use 64. The recipient must not retain eight while the originator uses 64. A rejected modification request leaves the existing agreement unchanged.
+
+A-MSDU permission also belongs to negotiation. An A-MSDU combines multiple MSDUs within one MPDU. Both the request and response flags must permit it before the originator constructs an A-MSDU under the agreement. If either flag denies permission, individual DATA can still use Block Ack. Normal Ack must not bypass a denial for an A-MSDU that already exists.
+
+Basic admission preserves non-HT and legacy ad hoc compatibility. Compressed admission requires local HT operation and valid peer HT state for the relevant direction. S3b checks Compressed admission through isolated policies; this does not enable production Compressed exchanges. The [negotiation contract](../pending/ieee80211-blockack-agreement-lifecycle.md#3-agreement-behavior) defines these conditions and policy choices.
+
+## A3: end the correct agreement without stale work
+
+A3a covers directional teardown and inactivity. A3b covers peer reset. A3c covers Action disposal and recovery when no agreement exists. These are review units within one lifecycle contract. The first retirement path must also invalidate obsolete Actions that could later change the retired state.
+
+### Directional teardown preserves the opposite role
+
+For received DELBA, HCF uses the transmitter address as the peer. The Initiator field identifies which local role must end. If the peer sends DELBA as originator, the local recipient agreement ends. If the peer sends DELBA as recipient, the local originator agreement ends.
+
+Ordinary recipient teardown first removes the agreement from lookup. The recipient data service then preserves duplicate state and obtains complete buffered units in cyclic sequence order. HCF delivers those units before the deletion event and resource release. Incomplete Basic units undergo disposal. Association reset uses a different policy: it discards old buffered units.
+
+```mermaid
+flowchart TD
+ START["Recipient agreement ends"] --> DETACH["Remove agreement from lookup
and invalidate obsolete Actions"]
+ DETACH --> KIND{"Association reset?"}
+ KIND -->|No| ORDER["Collect complete buffered units
in cyclic sequence order"]
+ ORDER --> DELIVER["Deliver complete units once
and discard incomplete units"]
+ KIND -->|Yes| DISCARD["Discard old buffered units
without upper delivery"]
+ DELIVER --> EVENT["Emit deletion event
and release agreement resources"]
+ DISCARD --> EVENT
+```
+
+Hypothetical Basic example: sequence 100 is absent, while complete units 101 and 102 remain behind the gap. Ordinary recipient teardown delivers 101 followed by 102 before cleanup. Acknowledgment of those units alone does not establish upper delivery. This release across a gap is an INET policy choice. The [migration guide](../../doc/src/migration-guide/index.rst) explains the implemented teardown policy.
+
+At ordinary originator teardown, HCF discards transmitted, unacknowledged DATA for the selected agreement. HCF also discards unsent fragments through the highest transmitted allocation index. The QoS data service assigns this local index before fragmentation; all fragments retain the same index. The agreement retains the highest transmitted index after ACK processing removes an earlier fragment. Allocation indices preserve order across large sequence gaps and wraparound; older retransmissions cannot move the boundary backward. Later intact DATA and another peer's DATA remain eligible.
+
+The fragment regression supplies a concrete example. Fragment 0 of sequence 4095 enters transmission before inactivity expires. HCF discards its unsent siblings at teardown and preserves the intact successor with sequence 0. The fixture also checks expiration after fragment 0 receives its Normal Ack. A separate collision regression checks disposal of the first DATA before transmission; HCF retires that agreement before its successor can use it.
+
+```mermaid
+flowchart LR
+ F0["Sequence 4095, fragment 0
Transmission accepted"] --> E["Inactivity expires
Retire originator agreement"]
+ E --> D["Discard unsent fragments
of sequence 4095"]
+ E --> K["Keep intact successor
with sequence 0"]
+ E --> P["Keep another peer's DATA"]
+```
+
+### Two timers serve different purposes
+
+The ADDBA response timer bounds setup after the request receives its Normal Ack. The inactivity timer bounds an active agreement without relevant activity. S3b uses separate timers because setup uncertainty and agreement inactivity require different state transitions.
+
+| Timer | Start or refresh condition | Expiration effect |
+| --- | --- | --- |
+| ADDBA response timer | Request ACK starts that peer/TID deadline. The default INET interval is 100 ms. | HCF starts teardown because the peer can hold an agreement. |
+| Agreement inactivity timer | Agreement activation starts its deadline. Relevant traffic refreshes it. | HCF retires each overdue agreement and starts DELBA. |
+
+HCF schedules the earliest absolute deadline across both agreement roles. It cancels the inactivity timer when no finite deadline exists. Expiration selects deadlines at or before the current time, so a late callback does not miss an agreement.
+
+```mermaid
+flowchart LR
+ O["Originator role
earliest active deadline"] --> MIN["HCF selects the minimum
absolute deadline"]
+ R["Recipient role
earliest active deadline"] --> MIN
+ MIN -->|Finite deadline| AT["Schedule at that time
or now if overdue"]
+ MIN -->|No finite deadline| CANCEL["Cancel inactivity timer"]
+```
+
+Hypothetical timing example: the current time is 1 ms and the absolute deadline is 5 ms. HCF schedules expiration at 5 ms. A relative delay of 5 ms would incorrectly schedule 6 ms. The execution record reports this exact parent failure. The [timeout contract](../pending/ieee80211-blockack-agreement-lifecycle.md#response-timeout-and-data-lifetime) distinguishes the INET response timeout from DATA lifetime.
+
+### Peer reset clears both roles before callbacks
+
+Association replacement ends the old association even when the replacement publishes identical rates and HT capabilities. A rate-change notification alone cannot identify that event. Management therefore passes the peer identity to HCF before replacement. HCF clears both roles and old peer work before the first agreement deletion callback.
+
+```mermaid
+sequenceDiagram
+ participant M as Management
+ participant H as HCF
+ participant A as Agreement owners
+ participant D as DATA and Action owners
+ participant V as Deletion observers
+ M->>H: Reset old peer before association replacement
+ H->>A: Detach both roles and pending procedures
+ H->>D: Invalidate old Actions and clear old peer DATA
+ H->>D: Discard old receive buffers and duplicate state
+ H->>V: Emit agreement deletion events
+ Note over V: Neither old role remains visible
+ M->>M: Complete association state replacement
+```
+
+With Block Ack enabled, reset discards the old peer's queued and in-progress QoS DATA, including unsent Normal Ack DATA without an agreement. Another peer retains its agreements, DATA, and lifetime records. The MIB continues to publish coherent committed state through its existing boolean notification. The [association-reset contract](../pending/ieee80211-blockack-agreement-lifecycle.md#reassociation-consumers-and-callback-order) explains why the separate reset handoff is necessary.
+
+An accepted physical exchange retains its context until completion. Physical statistics still record that exchange, but its callbacks cannot submit retired DATA after CTS. The frame-sequence handler destroys the context before it deletes deferred frame copies.
+
+### Queue loss and uncertain setup require explicit recovery
+
+HCF tracks each locally generated ADDBA or DELBA Action by frame identity, peer, TID, and role. Queue rejection, eviction, retry failure, and acknowledgment reach that Action's owner. Each Action produces one terminal transition. Intentional invalidation removes its procedure identity before queue disposal can report a second failure.
+
+If Tx never accepts a request attempt, local cancellation can release DATA under Normal Ack. After a possible request transmission, no response does not prove that the peer lacks an agreement. HCF must defer DATA until teardown confirms a common state. A successful recipient agreement remains active if its response suffers disposal; response loss does not undo recipient acceptance.
+
+```mermaid
+flowchart TD
+ P["ADDBA pending
DATA deferred"] -->|Matching success| A["Active agreement
release eligible DATA"]
+ P -->|Initial rejection or cancellation
before any accepted request attempt| N["No agreement
Normal Ack DATA can resume"]
+ P -->|Attempted request fails
or response deadline expires| T["DELBA pending
DATA remains deferred"]
+ T -->|DELBA receives ACK| N
+ T -->|Originator DELBA fails
or suffers queue disposal| R["Drop deferred batch
retain recovery-needed state"]
+ R -->|New DATA arrives| T
+```
+
+S3b treats a DELBA ACK as teardown confirmation for this INET profile. This policy assumes that the peer processes a valid DELBA. Failed originator DELBA drops the current deferred batch and leaves recovery-needed state without a periodic retry timer. New DATA triggers one fresh DELBA exchange; DATA that arrives during that exchange shares its result.
+
+Failed recipient-role DELBA clears its teardown procedure without an originator recovery record or disposal of opposite-direction DATA. Valid Block Ack-policy DATA or a valid BAR without an agreement triggers recipient-role DELBA. That DATA does not reach upper delivery, and that BAR does not create receive state. Malformed BAR input does not trigger this recovery path. The [lifecycle contract](../pending/ieee80211-blockack-agreement-lifecycle.md#3-agreement-behavior) owns these separate outcomes.
+
+## A4: preserve the first sequence number and unrelated DATA access
+
+The ADDBA request specifies the first sequence number for the agreement. Setup must not consume that number through premature extraction. After the first eligible DATA transmission, more unnumbered DATA for that peer and TID can trigger setup intent. Already numbered DATA first completes or drops under its existing policy. HCF then queries the next sequence number without consumption and creates ADDBA Request.
+
+Hypothetical example: the next unassigned number is 100. HCF requests an agreement with first number 100 while the affected DATA remains queued. After a matching response, extraction assigns 100 to the first DATA. If DATA 100 suffers terminal disposal before transmission, HCF starts teardown before DATA 101 can use that agreement. Extraction alone does not count as first transmission.
+
+Deferral applies to one peer and TID. It must not prevent another peer, another TID, or eligible management traffic from access. The data service selects the first eligible frame in the configured queue service order. HCF uses the same eligibility condition for DATA contention. A nonempty queue that contains only deferred DATA does not justify a new DATA contention request.
+
+```mermaid
+flowchart LR
+ subgraph Q["Configured queue service order"]
+ A["Peer A, TID 2
setup pending
deferred"]
+ B["Peer B, TID 5
eligible"]
+ C["Peer A, TID 2
deferred"]
+ A --- B --- C
+ end
+ B --> X["Extract B through its queue owner
and assign its sequence number"]
+ A -.-> W["Remain queued
with existing lifetime state"]
+ C -.-> W
+```
+
+Selected extraction must preserve ownership, queue residence metadata, pull events, and compound queue accounting. Arbitrary removal has different effects and cannot replace extraction. The optional `ISelectivePacketCollection` interface supplies an eligibility query and extraction of the selected frame. Built-in support covers `PacketQueue`, `PriorityScheduler`, and `CompoundPacketQueueBase`.
+
+An external queue needs this interface only when deferred head DATA hides eligible work. Its ordinary dequeue path remains usable when its next frame is eligible. If every frame is deferred, the service waits. If eligible work requires an unsupported bypass, the service reports a diagnostic before mutation. The [selected-extraction contract](../../src/inet/queueing/contract/ISelectivePacketCollection.h) defines these operations.
+
+Before A-MSDU construction, the service checks one proposed source list. The first source must match the selected frame; every source must remain eligible and have permission. An invalid list causes individual extraction of the selected frame. Other sources retain their queue, lifetime, and sequence state. This prevents aggregation that bypasses deferral or the configured service order.
+
+## Completed verification
+
+The [completion record](ieee80211-compressed-blockack-s3b-execution.md#completed-s3b) reports broad results for source revision `8ba7c9e47bc3ded924cfa3917c8b899baccf789d`. Debug and release compilation pass at that revision. All 26 selected debug module fixtures and 19 functional fingerprint cases pass at that revision. The functional selection excludes graphical `tyf` expectations.
+
+The later allocation-index repair records debug and release compilation and four debug fixtures with 50 successful simulation runs. Those fixtures cover fragment retirement, lifecycle behavior, association reset, and DATA selection. The fragment fixture supplies 20 runs across sequence gaps, wraparound, and teardown before or after ACK reception. These results belong to original revision `ea20a2880e`, which maps to `8646a9e680` without source or test changes. The history comparison establishes content equivalence; it is not new behavioral evidence. This document correction repeats no build or simulation test.
+
+| Verified behavior | Relevant fixtures in this checkout |
+| --- | --- |
+| Accepted Actions return once; duplicates retain ACK behavior | [Action delivery](../../tests/module/Ieee80211BlockAckActionDelivery_1.test), [Action dispatch](../../tests/module/Ieee80211BlockAckActionDispatch_1.test) |
+| A matching Action resolves setup before obsolete exchange failure | [Response wait](../../tests/module/Ieee80211BlockAckResponseWait_1.test) |
+| Accepted parameters and profile admission reach their owners | [Negotiation](../../tests/module/Ieee80211BlockAckNegotiation_1.test), [Admission](../../tests/module/Ieee80211BlockAckAdmission_1.test) |
+| Directional cleanup and absent-agreement recovery preserve other state | [Lifecycle](../../tests/module/Ieee80211BlockAckLifecycle_1.test) |
+| Association replacement clears both roles before observers run | [Association reset](../../tests/module/Ieee80211BlockAckAssociationReset_1.test) |
+| Setup deferral preserves sequence state and unrelated progress | [Deferral](../../tests/module/Ieee80211BlockAckDeferral_1.test), [DATA selection](../../tests/module/Ieee80211BlockAckDataSelection_1.test) |
+| Selected extraction preserves queue effects | [Selective extraction](../../tests/module/PacketSelectiveExtraction_1.test) |
+| First-DATA disposal retires the agreement before successor transmission | [Deferred collision](../../tests/module/Ieee80211BlockAckDeferredCollision_1.test) |
+| Teardown removes unsent fragments through the highest transmitted allocation index | [Fragment retirement](../../tests/module/Ieee80211BlockAckFragmentRetirement_1.test) |
+
+The earlier source revision `667ae51dc9fbfbfac9ea68122bf65bc4f2d3e1df` supplies broader module, unit, protocol, and Basic demonstration results. Those results remain separate from verification of the allocation-index repair. The later focused results also do not extend the earlier 26-fixture or 19-fingerprint results to the current source revision. Seven approved functional fingerprint rows belong to the lifecycle implementation; the later repairs change no baseline. Graphical expectations remain unchanged.
+
+The later audits inspect the repair and documentation claims; they do not establish a full independent correctness audit. The archived local results establish no release-mode behavioral tests, sanitizer results, or other-platform results for the allocation-index repair. CI results remain separate from these local records.
+
+## Scope limits
+
+S3b does not establish these later capabilities:
+
+- Production Compressed BAR and Block Ack exchanges, serialization, or receipt-state behavior.
+- S4 BAR arbitration, BAR-only channel access, or teardown after the final Block Ack exchange.
+- Compressed response-rate and airtime correctness.
+- General MAC stop/start recovery.
+- A performance guarantee across repeated setup and teardown.
+
+S3b supplies a defined agreement lifecycle with safe DATA eligibility and cleanup. Later stages can add Compressed exchanges without a separate lifecycle mechanism. Their own production evidence remains necessary.
diff --git a/plan/pending/ieee80211-compressed-blockack-s4-execution.md b/plan/pending/ieee80211-compressed-blockack-s4-execution.md
new file mode 100644
index 00000000000..dc83901f766
--- /dev/null
+++ b/plan/pending/ieee80211-compressed-blockack-s4-execution.md
@@ -0,0 +1,178 @@
+# S4 execution record
+
+S4 completes Basic BAR work independently of DATA extraction. The initial source parent is `b29151fc8b`. The user authorized S4 implementation on 15 October 2026. Existing local edits to `AGENTS.md` and the prerequisite plans remain outside this change.
+
+## Implementation contract
+
+HCF previews the next eligible frame before a frame sequence extracts DATA. The originator ACK policy selects outstanding frames by peer and TID. The policy permits threshold deferral only for a candidate within that agreement's transmit window. The agreement owns the window start and size. Resolved DATA permits window progress; a received response number alone does not.
+
+HCF treats BAR work as contention work for its access category. Frame sequences permit BAR without a DATA pointer. Internal collision retains BAR work without DATA retry feedback. At an exchange boundary, HCF queues DELBA when an accepted agreement has no queued DATA, in-progress DATA, or active BAR exchange. Expiration uses the same teardown path after unresolved DATA disappears.
+
+The affected components are HCF, the QoS ACK policy, the ACK handler, the originator agreement, the in-progress queue, and HCF frame sequences. The optional QoS data-service contract exposes preview without a concrete dependency from the shared queue. Queue preview borrows a Packet pointer. Extraction retains the existing ownership transfer and sequence assignment. DCF extraction remains unchanged. S5 owns Compressed response validation and integration.
+
+The checks use focused module fixtures through HCF, frame sequences, Tx, and production response callbacks. They cover small windows, short bursts, peer/TID isolation, failed BAR recovery, collision, final expiration, and transfer completion. Timing checks compare Basic BAR exchanges with the pinned parent under single and multiple protection. A new unlawful continuation requires an admission correction before completion.
+
+The initial contract review checked source, ownership, protection, and callers before the first source edit. The affected source paths have no seal. S1 recipient progress and S3b teardown exist in the pinned parent.
+
+## Standards evidence
+
+The local corpus supplies IEEE Std 802.11-2024 normative Clauses 10.25.4, 10.25.6.7.1, and 10.23.2.9. Their canonical identifiers are `ieee80211-2024:clause:`. Their physical PDF pages are 2056, 2068–2070, and 2012–2014. Corpus status reports fresh input. The corpus linter result is `/tmp/s4-standard-lint.json`. Direct PDF inspection was unnecessary.
+
+## Verification
+
+Failed BAR recovery also requires the Retry bit on the next DATA attempt. Source inspection found that the old BAR failure path only changed ACK status. HCF now derives that bit from the failed Block Ack status before retransmission. IEEE Std 802.11-2024, Clause 9.2.4.1.6, physical PDF page 665, supplies this requirement for individual DATA.
+
+### Direct behavior checks
+
+The fresh debug build passes. The final focused selection passes 31 module fixtures and six unit fixtures. The module log is `/tmp/s4-module-final-limit.log`. The unit log is `/tmp/s4-unit-final-limit.log`. The new BAR fixture executes 30 scenarios across single and multiple protection. These results establish the recorded Basic paths. They do not establish Compressed support.
+
+The unit fixture checks peer/TID selection, sequence wrap, window boundaries, response identity, failed-response release, and exact lifetime eligibility. The production fixture exercises HCF, EDCA, frame sequences, Tx, and response callbacks. Its cases cover thresholds below five, accepted windows one and four, unrelated peers, negative bitmaps, timeout, and final expiration. The collision cases require BAR contention without a DATA retry entry. The nonzero contention-window case requires contention-window growth after collision. The short-limit case permits the initial Control-frame exception.
+
+BAR failure updates the access category's short retry counter and contention window. The one-failure limit case requires growth on the first failure. The unit fixture requires reset on the next failure. A successful Block Ack response resets both values. These checks require no DATA retry entry.
+
+The final-expiration case creates an incomplete fragment unit before two complete units. The production recipient retains those complete units behind the gap. Final expiration triggers DELBA. The recipient delivers both complete units before it deletes the agreement. This connects A5 to the existing recipient teardown path.
+
+Three existing fixtures require changed expectations because a short burst now resolves its final BAR and DELBA. The deferral fixture checks both remaining original DATA frames. Both retain their lifetime obligation after unsent disposal. The fragment-retirement fixture answers the unrelated peer's final BAR before its successor DATA. The multiple-protection fixture separates the DELBA management ACK from DATA ACKs.
+
+### Basic timing comparison
+
+The comparison parent is `b29151fc8b8953564dbbf61b48ece12658a1475c`. A detached checkout at `/tmp/inet-s4-parent` supplies its separately built debug library. The timing observer records actual transmit modes, preceding IFS, response airtime, TXOP deadline, and DATA count. The parent fixtures supply observations without S4 assertions. Their successful runner result alone supplies no S4 behavior proof.
+
+For the 1160-microsecond TXOP, the parent transmits a final BAR after five DATA frames. That BAR exchange ends at 2.270 ms. The deadline is 2.230 ms. This 40-microsecond overrun occurs with both protection modes. The S4 admission check rejects that continuation. EDCA later contends for a standalone BAR. The direct fixture requires every later BAR exchange to fit its applicable bounds.
+
+A separate case gives a standalone BAR a 50-microsecond TXOP limit. The initial BAR exchange takes 120 microseconds. Its 70-microsecond overrun uses the permitted initial Control-frame exception. It has no preceding DATA or Management frame. The admission check preserves this case.
+
+The diagnostic showcase traces also find DATA continuations beyond the TXOP bound in both parent and candidate. Each recorded violation ends with DATA. S4 does not repair general DATA admission. These observations limit the result to the changed Basic BAR paths. They do not establish general TXOP or NAV conformance.
+
+### Completion cost
+
+The cost fixture sends twelve UDP payloads of 700 bytes over a fixed 100-ms observation interval. One configuration sends one continuous burst. The other sends three bursts of four, with 20-ms gaps. Both configurations deliver all twelve payloads without a MAC drop. The payload goodput is 672000 bit/s in each configuration.
+
+| Candidate measurement | Continuous burst | Three bursts |
+| --- | ---: | ---: |
+| ADDBA requests / responses / DELBA frames | 1 / 1 / 1 | 3 / 3 / 3 |
+| Management airtime | 108 microseconds | 324 microseconds |
+| Total measured deferral | 280.133424 microseconds | 822.400272 microseconds |
+| Request contention | 61 microseconds | 156 microseconds |
+| Response time after request | 159.066712 microseconds | 486.200136 microseconds |
+| DELBA completion time | 114.066712 microseconds | 387.200136 microseconds |
+| Sum of payload delivery delays | 147.632334920 ms | 43.933601088 ms |
+
+The parent sends one request and one response in each configuration. It sends no completion DELBA. Its management airtime is 72 microseconds in each configuration. Both parent configurations deliver the same payload total. The parent and candidate also differ in extraction and BAR behavior. Thus, this comparison does not isolate an agreement-reuse cost. It supplies no general throughput or latency claim.
+
+### Regression and source checks
+
+The parent matches all nineteen original fingerprint rows. The evidence is `/tmp/s4-parent-fingerprints.log` and `/tmp/s4-parent-fingerprints-retry.log`. The selection preserves each original configuration, run number, simulation limit, and requested fingerprint ingredient. The final candidate run completes all nineteen rows in 227.330 seconds. Twelve rows match their baselines. Seven rows report fingerprint differences in configurations with Basic Block Ack enabled.
+
+The final log is `/tmp/s4-fingerprints-final-limit.log`. Its runner exits with status 1 because the seven recorded baselines differ. Every simulation reaches its required limit, including the enabled ad hoc row at ten seconds. Earlier attempts hit its 600-second CPU limit. Those partial attempts supply no replacement baseline. The final retry-limit correction preserves all nineteen results from the preceding full run.
+
+The following values are review candidates only. This execution does not approve or change a recorded baseline.
+
+| Configuration | Candidate fingerprint values |
+| --- | --- |
+| `NoFragmentation` | `4a83-1e6f/tplx;09db-9f72/~tNl;0596-3a23/~tND` |
+| `Fragmentation` | `91a8-1fb6/tplx;f43e-1f17/~tNl;aaae-de01/~tND` |
+| `MixedTraffic` | `202c-1e73/tplx;4e7c-be6a/~tNl;5544-6a00/~tND` |
+| `HCFfragblockack` | `140a-28bf/tplx;233d-c918/~tNl;5890-e570/~tND` |
+| TXOP `General` | `06db-3079/tplx;3727-bbbd/~tNl` |
+| Ad hoc `MacQos`, run 1 | `491a-01b6/tplx;9558-1598/~tNl;6970-8509/~tND` |
+| `MacQosWithBlockAck` | `cdb0-57dc/tplx;aa99-d171/~tNl;1368-030c/~tND` |
+
+The architecture check passes. The source-seal check permits every changed source path. The naming check reports the same 32 findings as the parent. The interface check retains the parent's two frame-step findings. The new QoS service interface passes that check. The logs use the `/tmp/s4-` prefix. No recorded fingerprint baseline changes belong to this implementation.
+
+The protocol selection executes eight fixtures. Four pass. Delayed Block Ack, Multi-TID Block Ack, and both A-MPDU fixtures retain four declared expected failures. `N_CompressedBa` selects Basic in this branch. Its pass does not establish Compressed behavior. The log is `/tmp/s4-protocol-final-limit.log`.
+
+The frame observer compares the first changed transmission in each affected configuration. It preserves actual bytes, sequence, TID, Retry, Duration, and selected mode. Its configuration adds an observer and a shorter observation limit. Thus, these observations explain local mechanisms; they do not replace the full fingerprint runs.
+
+| Configuration | First observed change | Confirmed mechanism |
+| --- | --- | --- |
+| `NoFragmentation` | ADDBA request replaces the next Normal Ack DATA at sequence 1. | Preview avoids speculative DATA extraction before setup. |
+| `MixedTraffic` | ADDBA request replaces the next large Normal Ack DATA at sequence 1. | The same extraction change permits setup before the successor. |
+| `Txop` | ADDBA request precedes the next large DATA at sequence 2. | The same extraction change preserves the setup boundary. |
+| `Fragmentation` | Final fragment 15 of sequence 1 reserves 464 instead of 116 microseconds. | Preview reads the queued unit before aggregation or fragmentation. |
+| `HCFfragblockack` | Retransmitted fragment 2 of sequence 1 sets Retry. | Failed Block Ack status now sets the DATA Retry bit. |
+| `MacQosWithBlockAck` | The first changed DATA has a 332-byte A-MSDU instead of a 166-byte unit. | Later extraction changes the available aggregation set. |
+| Ad hoc `MacQos`, run 1 | Fragment 2 of sequence 1 reserves 484 instead of 628 microseconds. | Preview no longer extracts the next aggregate for the Duration estimate. |
+
+These Duration observations do not prove complete NAV correctness. The existing Basic forecast does not model every BAR boundary. The direct timing assertions establish the separate BAR admission result.
+
+The saturated ad hoc case also exposes a host execution cost in transfer-completion checks. A temporary queue observer records 5120 predicate checks in each unrelated queue. Its stack trace identifies `completeTransfers` through `hasQueuedData`. HCF now checks the category from EDCA's existing TID mapping first. It retains the other categories as a fallback for custom classification. This changes search order without new persistent state or a different existence predicate.
+
+The temporary HCF observer records the same event count and fingerprint values before and after that search-order change. Its observed time in `processLowerFrame` falls from 41.0384 to 4.68354 seconds. Its observed time in `frameSequenceFinished` falls from 8.56017 to 0.226506 seconds. Host load differs between these observations. These values locate the cost; they supply no controlled speed benchmark.
+
+### Reproduction and retained evidence
+
+The local evidence directory is `audit/2026-10-15-s4/`. It retains the raw logs, parent observations, fingerprint selection, and temporary diagnostic sources. The diagnostic sources remain outside the delivered test suite. The directory also retains source hashes for this result. The build uses OMNeT++ from `/home/user/omnetpp-6.4.0aipre2` and the checkout's `setenv`.
+
+The category runners use these selections from the repository root:
+
+```sh
+make MODE=debug -j8
+inet_run_unit_tests -m debug -f 'Ieee80211(BlockAckScheduling|MultipleProtection|HtModeSet|HtModeCache|VhtHtCapabilities|ResponseModeSelection)_1\.test'
+inet_run_module_tests -m debug -f 'Ieee80211(BlockAck(ActionDelivery|ActionDisposal|ActionDispatch|AssociationReset|DataSelection|Deferral|DeferredCollision|FragmentRetirement|Lifecycle|Negotiation|RetiredData|Reordering|ResponseWait|Admission|Variant|Scheduling|CompletionCost)|EdcaDataLifetime(Arbitration|Aggregation|Congestion|Defaults)?|Hcf(Header|SingleProtection|MultipleProtection|InternalCollision)|BasicCompatibility|RecipientNav|SingleProtectionGroup|VhtHtSupport)_[12]\.test'
+inet_run_protocol_tests -p inet -m debug -w wifi -f '(N_BlockAck|Ac_BlockAck|N_Amsdu|N_CompressedBa|WifiQosDelayedBa|Ac_MultiTidBa|N_Ampdu|Ac_Ampdu)\.test$'
+```
+
+The fingerprint command runs from `tests/fingerprint`. The retained CSV contains the exact nineteen original rows.
+
+```sh
+./fingerprinttest -d -t4 -m '^/(showcases/wireless/(blockack|aggregation|fragmentation|qos|txop)|examples/(wireless|adhoc)/qos)/ ' -f tplx -f '~tNl' -f '~tND' -l /tmp/s4-fingerprints-final-limit.out /tmp/s4-fingerprint-final-limit.csv
+```
+
+### Scope limits
+
+S5 owns Compressed response integration. Delayed Block Ack, Multi-TID Block Ack, and A-MPDU remain outside this result. Recorded fingerprint baselines remain unchanged. The existing prerequisite plans and unrelated local files retain their prior edits. The initial implementation checks precede the commit and push request. The publication checks extend that initial evidence.
+
+## Initial checks before publication
+
+The user authorized commit and push on 15 October 2026. The series separates DATA preview from its first BAR selection consumer. The preview commit is `e2d20d6be6`. Its isolated debug build passes. Five focused module fixtures pass, including the direct preview checks for ownership and sequence assignment. The behavior commit contains BAR selection, window progress, transfer completion, and their direct fixtures.
+
+The final debug build passes. Its fresh selection passes 31 module fixtures and six unit fixtures. A separate scheduling run also passes after removal of the temporary parent-comparison directive. These checks retain the same runtime assertions. The logs are `/tmp/s4-publish-debug-build.log`, `/tmp/s4-publish-modules.log`, `/tmp/s4-publish-units.log`, and `/tmp/s4-publish-scheduling-clean.log`.
+
+The preview commit matches all nineteen retained fingerprint rows. Its log is `/tmp/s4-preview-fingerprints.log`. The final protocol rerun returns four passes and four declared expected failures across eight fixtures. Its log is `/tmp/s4-publish-protocol.log`.
+
+The final release compilation also passes. Its log is `/tmp/s4-publish-release-build.log`. The final fingerprint rerun completes nineteen rows. Twelve match their baselines; seven retain exactly the candidate values in this record. Its runner exits with status 1 for those differences. Its log is `/tmp/s4-publish-fingerprints.log`.
+
+The whole-tree architecture gate reports 36 findings. The whole-tree naming gate reports 23 findings. The interface gate reports 15 findings. Each sorted finding set matches the selected parent. The source-seal check passes.
+
+The series gates pass against `origin/master`; their two notes concern the earlier lifecycle commit `667ae51dc`. No pull request description check runs because this task creates no pull request.
+
+The C++ quality gate does not run because this checkout lacks `src/compile_commands.json`. Its setup exits with status 2. The log is `/tmp/s4-publish-cpp.log`. The compiler checks and the other gates do not replace this static analysis.
+
+## Rebase onto the updated S3b branch
+
+The user authorized this rebase after the initial publication. The pinned S3b target is `f6f12c5c9fed341f5834d4ccdeedeec7a562e102`. The original S4 head is `65804c762e524f64d07cfa336da0ceeb0d2bd425`. The backup ref is `refs/backup/compressed-blockack-s4-before-s3b-rebase-20261015`. The two S4 commits retain their order and purpose.
+
+The target adds local allocation indices for DATA retirement across large sequence gaps. The rebase retains that production mechanism alongside S4 window progress and completion DELBA. The collision fixture now records the extracted frame's allocation index. The fragment-retirement fixture retains all twenty gap scenarios and the Basic response path. A deferred successor keeps the earlier transfer open until the fixture advances the allocator. This prevents completion DELBA before the intended inactivity check.
+
+The user permits builds and tests only at the final commit. No intermediate commit receives a build or test. The earlier results above remain historical evidence. Final verification uses the last rebased commit. Its results and preserved-file hashes belong to `audit/2026-10-15-s4-rebase/`.
+
+## Approved fingerprints and final Basic boundary
+
+The user approved the seven final fingerprint changes on 2026-10-15 after review of their causes in PR #1344. This approval covers the non-graphical ingredients in the table below. Graphical `tyf` values remain unchanged and unverified. This section supersedes the earlier candidate values and the statements that S4 includes no baseline update.
+
+The final correction limits Basic single protection to an already prepared successor. HCF ends the TXOP before further DATA extraction can change aggregation, fragmentation, or agreement setup. Prepared fragments retain their continuation path. Final DATA in a zero-limit TXOP reserves only an immediate response when required. The policy can require more contention; it preserves standalone BAR progress.
+
+The source and tests at `8745330c1cc398f3dbc0a43be0c60cd7c2d5c529` contain this correction. Debug and release builds pass at that revision. All 31 module fixtures and six unit fixtures pass. Protocol execution reports four passes and four declared expected failures. The nineteen fingerprint simulations complete; twelve match the previous baselines and seven produce the approved values below. The raw evidence belongs to `audit/2026-10-15-s4-duration/`.
+
+The final observer shows zero Duration for sequence 1, fragment 15, in the fragmentation showcase. The TXOP ends with that DATA; BAR follows in a separate TXOP. The ad hoc observer shows 44 microseconds for sequence 1, fragment 2. Its ACK advertises zero Duration, and the TXOP ends. These observations replace the earlier unverified 464- and 484-microsecond forecasts. They do not establish general Basic DATA admission or complete NAV conformance.
+
+| Configuration | Approved fingerprints | Cause of the movement |
+| --- | --- | --- |
+| Block Ack `NoFragmentation`, run 0 | `9161-625d/tplx;6a02-09aa/~tNl;cbc0-3b0a/~tND` | Final Normal Ack DATA reserves only its ACK; setup and BAR exchange order also change. |
+| Block Ack `Fragmentation`, run 0 | `36f4-5c22/tplx;66b0-94cd/~tNl;45f3-af4f/~tND` | Final Block Ack DATA reserves zero; BAR obtains another TXOP. |
+| Block Ack `MixedTraffic`, run 0 | `351b-d4ba/tplx;5c1f-69a8/~tNl;6182-6da3/~tND` | The current reservation excludes the unextracted large successor; setup can precede that DATA. |
+| Fragmentation `HCFfragblockack`, run 0 | `2597-1e2e/tplx;d269-c6ae/~tNl;2f5c-4cca/~tND` | Failed Block Ack feedback sets Retry on retransmitted DATA; the final burst boundary also changes. |
+| TXOP `General`, run 0 | `ab91-87f3/tplx;f30c-6aa4/~tNl` | The reservation excludes an unextracted successor, and the frame sequence enforces that boundary. |
+| Ad hoc `MacQos`, run 1 | `4fd2-bb56/tplx;ecaf-89f7/~tNl;9398-0fae/~tND` | Final Normal Ack DATA reserves SIFS plus ACK; further extraction requires another TXOP. |
+| Wireless `MacQosWithBlockAck`, run 0 | `6edb-0326/tplx;2a8a-50ee/~tNl;4301-882d/~tND` | ADDBA reserves its immediate ACK without unrelated queued DATA; later extraction can change aggregation. |
+
+The final history keeps the preview preparation separate. One behavior commit contains BAR selection, the final Duration boundary, and these seven baseline rows. Its production source and tests match `8745330c1c`. This placement avoids an intermediate behavior commit with obsolete expectations. Final verification after the baseline update uses only the final commit, as requested.
+
+## Explicit agreement arguments
+
+BAR policy calls now require an explicit agreement handler through both the interface and concrete policy types. Transmitted-BAR processing also requires an explicit originator agreement. An omitted argument fails compilation instead of silently selecting behavior without agreement state. Explicit null values retain their documented behavior. Production callers already supply these arguments, so their runtime behavior stays unchanged.
+
+Compile-time checks cover omitted arguments through both policy types and the ACK handler. The scheduling fixture also checks explicit null policy calls with outstanding DATA. Custom test policies follow the same explicit contract. The migration guide describes the source compatibility change. The seven approved fingerprint rows stay unchanged.
+
+The source-equivalence statement in the preceding section describes baseline acceptance at `fcedba79e3`. This later correction changes API declarations, compile-time checks, and documentation. It changes no production C++ function body.
diff --git a/plan/pending/ieee80211-compressed-blockack-s5-execution.md b/plan/pending/ieee80211-compressed-blockack-s5-execution.md
new file mode 100644
index 00000000000..e89c9e74f7d
--- /dev/null
+++ b/plan/pending/ieee80211-compressed-blockack-s5-execution.md
@@ -0,0 +1,227 @@
+# S5 execution: Compressed Block Ack beside Basic compatibility
+
+S5 is complete through C1, C2, C3, and C4. The authorized TXOP correction removes the active Compressed timing blocker. HCF now defaults to Compressed. Explicit Basic compatibility remains available.
+
+The comparison parent is `75559a49661e33f727fa93a8c81c021596cfb4c9`. The active checkout is `/home/user/omnetpp_ws/inet-compressed-blockack-s5`. The maintainer requested S5 execution on 16 October 2026. That request authorizes C1 through C4 in the [replacement plan](ieee80211-compressed-blockack-replacement.md#compressed-replacement-steps).
+
+The checkout contained changes to `AGENTS.md` and four prerequisite plans before execution. It also contained an untracked planning history. This work preserves those changes. The pinned parent contains the S4 implementation. The [S4 execution record](ieee80211-compressed-blockack-s4-execution.md) supplies its earlier evidence and limits.
+
+## Implementation contract
+
+HCF owns the immutable profile and validates received frame formats before agreement updates. An active agreement identifies one peer and TID. A valid conflicting BAR stops the simulation because ADDBA does not negotiate the profile. Malformed input cannot cause that diagnostic, receipt updates, or agreement recovery.
+
+The recipient record owns explicit Compressed receipt bits and its scoreboard window. The reorder service owns delivery order and its separate window. Delivery must preserve receipt bits. An absent receipt produces zero, including positions before the first received DATA. Basic retains its fragment bitmap and existing compatibility behavior.
+
+The rate selector consumes the MIB rate sets. It selects a non-HT Compressed response and distinguishes initial BAR from later BAR. Frame sequences supply the actual request profile. Protection, timeouts, and duration calculations use the corresponding response length. Compressed responses contain 32 bytes with FCS; Basic responses contain 152 bytes.
+
+For an active Compressed agreement, the originator suppresses fragmentation for that peer and TID. The recipient rejects fragmented DATA before receipt or reorder updates. Normal Ack outside that agreement retains its existing fragmentation behavior.
+
+## Recipient interframe design
+
+The recipient must distinguish receipt evidence from the interframe relation. An agreement with start 100 and size four first receives Normal Ack DATA 100. Delivery advances the reorder start to 101. DATA 104 advances the scoreboard start to 101 and preserves receipt evidence for 100. A qualifying interframe relation permits delivery of 104 despite the gap. A false receipt bit or absent relation retains 104.
+
+Local receive endpoints alone cannot establish the ACK-to-DATA relation with propagation delay. The medium therefore supplies one copied snapshot of physical endpoints. The snapshot contains transmission identity, transmitter identity, transmission endpoints, and cached arrival endpoints. It retains no frame, transmission, arrival, or radio pointer.
+
+The PHY derives the interval at the current transmitter. For the same transmitter, it subtracts the preceding transmission end from the current transmission start. Otherwise, it subtracts the preceding signal's cached arrival end at the current transmitter. This calculation uses the propagation result that the medium already produced. It does not assume equal outward and return delay.
+
+HCF owns local event history. A pending candidate preserves the interval across PHY reception completion and later MAC delivery. A receiver-created transmission identifier connects those events. Unrelated receptions replace prior history. Failed, abandoned, and overlapping receptions must not preserve stale eligibility. Every local transmission clears prior history; completed ACK transmission can establish new history.
+
+SIFS recognition uses the reconstructed interval after signal extension. The current PHY model uses baseline timing without coverage-class adjustment. The permitted bound is one tenth of the slot time. Integer tick division rounds that bound inward. Physical propagation remains separate from this MAC timing parameter.
+
+RIFS recognition requires permitted HT operation, the same transmitter and RA, and no preceding SIFS response obligation. It uses the preceding PPDU endpoint without signal extension. Management must preserve the advertised RIFS Mode. VHT operation does not permit RIFS. This recipient rule adds no RIFS transmitter scheduler.
+
+The review found that ordinary HT transmission at 2.4 GHz omitted the six-microsecond signal extension. The correction belongs in the authoritative PHY duration. HCF must not add it again. Existing short-GI duration differences remain inherited PHY fidelity limits; they do not independently establish an interval error.
+
+## Standards evidence
+
+The standards source is IEEE Std 802.11-2024. The fresh local corpus is `/home/user/omnetpp_ws/inet/standards/processed`. The corpus lint output is `/tmp/inet-s5-standards-lint.json`.
+
+| Canonical node | Physical PDF pages | Requirement |
+| --- | --- | --- |
+| `ieee80211-2024:table:9-36` | 719–720 | BAR variant Type encoding. |
+| `ieee80211-2024:table:9-37` | 722–723 | Block Ack variant Type encoding. |
+| `ieee80211-2024:clause:10.25.6.3` | 2060–2061 | Full-state recipient receipt state. |
+| `ieee80211-2024:clause:10.25.6.6.2.1` | 2065–2066 | Recipient delivery and interframe conditions. |
+| `ieee80211-2024:clause:10.3.2.3.3` | 1889–1890 | SIFS definition and tolerance. |
+| `ieee80211-2024:clause:10.3.2.3.2` | 1888–1889 | RIFS definition and conditions. |
+| `ieee80211-2024:clause:10.22.5` | 1997–1998 | Coverage-class timing behavior. |
+| `ieee80211-2024:table:19-25` | 3493–3494 | Band-specific HT signal extension. |
+
+These provisions are normative. The selected corpus clauses resolve uniquely. The review inspected the original PDF for Clause 11.38.1 on physical page 2886. That clause requires a VHT AP to clear RIFS Mode. The corpus result without children does not include all later prose in that clause.
+
+## Verification boundary
+
+The initial full debug build and subsequent focused rebuilds pass. A build supplies no exchange or timing proof. The focused wire fixture passes independent octet checks. The constructed serializer fixture passes 278 cases. The HCF input fixture passes both configured profiles with supplied recipient agreements. Those results do not establish negotiation or PHY interval recognition.
+
+Five initial protocol simulations pass across the selected n/ac fixtures. They retain Basic coverage and add actual Compressed exchanges. This initial boundary precedes complete infrastructure timing classification and final interframe execution. Later sections record those results and the authorized correction.
+
+An isolated observer reproduces a DATA continuation overrun in the pinned parent. With a one-millisecond TXOP, the third DATA ends 178 microseconds after the bound. The candidate reaches the same endpoints. The parent also reproduces the first observed A-MSDU overrun. These observations identify inherited DATA admission limits. They do not establish general TXOP conformance or excuse a new Compressed violation.
+
+No recorded fingerprint baseline changed. This record does not authorize a baseline change or publication.
+
+## Confirmed timing blocker
+
+The candidate before the TXOP correction introduced a failing supported case that the exact parent did not reproduce. General run 6 uses HT operation, 24 Mbit/s DATA, single protection, aggregation, a one-millisecond TXOP, and seed zero. That candidate transmits two later A-MSDUs beyond their TXOP bounds. Each overrun is 310 microseconds. The affected TXOP contains two DATA frames, so the initial-MSDU exception does not apply.
+
+The second DATA ends at 1.014426 seconds. Its TXOP bound is 1.014116 seconds. The violation exists before any Block Ack response. The aggregate contains 1532 bytes in both versions. The exact parent has no aggregate overrun in run 6. Parent run 14 demonstrates a similar admission defect under different conditions. That other case does not make candidate run 6 acceptable.
+
+A direct run 6 reproduction also fails with the final reviewed debug library. Its output is `/tmp/inet-s5-reviewed-run6.out`; its error is `/tmp/inet-s5-reviewed-run6.err`. That run stops at the first later A-MSDU violation, at 1.014426 seconds. The earlier non-fatal observer campaign records both violations.
+
+The inherited admission mechanism causes the new result. `HcfFs` tests positive remaining time before a later exchange. It does not establish that the complete exchange fits. HCF excludes Block Ack support from its existing complete-cost DATA guard. The corrected HT control airtime and Compressed responses change the trajectory. The comparison does not isolate their separate contributions.
+
+All sixteen initial parent and candidate runs deliver thirty payloads, with 21000 payload bytes per run. All observed BAR/Block Ack exchanges remain within their applicable bounds. Those successes do not establish DATA containment. An independent review rejected that candidate under the [plan's timing condition](ieee80211-compressed-blockack-replacement.md#airtime-acceptance-and-admission-dependencies). The later correction and C3 acceptance below supersede that rejection.
+
+The [separate correction proposal](ieee80211-compressed-blockack-txop-admission.md) addresses preparation and admission. A final transmit guard alone cannot solve the problem. Aggregation already deletes constituent frames before HCF selects the actual mode. An oversized aggregate can therefore remain prepared and fail every later attempt. The proposal must preserve progress through ordinary MSDUs and the permitted initial-MSDU exception.
+
+## Focused verification after source review
+
+The reviewed debug library has SHA-256 `1635793563ff1c0912a4a0e55a70507879af2a20ef83a0b88502562748a347db`. Its build log is `/tmp/inet-s5-reviewed-debug-build.log`. The interframe fixture and direct run 6 reproduction use this library. The earlier mixed-interface regression batch is discarded.
+
+The unit and legacy results below use their recorded fresh libraries before the final bounded PHY review corrections. Those corrections repair empty-cache lookup, remove an unused virtual, and expose a watch. The protocol campaign uses library `1c57b8f562dbeb5cddbba49b77a3a676042fea7aef9b9e5578e1980c63109f56`. These earlier results retain their recorded source boundaries; they are not new executions against the final relink.
+
+| Check | Result | Evidence |
+| --- | --- | --- |
+| Focused units | Nine fixtures pass. | `/tmp/inet-s5-final-unit.log` |
+| HT Operation conversion | One fixture passes both RIFS permission values and operation equality checks. | `/tmp/inet-s5-ht-operation-unit.log` |
+| Legacy and HCF input modules | Thirty fixtures pass across the main batch and two repaired-fixture reruns. | `/tmp/inet-s5-final-module.log`, `/tmp/inet-s5-repaired-module.log` |
+| EDCA DATA lifetime modules | Nine fixtures pass. | `/tmp/inet-s5-lifetime-module.log` |
+| Production interframe fixture | All 28 runs pass with seed zero. | `/tmp/inet-s5-interframe-final/` |
+| Constructed serializers | All 278 cases pass. | `/tmp/inet-s5-serializer-roundtrip.log` |
+| n/ac protocol fixtures | Five simulations pass with Basic and Compressed coverage. | `/tmp/inet-s5-exchange-protocol-final.log` |
+| Lowest response mode | Two real infrastructure runs pass at 6 Mbit/s. | `/tmp/inet-s5-exchange-lowest-config.out` |
+
+The two legacy fixtures required valid synthetic frames. The Action-dispatch fixture now supplies the BAR trailer. The fragment-retirement fixture now supplies all 64 fragment bitmap entries. Their behavioral assertions remain intact.
+
+The interframe fixture supplies accepted agreements. It uses real PHY reception and HCF ACK completion. It verifies SIFS and RIFS bounds, sequence wrap, prior receipt truth, unrelated traffic, retirement, and cache expiry. Its BUSY control uses a signal below reception sensitivity but above energy detection. Its 168 malformed BAR receptions each leave zero pending candidates. Separate infrastructure tests establish real association and ADDBA; the interframe fixture does not claim negotiation coverage.
+
+The source reviewers report no remaining confirmed correctness defect in their bounded core and interframe scopes. Architecture gates pass for the changed MAC and PHY subtrees. Review supplies no runtime proof beyond the executed fixtures.
+
+## Extension contracts in this candidate
+
+These obligations describe the current candidate. They require migration guidance before publication. They do not change the C3 blocker or activate the default.
+
+| Contract | Obligation for a custom implementation |
+| --- | --- |
+| `IQosRateSelection::computeCompressedBlockAckReqMode()` | Select a legal non-HT BAR mode from the supplied initial-or-later TXOP position and current rate sets. |
+| `IRecipientQosAckPolicy::computeCompressedBlockAckDurationField()` | Subtract SIFS and the actual 32-byte response airtime from the request reservation. Preserve the Basic method. |
+| `IRecipientQosMacDataService::dataFrameReceived()` | Accept the new interframe argument. Preserve its meaning through recipient processing. A false value supplies no interval evidence. |
+| `QoSContext` | Supply HCF's resolved profile to the exchange. Existing callers retain the Basic default unless they explicitly supply Compressed. |
+| `RecipientBlockAckAgreement::setVariant()` | Set the profile during agreement creation. Do not change it during that agreement's lifetime. Compressed creation initializes explicit receipt state. |
+| `IRadioMedium` | Implement the new snapshot and gap queries with copied physical facts. Unavailable transmissions or arrival endpoints produce an absent result. |
+| `ICommunicationCache::findCachedArrival()` | Return a cached arrival without insertion, resizing, geometry recalculation, or other state changes. |
+| `IIeee80211Mode::getSignalExtensionTime()` | Supply the nominal extension for the mode. `Ieee80211ModeBase` supplies zero; ERP and HT override it where necessary. |
+
+## Common regression result
+
+All nineteen selected rows match their recorded non-graphical fingerprints. The runner uses debug mode and the original configuration, run, time limit, and requested non-graphical ingredients. It completes in 182.173 seconds with exit status zero. The selected input is `/tmp/inet-s5-fingerprint-selection.csv`; the log is `/tmp/inet-s5-fingerprint.log`.
+
+The command runs from `tests/fingerprint`:
+
+```sh
+./fingerprinttest -d \
+ -m '(showcases/wireless/(blockack|aggregation|fragmentation|qos|txop)|examples/(wireless/qos|adhoc/qos))' \
+ -f tplx -f '~tNl' -f '~tND' -t 6 \
+ /tmp/inet-s5-fingerprint-selection.csv
+```
+
+The graphical `tyf` ingredient remains unverified. No fingerprint baseline changed. These matches preserve the selected legacy regression evidence. They do not override the direct Compressed timing failure.
+
+These initial regression matches precede the separate TXOP correction and C4. No commit or publication occurred during this execution.
+
+## Build state before the authorized TXOP correction
+
+Both debug and release builds pass. The final release freshness check uses `make MODE=release -j6` from the repository root. Its log is `/tmp/inet-s5-reviewed-release-build.log`. The final debug build uses `make MODE=debug -j6`; its log is `/tmp/inet-s5-reviewed-debug-build.log`. The later NED edit corrects a comment and retains the Basic default.
+
+The separate TXOP proposal passed a consistency review as a scope request. On 2026-10-16, the maintainer authorized that correction and subsequent S5 completion. Source validation and independent review resolved the RTS boundary, lifetime ownership, and mode validation before implementation. The retained strict fixture preserves the inherited outside-agreement failure separately from active Compressed acceptance.
+
+## Authorized TXOP correction and C3 acceptance
+
+The [TXOP contract](ieee80211-compressed-blockack-txop-admission.md#resolved-implementation-contract) defines source ownership and each event boundary. The service creates duplicate previews without source removal. HCF selects one DATA mode, evaluates the aggregate, and evaluates an ordinary fallback with that same mode. Commitment transfers the same preview pointer only before DATA submission. Refusal preserves queue frames, deadlines, sequence state, and allocation state.
+
+RTS failure before DATA updates QSRC and the contention window without a provisional sequence counter. Original MSDU lifetimes bound repeated attempts. Expiry, capability loss, or changed response inputs cancel invalid preparation. Single protection reserves only the current exchange. A later BAR can require fresh contention. Basic and traffic outside active Compressed agreements retain their existing preparation and admission.
+
+Runtime verification found two effective selection callers that bypassed the first implementation. `RtsCtsFs` still extracted originals after admission. The multicast repeat predicate could also extract a prepared unicast successor before it ended the group sequence. Both callers now use the context-selected frame. Their focused regression cases preserve original queue state when no DATA submission occurs.
+
+The first retry policy required containment for all committed frames. A real BAR collision exposed a progress failure at 6 Mbit/s. One ordinary protected exchange needs 1.166 milliseconds under a nominal one-millisecond limit. Its first attempt qualifies for an exception, but conservative retry containment refused every later attempt. The implementation now uses the standard same-size single-MPDU exception with no earlier Data or Management frame. The supported path preserves committed MPDU size across retries.
+
+New previews clear Retry from the previous wireless hop. Committed retries derive Retry from local acknowledgment state. An oversized first-attempt A-MSDU still cannot use the retry exception. Internal collision commits only ordinary queued DATA before its retained recovery action. Multiple protection reserves the actual complete exchange for a permitted overrun.
+
+The corrected debug library has SHA-256 `0298dbd4c9625cd6f306865f5c1cfd0a3412230c6339b3a84289eb68eba713df`. The release library has SHA-256 `4b32335cdb2f558d60ada0761e46fde84c9879889e1c46b3c3f69bd11d9ae780`. Debug build 4 and the release build pass. Their logs are `/tmp/inet-s5-txop-debug-build-4.log` and `/tmp/inet-s5-txop-release-build.log`.
+
+The focused admission fixture passes all 24 cases with seed zero. It checks refusal, ordinary fallback, ownership, deadlines, sequence state, one mode query, and RTS cancellation. It also checks QSRC rollover, lifetime expiry, capability changes, internal collision, and multicast-to-unicast refusal. Cases 22 and 23 require actual ordinary and A-MSDU retries with unchanged size and DATA airtime above one millisecond. Evidence is `/tmp/inet-s5-admission24.out` and `/tmp/inet-s5-admission24.err`.
+
+All sixteen explicit-profile infrastructure cases pass the scoped timing assertions. The observer records agreement scope at DATA submission, before later teardown can change lookup results. Active Compressed checks include DATA with Normal Ack. No active later DATA or ACK overrun remains. All 98 matched BAR/Block Ack exchanges finish within their bounds. All 32 hop summaries report zero Block Ack overruns.
+
+The former run 6 failure now passes with zero DATA, ACK, or Block Ack overruns on both hops. Those hops transmit 23 and 11 active aggregates. The separate two-run lowest-response campaign records 29 actual 32-byte responses at 6 Mbit/s. Their airtime is 74 microseconds, with exactly 10 microseconds of SIFS. No Block Ack response exceeds its bound.
+
+The infrastructure observer separately records inherited violations outside active agreements. `StrictAllTiming` retains the original aggregate assertions and reproduces the pre-agreement aggregate failure in run 4. This acceptance establishes no timing compliance for those retained paths. The default change does not repair Basic or outside-agreement admission.
+
+The cleanup assertion permits only the idle `WAITING_FOR_SUCCESSOR` procedure with `SIMTIME_MAX` response deadline. Queues, in-progress frames, agreements, pending BAR state, and agreement timers must remain empty or inactive. That phase records a possible future setup after a completed Normal Ack transfer. It holds no unresolved exchange.
+
+Independent review accepts C3 for this scope and permits C4. The verdict is `/tmp/inet-s5-c3-acceptance-review.md`. Explicit-profile evidence is `/tmp/inet-s5-exchange-scoped-general16.out` and `/tmp/inet-s5-exchange-scoped-lowest2.out`. The scope table is `/tmp/inet-s5-exchange-scoped-general16.csv`. The unchanged strict failure remains in `/tmp/inet-s5-exchange-strict-all-inherited.out` and its `.err` file.
+
+## C4 default and migration verification
+
+`Hcf.ned` now defaults to Compressed. The infrastructure fixture omits the selector after C3 and requires actual Compressed frames. All sixteen default cases pass. Both lowest-response cases also pass after default activation. Evidence is `/tmp/inet-s5-default-exchange16.out` and `/tmp/inet-s5-default-lowest2.out`.
+
+All six external-style migration cases pass without an explicit selector. Legacy g and a links, and HT ad hoc links without peer records, deliver twelve UDP payloads through Normal Ack. Support-disabled controls produce no irrelevant warning. The fixture checks the resolved default, fallback diagnostics, absent ADDBA/BAR/Block Ack, and empty active state. Evidence is `/tmp/inet-s5-default-migration6.out`.
+
+All twelve initialization cases pass after default activation. They cover the omitted selector, explicit Basic, explicit Compressed, unknown values, and both delayed-policy guards. Evidence is `/tmp/inet-s5-default-variant12.out`. Retained Basic fragment, bitmap, and public API checks remain separate regression obligations.
+
+Both `N_BlockAck` and `Ac_BlockAck` also pass three consumer runs: explicit Basic, explicit Compressed, and the NED default. The third run uses bare INI `default`, which invokes OMNeT++ `acceptDefault()`. It verifies the resolved profile at all three HCF instances. Actual Compressed BAR/Block Ack and positive receipt bits complete that run. All six simulations and both diagnostic scripts pass. Evidence is `/tmp/inet-s5-default-consumer-protocol6.log` and `/tmp/inet-s5-default-consumer-{n3,ac3}.out`.
+
+Both final build freshness checks pass after the NED change. Their logs are `/tmp/inet-s5-c4-debug-build.log` and `/tmp/inet-s5-c4-release-build.log`. The C4 change affects the runtime NED default; the recorded debug and release library hashes remain unchanged.
+
+## Final regression evidence
+
+All final checks use the active checkout and seed zero where a simulation requires a seed. Debug mode retains runtime assertions. The release build supplies a separate compilation check. No fingerprint expectation changed.
+
+| Check | Final result | Evidence |
+| --- | --- | --- |
+| Unit fixtures | All ten pass. | `/tmp/inet-s5-c4-unit.log` |
+| Module regression batch | Forty-one pass initially. The retained Basic deferral fixture passes after its explicit profile correction. All 42 selected fixtures therefore pass across the batch and focused rerun. | `/tmp/inet-s5-c4-module-regression.log`, `/tmp/inet-s5-c4-deferral-module.log` |
+| Default infrastructure and migration | Both additional module fixtures pass: sixteen infrastructure cases and six migration cases. Both lowest-response cases pass. | `/tmp/inet-s5-default-module.log`, `/tmp/inet-s5-default-lowest2.out` |
+| Serializer | All 278 constructed cases pass, with zero unexpected or stale coverage entries. The separate golden-octet unit also passes. | `/tmp/inet-s5-c4-serializer.log`, `/tmp/inet-s5-c4-unit.log` |
+| Wi-Fi protocol files | Five supported files pass. Four deferred files retain expected failures. No unexpected result occurs. | `/tmp/inet-s5-default-plan-protocol9.log`, `/tmp/inet-s5-default-protocol-deferred.txt` |
+| Fingerprint rows | All nineteen selected rows match in 153.811 seconds. | `/tmp/inet-s5-c4-fingerprint.log` |
+| Architecture | All three scoped checks pass. | `/tmp/inet-s5-c4-architecture-mac.log`, `/tmp/inet-s5-c4-architecture-common.log`, `/tmp/inet-s5-c4-architecture-wifi.log` |
+
+The module union contains 44 distinct fixtures. It preserves Basic teardown, fragmentation, NAV, setup, association reset, response modes, and lifetime checks. It repeats the 28 physical interframe cases, 24 admission cases, and 76 scheduling cases after default activation. The scheduling fixture retains all original Basic cases. It adds actual Compressed shifted responses through active frame sequences and small-window arbitration.
+
+The recipient integration checks DATA 100, 102, and 101 through HCF. Delivery order is 100, 101, and 102. A subsequent BAR produces three positive receipt bits and zero holes after delivery. The six focused originator cases call the HCF control boundary directly. The scheduling fixture separately supplies actual response matching. These fixtures supply accepted agreements; the infrastructure and protocol fixtures prove negotiation separately.
+
+The legacy deferral fixture omitted a selector while it used non-HT mode `a`. The Compressed default correctly prevented its expected Basic setup. The fixture now selects Basic explicitly. All twelve original scenarios pass without assertion changes. This correction preserves the fixture's intended historical profile.
+
+The deferred protocol results remain explicit. `N_Ampdu` and `Ac_Ampdu` first miss an A-MPDU subframe. `Ac_MultiTidBa` passes both TID observations before it misses Multi-TID Block Ack. `WifiQosDelayedBa` observes a delayed request before recipient delayed acceptance fails. No expectation changed, and these results establish no support for those features.
+
+The interface gates report two MAC findings and three PHY findings. Their output is byte-identical to the pinned parent's output. The implicated source files remain unchanged. Logs are `/tmp/inet-s5-c4-interfaces-mac.log` and `/tmp/inet-s5-c4-interfaces-phy.log`, with corresponding `/tmp/inet-s5-parent-interfaces-*.log` files. This work adds no interface-gate finding.
+
+The final unit command runs from the checkout root:
+
+```sh
+inet_run_unit_tests -m debug -f '(Ieee80211BlockAck(Wire|Scheduling|ActionWire)_1|Ieee80211CompressedBlockAck(Rate|Record)_1|Ieee80211(ResponseModeSelection|ResponseTimeout|HtModeSet|HtCapabilities|VhtHtCapabilities)_1)\.test$'
+```
+
+The module regression command runs from the checkout root:
+
+```sh
+inet_run_module_tests -m debug -f 'Ieee80211(BlockAck.*|EdcaDataLifetime.*|PreparedCancellation_1|RecipientNav_1|Txnav_1|Hcf(SingleProtection_1|MultipleProtection_[12]|Header_1)|SingleProtectionGroup_1|VhtHtSupport_1|HtAssociation_1|Mgmt(StateCommit|RatePublication)_1|CompressedBlockAck(Input|Interframe|Admission|Originator)_1)\.test$'
+inet_run_module_tests -m debug -f 'Ieee80211BlockAckDeferral_1\.test$'
+inet_run_module_tests -m debug -f '(Ieee80211CompressedBlockAckExchange_1|Ieee80211CompressedBlockAckMigration_1|Ieee80211BlockAckVariant_1)\.test$'
+```
+
+The serializer and protocol commands run from the checkout root:
+
+```sh
+inet_run_serializer_tests -m debug -f 'serializer_chunk_roundtrip\.test$'
+inet_run_protocol_tests -m debug -w '^tests/protocol/wifi$' -f '(N_BlockAck|N_CompressedBa|N_Amsdu|N_Ampdu|Ac_BlockAck|Ac_BlockAckFallback|Ac_Ampdu|Ac_MultiTidBa|WifiQosDelayedBa)\.test$'
+inet_run_protocol_tests -m debug -w '^tests/protocol/wifi$' -f '(N_BlockAck|Ac_BlockAck)\.test$'
+```
+
+The final fingerprint command repeats the nineteen-row command above from `tests/fingerprint`. It uses the original configurations, runs, time limits, and selected non-graphical ingredients. Graphical `tyf` remains unverified. No CSV or other recorded baseline changed.
+
+Current user and migration guides describe the default, fallback, profile conflict, fragmentation boundary, and added interface obligations. Release notes preserve the supported `ac` path and all four deferred protocol requirements. The Basic showcase retains its explicit profile, topology, and result rows.
+
+The evidence remains limited to the recorded deterministic cases and seed. Production HT catalogs provide no distinct legal alternate response for the tested 32-byte frame. The selector unit verifies that alternate-rate contract separately. The recipient prefix release remains an interpretation of ordered delivery together with the individual-MPDU release condition. This work establishes no complete historical Basic conformance or complete VHT transfer conformance.
+
+The implementation checks above precede the separate commit, push, and pull request phase. The user authorized that phase after implementation. The original local plan changes and untracked history remain outside the publication scope.
diff --git a/plan/pending/ieee80211-compressed-blockack-txop-admission.md b/plan/pending/ieee80211-compressed-blockack-txop-admission.md
new file mode 100644
index 00000000000..6664e786d49
--- /dev/null
+++ b/plan/pending/ieee80211-compressed-blockack-txop-admission.md
@@ -0,0 +1,121 @@
+# TXOP prerequisite: admit Compressed DATA before destructive preparation
+
+This correction is complete and removes the active Compressed timing blocker. The [replacement plan](ieee80211-compressed-blockack-replacement.md#excluded-features-and-fragmentation) excludes complete-cost TXOP admission from its original scope. On 2026-10-16, the maintainer authorized this separate correction and subsequent S5 completion. The resolved contract below passed source validation and independent review before production edits. The [S5 execution record](ieee80211-compressed-blockack-s5-execution.md#authorized-txop-correction-and-c3-acceptance) supplies the final runtime evidence and scope limits.
+
+## Problem and required result
+
+The final S5 candidate sends two later A-MSDUs beyond their TXOP bounds in a supported case. The exact parent has no such violation in that case. The [S5 execution record](ieee80211-compressed-blockack-s5-execution.md#confirmed-timing-blocker) supplies the comparison and its limits. IEEE Std 802.11-2024, Clause 10.23.2.9 does not permit the observed continuation.
+
+The correction must establish the complete exchange cost before DATA commitment. An aggregate must fit its applicable bound. A later ordinary MSDU must also fit. The correction must retain the permitted initial unaggregated MSDU exception. It must not strand an oversized aggregate until lifetime expiry.
+
+A zero TXOP limit permits one SU PPDU with one MSDU or A-MSDU, plus its required protection and response. It does not mean zero permitted airtime. With a positive limit, the initial unaggregated MSDU exception permits at most one Data or Management frame. RTS and CTS airtime counts toward the exchange cost, but those Control frames do not increase that count. A same-size retransmission has a separate permitted exception under Clause 10.23.2.9; the implementation contract must state its retry policy.
+
+Hypothetical example: a TXOP has 200 microseconds left. The proposed aggregate needs 400 microseconds, while its first ordinary MSDU needs 150 microseconds. The service must commit that ordinary MSDU and leave the other MSDUs queued. If neither form qualifies, the sequence must end without queue removal or sequence assignment.
+
+The initial implementation scope covers active Compressed agreements. Explicit Basic and Normal Ack fallback outside an agreement retain their current preparation paths. This boundary avoids an unrelated compatibility repair. It does not claim that those retained paths satisfy every TXOP constraint.
+
+## Confirmed cause
+
+The [data service](../../src/inet/linklayer/ieee80211/mac/originator/OriginatorQosMacDataService.cc) removes aggregation candidates before HCF selects the actual DATA mode. The aggregation builder deletes the constituent frames. The service replaces their lifetime records and assigns a sequence number. At that point, a final HCF refusal cannot recover the original queue form.
+
+The [frame sequence](../../src/inet/linklayer/ieee80211/mac/framesequence/HcfFs.cc) tests whether positive TXOP time remains. That test does not prove that a complete later exchange fits. HCF excludes Block Ack support from its existing complete-cost DATA guard. A guard at final submission therefore needs a preparation correction as well.
+
+AARF and Onoe can change state when the rate selector queries them. Separate mode queries for a preview, RTS protection, and DATA submission can produce different decisions. The cost calculation must use the actual selected mode without extra rate-control queries.
+
+## Proposed ownership and sequence
+
+| Step | Responsible component and proposed action | Expected result and verification |
+| --- | --- | --- |
+| 1. Prepare without commitment. | The data service obtains borrowed candidates from the aggregation policy. It constructs a preview from duplicates. | Queue ownership, deadlines, and sequence counters remain unchanged. A focused fixture records them before and after refusal. |
+| 2. Select one DATA mode. | HCF selects the mode once for this preparation. The frame-sequence context owns that decision and the preparation state. | Aggregate evaluation, ordinary-MSDU evaluation, protection, and submission use one mode decision. A test rate control counts queries. |
+| 3. Calculate complete cost. | HCF derives the applicable IFS, DATA duration, protection, and immediate response from their existing owners. | Admission checks the TXOP bound and the applicable TXNAV bound. PHY mode objects supply durations. |
+| 4. Select a permitted form. | HCF first evaluates the aggregate. If it fails, HCF evaluates the selected ordinary MSDU with the same mode. | An oversized aggregate does not cause permanent refusal. The ordinary MSDU can fit or qualify for the initial-MSDU exception. |
+| 5. Defer when necessary. | The frame sequence ends normally when neither form qualifies. | The queue remains intact. Refusal causes no DATA retry outcome, sequence assignment, or aggregate lifetime replacement. |
+| 6. Commit at a safe boundary. | The data service commits only the accepted form. HCF uses its retained mode for the actual exchange. | Extraction, lifetime replacement, and sequence assignment occur once. No committed aggregate waits indefinitely because its form cannot fit. |
+
+BAR arbitration must precede DATA preparation, as S4 requires. The correction adds no BAR forecast. A final BAR can obtain a later TXOP through the existing arbitration path.
+
+The existing final false-transmit result remains a safety check. The frame-sequence handler removes a refused step and ends the sequence without a transmission-failure result. Normal admission refusal must occur before destructive source commitment.
+
+## Boundary requirements
+
+RTS creates an event boundary between preparation and DATA submission. A capability update, lifetime expiry, or RTS failure can occur before DATA starts. The implementation contract must identify the owner of each preview and original frame throughout that interval.
+
+The resolved contract retains reversible preparation until the first DATA submission. A failed RTS or invalidated capability discards the preview and its selected mode. Original queue frames retain their existing deadlines and sequence state. A later attempt obtains a new preparation and mode decision. The source trace established that this boundary fits the existing ownership and lifetime contracts.
+
+The frame-sequence API must distinguish preparation refusal from an empty queue and a failed transmission. `InProgressFrames` must not convert those outcomes into a DATA retry. The integration must preserve S4's preview and BAR-first contracts.
+
+Admission refusal causes no DATA retry outcome. An actual RTS failure retains its required recovery behavior while the service discards reversible preparation. The implementation contract must keep those paths separate.
+
+For DATA under an active Block Ack agreement, an actual RTS failure updates QSRC and the contention window. QSRC is the short retry counter for an access category. Clause 10.23.2.12.1 applies its associated-RTS increment rule to MSDUs and A-MSDUs outside a Block Ack agreement. A preview must not create a sequence-keyed counter before sequence assignment. QSRC rollover therefore does not discard queued DATA; its lifetime still bounds repeated attempts. This changes INET's former per-frame RTS discard behavior on this scoped path. Basic and Normal Ack outside an agreement retain their existing recovery.
+
+At the preceding exchange completion, TXNAV admission compares complete exchange cost strictly below the remaining TXNAV. That cost excludes the leading IFS. Positive-limit TXOP containment includes the leading IFS and permits equality with the bound. Clauses 10.23.2.8 and 9.2.5.2 support these separate comparisons. The correction must not move the strict TXNAV comparison to a later reference that rejects an exact single-protection reservation.
+
+The correction must not retain an illegal mode after peer capabilities change. It must also avoid a second adaptive-rate query merely to validate an unchanged prepared decision. The implementation contract must specify that validation separately from mode selection.
+
+These are engineering checks for the approved scope. They do not require the maintainer to choose a cache format or callback signature. If source analysis needs a different behavior or a wider compatibility change, the owner must update this proposal first.
+
+## Resolved implementation contract
+
+The data service owns duplicate previews through `PreparedQosData`. Each preparation records original frame IDs and its source queue. It retains no original frame pointer across events. `prepareFrame(queue, allowAggregation)` creates one form without extraction. HCF first requests the aggregate form. If that form fails admission, HCF requests the ordinary form with the same DATA mode.
+
+`QoSContext` owns the selected preparation and its retained modes. HCF supplies a preparation callback with three outcomes: `LEGACY`, `READY`, and `DEFERRED`. `HcfFs` calls it after BAR arbitration and before extraction. Refusal ends the repeat normally. Legacy contexts retain their existing extraction route.
+
+`isPreparationValid()` checks source membership, eligibility, deadlines, next sequence, and A-MSDU permission. HCF separately validates every retained mode and response input through pure queries. These checks include mode-set changes. A changed input cancels preparation without another adaptive DATA query. HCF repeats validation before RTS and before DATA submission.
+
+`commitPreparedFrame()` removes the accepted sources only before first DATA submission. It transfers the same preview pointer to `InProgressFrames`. Ordinary commitment transfers the original absolute deadline. Aggregate commitment starts the existing A-MSDU lifetime. Commitment assigns one sequence number and allocation index. Frame-sequence steps retain valid borrowed pointers until their disposal.
+
+```mermaid
+flowchart TD
+ Queue[Original MSDUs remain queued.] --> Preview[The service creates duplicate previews.]
+ Preview --> Admission{Does a permitted form qualify?}
+ Admission -->|No| Refuse[HCF ends the sequence without commitment.]
+ Admission -->|Yes| Protection{Does DATA require RTS?}
+ Protection -->|No| Validate{Are sources and retained modes valid?}
+ Protection -->|Yes| RTS[HCF submits RTS with the retained decision.]
+ RTS -->|CTS succeeds| Validate
+ RTS -->|CTS fails for valid sources| Recovery[Recovery updates QSRC and CW.]
+ RTS -->|Sources become invalid| Discard
+ Recovery --> Discard[Context disposal deletes the preview.]
+ Validate -->|No| Discard
+ Validate -->|Yes| Commit[The service commits the same preview pointer.]
+ Commit --> Data[HCF submits DATA with its retained mode.]
+```
+
+Expiry during CTS wait can remove originals safely because preparation stores their IDs. An invalid preparation ends without stale DATA or a synthetic RTS failure. A valid preparation with failed CTS causes one actual RTS recovery outcome. Context disposal deletes unused previews after step disposal.
+
+The scoped single-protection path reserves only its current exchange. It makes no forecast for unprepared successor DATA. A later BAR can contend through the existing S4 route. Multiple protection retains its reservation formula. This choice can increase contention and change simulation trajectories for active Compressed agreements.
+
+Committed retransmissions use the same-size single-MPDU exception from Clause 10.23.2.9 when no earlier Data or Management frame occurred in the TXOP. This exception can include a retransmitted A-MSDU. It does not permit an oversized first-attempt A-MSDU. The supported Compressed path has no fragmentation or A-MPDU operation. Its committed MPDU retains its size across retries; Retry and Duration field changes do not change that size. Basic and outside-agreement fallback retain their existing paths.
+
+A bounded LLDB observation established why conservative retry containment cannot satisfy the supported exchange. AP DATA at 6 Mbit/s needs a 1.166-millisecond protected exchange within a nominal one-millisecond TXOP. Its first ordinary transmission qualifies for the initial exception. A real BAR collision makes that DATA retry eligible. Conservative containment then refuses every attempt despite the permitted same-size exception. The root validated the required policy correction before source edits. Evidence is `/tmp/inet-s5-txop-diagnostic-lldb-cost.out`.
+
+Internal collision has a separate preparation entry. The existing HCF branch extracts DATA before it updates collision recovery. On the scoped queued path, HCF instead commits one ordinary preview before that existing recovery action. It must not create an aggregate before admission. Other queued MSDUs retain their ownership and deadlines. An unsent ordinary MSDU still qualifies for the first-transmission exception when its conditions hold.
+
+Multiple protection must reserve the actual exchange when a permitted initial ordinary MSDU or same-size retransmission exceeds a positive limit. The scoped path uses the existing helper's current-exchange form for that case. It must not reduce protection to zero merely because DATA exceeds the limit. The helper's reservation formula remains unchanged.
+
+The root validated this contract against the current source. An independent review found no unresolved blocking gap. This contract review supplies no runtime proof. Production implementation and focused verification follow this gate.
+
+## Proposed verification
+
+The direct reproduction uses `Ieee80211CompressedBlockAckExchange_1.test`, General run 6, seed zero. It selects HT operation, 24 Mbit/s DATA, single protection, aggregation, and a one-millisecond TXOP. The candidate must eliminate both 310-microsecond continuation overruns. The raw comparison remains in `/tmp/inet-s5-exchange-parent-candidate-comparison.json`.
+
+Add a focused preparation fixture at the proposed path `tests/module/Ieee80211CompressedBlockAckAdmission_1.test`. That fixture does not yet exist. Its cases must establish these results:
+
+- An initial aggregate that cannot fit becomes an ordinary MSDU before commitment.
+- A permitted initial unaggregated MSDU can exceed the TXOP limit under the applicable exception.
+- A smaller ordinary MSDU can replace an aggregate when it fits the remaining interval.
+- Refusal preserves queue ownership, deadlines, and sequence counters when neither form qualifies.
+- RTS failure, lifetime expiry, and capability changes discard invalid preparation without stale DATA transmission.
+- RTS protection, DATA duration, and admission consume one selected mode.
+- Basic controls preserve their existing behavior.
+
+Repeat S5's complete infrastructure campaign after the correction. Retain the lowest-response cases and exact BAR/Block Ack bounds. Repeat the affected lifetime, setup, frame-sequence, and fragmentation fixtures. Repeat the nineteen selected fingerprint rows without a baseline update.
+
+C3 can pass only after the supported corrected exchanges meet their applicable bounds. C4 and default activation must follow that result. This proposal grants no publication or fingerprint-baseline authority.
+
+## Execution result
+
+All 24 focused admission cases pass. All sixteen infrastructure cases pass the active Compressed timing assertions. Both lowest-response cases pass. Independent review accepts C3. C4 activates the default only after that decision and repeats its required checks.
+
+The former run 6 violation no longer occurs on either hop. Outside-agreement admission retains its inherited limits. `StrictAllTiming` preserves their separate reproduction. Basic settings and recorded fingerprint expectations remain unchanged. The final nineteen-row fingerprint campaign passes without a baseline update.
diff --git a/showcases/wireless/blockack/doc/index.rst b/showcases/wireless/blockack/doc/index.rst
index 1418ec4a985..b4808ed7c33 100644
--- a/showcases/wireless/blockack/doc/index.rst
+++ b/showcases/wireless/blockack/doc/index.rst
@@ -50,7 +50,7 @@ Block Acknowledgment in INET
In INET, the block ack mechanism can be enabled by setting the :par:`isBlockAckSupported`
parameter of the MAC's :ned:`Hcf` submodule to ``true`` (it is disabled by default).
-Select ``blockAckVariant = "basic"`` on both endpoints to retain the historical profile. Basic remains the default during preparation for Compressed support. This stage rejects ``"compressed"`` because its runtime exchange path does not yet exist. HCF rejects unknown selector values during initialization, also when Block Ack support is disabled.
+This showcase selects ``blockAckVariant = "basic"`` on both endpoints to retain the historical profile. HCF defaults to Compressed for other configurations. Compressed agreements require usable local and peer HT state. Without that state, DATA uses Normal Ack. HCF rejects unknown selector values during initialization, also when Block Ack support is disabled.
Several submodules of :ned:`Hcf` are involved in the block ack mechanism.
These modules have parameters controlling the block ack behavior. The following paragraphs
diff --git a/src/inet/linklayer/ieee80211/mac/Ieee80211DataSequenceTag.msg b/src/inet/linklayer/ieee80211/mac/Ieee80211DataSequenceTag.msg
new file mode 100644
index 00000000000..3d586aad6c0
--- /dev/null
+++ b/src/inet/linklayer/ieee80211/mac/Ieee80211DataSequenceTag.msg
@@ -0,0 +1,17 @@
+//
+// Copyright (C) 2026 OpenSim Ltd.
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+
+import inet.common.INETDefs;
+import inet.common.TagBase;
+
+namespace inet::ieee80211;
+
+// Local allocation order before fragmentation. All fragments retain this index.
+// Unlike the wire sequence number, the index does not wrap at 4096.
+class Ieee80211DataSequenceTag extends TagBase
+{
+ int64_t allocationIndex = -1;
+}
diff --git a/src/inet/linklayer/ieee80211/mac/Ieee80211Frame.msg b/src/inet/linklayer/ieee80211/mac/Ieee80211Frame.msg
index e139deaf75d..7d1367944d4 100644
--- a/src/inet/linklayer/ieee80211/mac/Ieee80211Frame.msg
+++ b/src/inet/linklayer/ieee80211/mac/Ieee80211Frame.msg
@@ -43,6 +43,8 @@ const b LENGTH_ADDBAREQ = LENGTH_MGMT + B(9); // mgmt length + action body lengt
const b LENGTH_ADDBARESP = LENGTH_MGMT + B(9); // mgmt length + action body length
const b LENGTH_DELBA = LENGTH_MGMT + B(6); // mgmt length + action body length
const b LENGTH_BASIC_BLOCKACK = B(16 + 2 + (2 + 128) + 4); // header + ba control + ba information + fcs
+const b LENGTH_COMPRESSED_BLOCKACK = B(16 + 2 + 2 + 8 + 4); // header + control + sequence + bitmap + FCS
+const b LENGTH_BLOCKACKREQ = B(16 + 2 + 2 + 4); // header + control + sequence + FCS
const b DATAFRAME_HEADER_MINLENGTH = B(2 + 2 + 3 * 6 + 2); //bits without QoS, without Address4: 2 + 2 + 3*6(addresses) + 2
const b QOSCONTROL_PART_LENGTH = b(2 * 8); // QoS Control field length (bits)
const short int MAX_NUM_FRAGMENTS = 16;
@@ -356,7 +358,7 @@ class Ieee80211BlockAckReq extends Ieee80211TwoAddressHeader
class Ieee80211BasicBlockAckReq extends Ieee80211BlockAckReq
{
- chunkLength = B(16 + 2 + 2); // header + BAR control + BAR information
+ chunkLength = LENGTH_BLOCKACKREQ - B(4);
int tidInfo @bit(4); // The TID_INFO subfield of the BAR Control field of the Basic BlockAckReq frame contains the TID for which a Basic BlockAck frame is requested.
@@ -373,7 +375,7 @@ class Ieee80211BasicBlockAckReq extends Ieee80211BlockAckReq
class Ieee80211CompressedBlockAckReq extends Ieee80211BlockAckReq
{
- chunkLength = B(16 + 2 + 2); // header + BAR control + BAR information
+ chunkLength = LENGTH_BLOCKACKREQ - B(4);
int tidInfo @bit(4); // The TID_INFO subfield of the BAR Control field of the Compressed BlockAckReq frame contains the TID for which a BlockAck frame is requested.
// The BAR Information field of the Compressed BlockAckReq frame contains the Block Ack Starting
@@ -455,7 +457,7 @@ class Ieee80211BasicBlockAck extends Ieee80211BlockAck
//
class Ieee80211CompressedBlockAck extends Ieee80211BlockAck
{
- // chunkLength TODO
+ chunkLength = LENGTH_COMPRESSED_BLOCKACK - B(4);
multiTid = 0;
compressedBitmap = 1;
diff --git a/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.cc b/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.cc
index b864c39f435..5db40af167f 100644
--- a/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.cc
+++ b/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.cc
@@ -34,6 +34,13 @@
namespace inet {
namespace ieee80211 {
+void Ieee80211Mac::resetPeerAgreements(const MacAddress& peer)
+{
+ Enter_Method("resetPeerAgreements");
+ if (hcf)
+ hcf->resetPeer(peer);
+}
+
using namespace inet::physicallayer;
Define_Module(Ieee80211Mac);
diff --git a/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.h b/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.h
index 25d1b25fe65..0b28e4a8bc0 100644
--- a/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.h
+++ b/src/inet/linklayer/ieee80211/mac/Ieee80211Mac.h
@@ -11,6 +11,7 @@
#include "inet/common/ModuleRefByPar.h"
#include "inet/linklayer/base/MacProtocolBase.h"
#include "inet/linklayer/ieee80211/mac/contract/IDs.h"
+#include "inet/linklayer/ieee80211/mac/contract/IBlockAckAgreementLifecycle.h"
#include "inet/linklayer/ieee80211/mac/contract/IRateControl.h"
#include "inet/linklayer/ieee80211/mac/contract/IRateSelection.h"
#include "inet/linklayer/ieee80211/mac/contract/IRx.h"
@@ -35,7 +36,7 @@ class Ieee80211MacHeader;
* exact operation of the MAC depend on the plugged-in components (see IUpperMac,
* IRx, ITx, IContention and other interface classes).
*/
-class INET_API Ieee80211Mac : public MacProtocolBase
+class INET_API Ieee80211Mac : public MacProtocolBase, public IBlockAckAgreementLifecycle
{
public:
static simsignal_t frameTransmissionOutcomeSignal;
@@ -106,6 +107,7 @@ class INET_API Ieee80211Mac : public MacProtocolBase
virtual void sendUpFrame(Packet *frame);
virtual void sendDownFrame(Packet *frame);
virtual void sendDownPendingRadioConfigMsg();
+ virtual void resetPeerAgreements(const MacAddress& peer) override;
virtual void processUpperFrame(Packet *packet, const Ptr& header);
virtual void processLowerFrame(Packet *packet, const Ptr& header);
diff --git a/src/inet/linklayer/ieee80211/mac/Ieee80211MacHeaderSerializer.cc b/src/inet/linklayer/ieee80211/mac/Ieee80211MacHeaderSerializer.cc
index 2a482cded2f..d2b1ae642fe 100644
--- a/src/inet/linklayer/ieee80211/mac/Ieee80211MacHeaderSerializer.cc
+++ b/src/inet/linklayer/ieee80211/mac/Ieee80211MacHeaderSerializer.cc
@@ -331,10 +331,13 @@ void Ieee80211MacHeaderSerializer::serializeFields(MemoryOutputStream& stream, c
stream.writeMacAddress(blockAckReq->getTransmitterAddress());
bool multiTid = blockAckReq->getMultiTid();
bool compressedBitmap = blockAckReq->getCompressedBitmap();
- // BAR Control is a 16-bit little-endian field: bit 0 BAR Ack Policy, bit 1
- // Multi-TID, bit 2 Compressed Bitmap, bits 3-11 reserved, bits 12-15 TID_INFO
+ // IEEE Std 802.11-2024, 9.3.1.7: B1-B4 encode the complete BAR Type.
+ if (!multiTid && !compressedBitmap && (blockAckReq->getReserved() & 3))
+ throw cRuntimeError("Basic BAR reserved fields must encode Type 0");
uint16_t barControl = (blockAckReq->getBarAckPolicy() ? 1 : 0) | ((multiTid ? 1 : 0) << 1)
| ((compressedBitmap ? 1 : 0) << 2) | ((blockAckReq->getReserved() & 0x1FF) << 3);
+ if (!multiTid && compressedBitmap)
+ barControl = 2 << 1;
if (!multiTid && !compressedBitmap) {
auto basicBlockAckReq = CHK(dynamicPtrCast(chunk));
stream.writeUint16Le(barControl | ((basicBlockAckReq->getTidInfo() & 0xF) << 12));
@@ -347,6 +350,8 @@ void Ieee80211MacHeaderSerializer::serializeFields(MemoryOutputStream& stream, c
}
else if (!multiTid && compressedBitmap) {
auto compressedBlockAckReq = CHK(dynamicPtrCast(chunk));
+ if (compressedBlockAckReq->getFragmentNumber() != 0)
+ throw cRuntimeError("Compressed BAR Fragment Number must be zero");
stream.writeUint16Le(barControl | ((compressedBlockAckReq->getTidInfo() & 0xF) << 12));
writeSequenceControl(stream, compressedBlockAckReq->getFragmentNumber(), compressedBlockAckReq->getStartingSequenceNumber().get());
if (stream.getLength() - startPos != compressedBlockAckReq->getChunkLength())
@@ -367,10 +372,13 @@ void Ieee80211MacHeaderSerializer::serializeFields(MemoryOutputStream& stream, c
stream.writeMacAddress(blockAck->getTransmitterAddress());
bool multiTid = blockAck->getMultiTid();
bool compressedBitmap = blockAck->getCompressedBitmap();
- // BA Control is a 16-bit little-endian field: bit 0 Ack Policy, bit 1 Multi-TID,
- // bit 2 Compressed Bitmap, bits 3-11 reserved, bits 12-15 TID_INFO
+ // IEEE Std 802.11-2024, 9.3.1.8: B1-B4 encode the complete BA Type.
+ if (!multiTid && !compressedBitmap && (blockAck->getReserved() & 3))
+ throw cRuntimeError("Basic Block Ack reserved fields must encode Type 0");
uint16_t baControl = (blockAck->getBlockAckPolicy() ? 1 : 0) | ((multiTid ? 1 : 0) << 1)
| ((compressedBitmap ? 1 : 0) << 2) | ((blockAck->getReserved() & 0x1FF) << 3);
+ if (!multiTid && compressedBitmap)
+ baControl = 2 << 1;
if (!multiTid && !compressedBitmap) {
auto basicBlockAck = CHK(dynamicPtrCast(chunk));
stream.writeUint16Le(baControl | ((basicBlockAck->getTidInfo() & 0xF) << 12));
@@ -386,6 +394,10 @@ void Ieee80211MacHeaderSerializer::serializeFields(MemoryOutputStream& stream, c
}
else if (!multiTid && compressedBitmap) {
auto compressedBlockAck = CHK(dynamicPtrCast(chunk));
+ if (compressedBlockAck->getFragmentNumber() != 0)
+ throw cRuntimeError("Compressed Block Ack Fragment Number must be zero");
+ if (compressedBlockAck->getBlockAckBitmap().getSize() != 64)
+ throw cRuntimeError("Compressed Block Ack bitmap must contain 64 bits");
stream.writeUint16Le(baControl | ((compressedBlockAck->getTidInfo() & 0xF) << 12));
// Block Ack Starting Sequence Control = 4-bit Fragment Number (reserved) + 12-bit sequence number
writeSequenceControl(stream, compressedBlockAck->getFragmentNumber(), compressedBlockAck->getStartingSequenceNumber().get());
@@ -600,6 +612,7 @@ const Ptr Ieee80211MacHeaderSerializer::deserializeFields(MemoryInputStre
blockAckReq->setReceiverAddress(stream.readMacAddress());
blockAckReq->setTransmitterAddress(stream.readMacAddress());
uint16_t barControl = stream.readUint16Le();
+ auto barType = (barControl >> 1) & 0xF;
bool multiTid = (barControl >> 1) & 1;
bool compressedBitmap = (barControl >> 2) & 1;
blockAckReq->setBarAckPolicy(barControl & 1);
@@ -608,7 +621,7 @@ const Ptr Ieee80211MacHeaderSerializer::deserializeFields(MemoryInputStre
blockAckReq->setReserved((barControl >> 3) & 0x1FF);
int fragmentNumber = 0;
SequenceNumberCyclic startingSequenceNumber;
- if (!multiTid && !compressedBitmap) {
+ if (barType == 0) {
auto basicBlockAckReq = makeShared();
copyBasicFields(basicBlockAckReq, macHeader);
copyBlockAckReqFrameFields(basicBlockAckReq, blockAckReq);
@@ -618,14 +631,18 @@ const Ptr Ieee80211MacHeaderSerializer::deserializeFields(MemoryInputStre
basicBlockAckReq->setStartingSequenceNumber(startingSequenceNumber);
return basicBlockAckReq;
}
- else if (!multiTid && compressedBitmap) {
+ else if (barType == 2) {
auto compressedBlockAckReq = makeShared();
copyBasicFields(compressedBlockAckReq, macHeader);
copyBlockAckReqFrameFields(compressedBlockAckReq, blockAckReq);
+ compressedBlockAckReq->setBarAckPolicy(false);
+ compressedBlockAckReq->setReserved(0);
compressedBlockAckReq->setTidInfo((barControl >> 12) & 0xF);
readSequenceControl(stream, fragmentNumber, startingSequenceNumber);
compressedBlockAckReq->setFragmentNumber(fragmentNumber);
compressedBlockAckReq->setStartingSequenceNumber(startingSequenceNumber);
+ if (fragmentNumber != 0)
+ compressedBlockAckReq->markIncorrect();
return compressedBlockAckReq;
}
else
@@ -640,9 +657,8 @@ const Ptr Ieee80211MacHeaderSerializer::deserializeFields(MemoryInputStre
blockAck->setDurationField(SimTime(stream.readUint16Le(), SIMTIME_US));
blockAck->setReceiverAddress(stream.readMacAddress());
blockAck->setTransmitterAddress(stream.readMacAddress());
- // BA Control is a 16-bit little-endian field: bit 0 Ack Policy, bit 1 Multi-TID,
- // bit 2 Compressed Bitmap, bits 3-11 reserved, bits 12-15 TID_INFO
uint16_t baControl = stream.readUint16Le();
+ auto baType = (baControl >> 1) & 0xF;
bool multiTid = (baControl >> 1) & 1;
bool compressedBitmap = (baControl >> 2) & 1;
uint8_t tidInfo = (baControl >> 12) & 0xF;
@@ -650,7 +666,7 @@ const Ptr Ieee80211MacHeaderSerializer::deserializeFields(MemoryInputStre
blockAck->setMultiTid(multiTid);
blockAck->setCompressedBitmap(compressedBitmap);
blockAck->setReserved((baControl >> 3) & 0x1FF);
- if (!multiTid && !compressedBitmap) {
+ if (baType == 0) {
auto basicBlockAck = makeShared();
copyBasicFields(basicBlockAck, macHeader);
copyBlockAckFrameFields(basicBlockAck, blockAck);
@@ -664,15 +680,16 @@ const Ptr Ieee80211MacHeaderSerializer::deserializeFields(MemoryInputStre
std::vector bytes;
bytes.push_back(stream.readByte());
bytes.push_back(stream.readByte());
- BitVector *blockAckBitmap = new BitVector(bytes);
- basicBlockAck->setBlockAckBitmap(i, *blockAckBitmap);
+ basicBlockAck->setBlockAckBitmap(i, BitVector(bytes));
}
return basicBlockAck;
}
- else if (!multiTid && compressedBitmap) {
+ else if (baType == 2) {
auto compressedBlockAck = makeShared();
copyBasicFields(compressedBlockAck, macHeader);
copyBlockAckFrameFields(compressedBlockAck, blockAck);
+ compressedBlockAck->setBlockAckPolicy(false);
+ compressedBlockAck->setReserved(0);
compressedBlockAck->setTidInfo(tidInfo);
// Block Ack Starting Sequence Control = 4-bit Fragment Number (reserved) + 12-bit
@@ -684,7 +701,9 @@ const Ptr Ieee80211MacHeaderSerializer::deserializeFields(MemoryInputStre
for (size_t i = 0; i < 8; ++i) {
bytes.push_back(stream.readByte());
}
- compressedBlockAck->setBlockAckBitmap(*(new BitVector(bytes)));
+ compressedBlockAck->setBlockAckBitmap(BitVector(bytes));
+ if (fragmentNumber != 0)
+ compressedBlockAck->markIncorrect();
return compressedBlockAck;
}
else {
diff --git a/src/inet/linklayer/ieee80211/mac/blockack/BlockAckRecord.cc b/src/inet/linklayer/ieee80211/mac/blockack/BlockAckRecord.cc
index 919bd813a6f..fa285580703 100644
--- a/src/inet/linklayer/ieee80211/mac/blockack/BlockAckRecord.cc
+++ b/src/inet/linklayer/ieee80211/mac/blockack/BlockAckRecord.cc
@@ -53,6 +53,50 @@ void BlockAckRecord::removeAckStates(SequenceNumberCyclic sequenceNumber)
}
}
+void BlockAckRecord::initializeCompressedWindow(SequenceNumberCyclic start, int size)
+{
+ ASSERT(start.isSpecified() && size > 0 && size <= 64);
+ windowStart = start;
+ windowSize = size;
+ completeReceipts.clear();
+}
+
+void BlockAckRecord::advanceCompressedWindow(SequenceNumberCyclic start)
+{
+ // IEEE Std 802.11-2024, 10.25.6.3: retain explicit receipt truth at WinStartR - 1.
+ if (!(windowStart < start))
+ return;
+ windowStart = start;
+ for (auto it = completeReceipts.begin(); it != completeReceipts.end();) {
+ auto distance = (*it - windowStart.get() + 4096) % 4096;
+ if (distance >= windowSize && distance != 4095)
+ it = completeReceipts.erase(it);
+ else
+ ++it;
+ }
+}
+
+void BlockAckRecord::compressedFrameReceived(const Ptr& header)
+{
+ ASSERT(header->getFragmentNumber() == 0 && !header->getMoreFragments());
+ auto sequence = header->getSequenceNumber();
+ // IEEE Std 802.11-2024, 10.25.6.3: advance before duplicate removal or reorder insertion.
+ if (windowStart <= sequence && !(sequence < windowStart + windowSize))
+ advanceCompressedWindow(sequence - windowSize + 1);
+ if (windowStart <= sequence && sequence < windowStart + windowSize)
+ completeReceipts.insert(sequence.get());
+}
+
+bool BlockAckRecord::hasCompleteReceipt(SequenceNumberCyclic sequenceNumber) const
+{
+ return completeReceipts.count(sequenceNumber.get()) != 0;
+}
+
+bool BlockAckRecord::getCompressedAckState(SequenceNumberCyclic sequenceNumber) const
+{
+ return windowStart <= sequenceNumber && sequenceNumber < windowStart + windowSize && hasCompleteReceipt(sequenceNumber);
+}
+
} /* namespace ieee80211 */
} /* namespace inet */
diff --git a/src/inet/linklayer/ieee80211/mac/blockack/BlockAckRecord.h b/src/inet/linklayer/ieee80211/mac/blockack/BlockAckRecord.h
index 971be8a8af9..1c90bf392dc 100644
--- a/src/inet/linklayer/ieee80211/mac/blockack/BlockAckRecord.h
+++ b/src/inet/linklayer/ieee80211/mac/blockack/BlockAckRecord.h
@@ -25,6 +25,9 @@ class INET_API BlockAckRecord
MacAddress originatorAddress = MacAddress::UNSPECIFIED_ADDRESS;
Tid tid = -1;
std::map acknowledgmentState;
+ SequenceNumberCyclic windowStart;
+ int windowSize = 64; // Sequence positions; the Compressed profile permits 1..64.
+ std::set completeReceipts;
public:
BlockAckRecord(MacAddress originatorAddress, Tid tid);
@@ -33,6 +36,12 @@ class INET_API BlockAckRecord
void blockAckPolicyFrameReceived(const Ptr& header);
bool getAckState(SequenceNumberCyclic sequenceNumber, FragmentNumber fragmentNumber);
void removeAckStates(SequenceNumberCyclic sequenceNumber);
+ void initializeCompressedWindow(SequenceNumberCyclic start, int size);
+ void compressedFrameReceived(const Ptr& header);
+ void advanceCompressedWindow(SequenceNumberCyclic start);
+ bool getCompressedAckState(SequenceNumberCyclic sequenceNumber) const;
+ bool hasCompleteReceipt(SequenceNumberCyclic sequenceNumber) const;
+ SequenceNumberCyclic getWindowStart() const { return windowStart; }
MacAddress getOriginatorAddress() { return originatorAddress; }
Tid getTid() { return tid; }
diff --git a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreement.h b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreement.h
index a516527bc34..c760205173a 100644
--- a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreement.h
+++ b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreement.h
@@ -8,6 +8,8 @@
#ifndef __INET_ORIGINATORBLOCKACKAGREEMENT_H
#define __INET_ORIGINATORBLOCKACKAGREEMENT_H
+#include
+
#include "inet/linklayer/ieee80211/mac/common/SequenceControlField.h"
namespace inet {
@@ -24,6 +26,9 @@ class INET_API OriginatorBlockAckAgreement : public cObject
SequenceNumberCyclic startingSequenceNumber;
int bufferSize = -1;
bool isAMsduSupported = false;
+ bool responseAMsduSupported = false;
+ int dialogToken = -1;
+ int64_t highestTransmittedDataIndex = -1;
bool isDelayedBlockAckPolicySupported = false;
bool isAddbaResponseReceived = false;
bool isAddbaRequestSent = false;
@@ -44,11 +49,47 @@ class INET_API OriginatorBlockAckAgreement : public cObject
virtual ~OriginatorBlockAckAgreement() {}
virtual int getBufferSize() const { return bufferSize; }
+ int getWindowSize() const { return std::min(bufferSize, 64); }
+ static int computeSequenceDistance(SequenceNumberCyclic start, SequenceNumberCyclic sequence)
+ {
+ return (sequence.get() - start.get() + 4096) % 4096;
+ }
+ bool containsSequence(SequenceNumberCyclic sequence) const
+ {
+ return computeSequenceDistance(startingSequenceNumber, sequence) < getWindowSize();
+ }
+ // The Basic and Compressed bitmaps cover 64 sequence positions, independent of the accepted window size.
+ static bool isInBitmap(SequenceNumberCyclic sequence, SequenceNumberCyclic bitmapStart)
+ {
+ return computeSequenceDistance(bitmapStart, sequence) < 64;
+ }
+ // Preserve the earliest unresolved position, or advance to the next allocation when none remains.
+ void advanceWindow(SequenceNumberCyclic nextSequence, const std::vector& unresolvedSequences)
+ {
+ auto start = getStartingSequenceNumber();
+ int distance = computeSequenceDistance(start, nextSequence);
+ for (auto sequence : unresolvedSequences)
+ distance = std::min(distance, computeSequenceDistance(start, sequence));
+ setStartingSequenceNumber(start + distance);
+ }
virtual SequenceNumberCyclic getStartingSequenceNumber() { return startingSequenceNumber; }
virtual void setStartingSequenceNumber(SequenceNumberCyclic sequenceNumber) { startingSequenceNumber = sequenceNumber; }
virtual bool getIsAddbaResponseReceived() const { return isAddbaResponseReceived; }
virtual bool getIsAddbaRequestSent() const { return isAddbaRequestSent; }
virtual bool getIsAMsduSupported() const { return isAMsduSupported; }
+ // IEEE Std 802.11-2024, 10.25.2 defines ADDBA negotiation; INET requires both flags before A-MSDU aggregation.
+ bool isAMsduPermitted() const { return isAMsduSupported && responseAMsduSupported; }
+ int getDialogToken() const { return dialogToken; }
+ void setDialogToken(int token) { dialogToken = token; }
+ void setResponseAMsduSupported(bool value) { responseAMsduSupported = value; }
+ bool getFirstDataTransmitted() const { return highestTransmittedDataIndex >= 0; }
+ int64_t getHighestTransmittedDataIndex() const { return highestTransmittedDataIndex; }
+ void recordTransmittedData(int64_t allocationIndex)
+ {
+ ASSERT(allocationIndex >= 0);
+ if (highestTransmittedDataIndex < allocationIndex)
+ highestTransmittedDataIndex = allocationIndex;
+ }
virtual bool getIsDelayedBlockAckPolicySupported() const { return isDelayedBlockAckPolicySupported; }
virtual MacAddress getReceiverAddr() const { return receiverAddr; }
virtual Tid getTid() const { return tid; }
diff --git a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.cc b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.cc
index 2f11dc365db..c526b7fe88c 100644
--- a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.cc
+++ b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.cc
@@ -8,15 +8,108 @@
#include "inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.h"
#include "inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreement.h"
+#include "inet/linklayer/ieee80211/mac/Ieee80211DataSequenceTag_m.h"
namespace inet {
namespace ieee80211 {
+void OriginatorBlockAckAgreementHandler::noteEligibleDataTransmission(MacAddress peer, Tid tid)
+{
+ Procedure procedure;
+ procedure.state = ProcedureState::WAITING_FOR_SUCCESSOR;
+ procedures.emplace(AgreementId(peer, tid), procedure);
+}
+
+void OriginatorBlockAckAgreementHandler::beginSetupIntent(MacAddress peer, Tid tid)
+{
+ auto result = procedures.emplace(AgreementId(peer, tid), Procedure());
+ if (result.first->second.state == ProcedureState::WAITING_FOR_SUCCESSOR)
+ result.first->second.state = ProcedureState::SETUP_INTENT;
+}
+
+void OriginatorBlockAckAgreementHandler::beginTeardown(MacAddress peer, Tid tid)
+{
+ Procedure procedure;
+ procedure.state = ProcedureState::TEARDOWN_PENDING;
+ procedures[{peer, tid}] = procedure;
+}
+
+void OriginatorBlockAckAgreementHandler::failTeardown(MacAddress peer, Tid tid)
+{
+ Procedure procedure;
+ procedure.state = ProcedureState::RECOVERY_NEEDED;
+ procedures[{peer, tid}] = procedure;
+}
+
+void OriginatorBlockAckAgreementHandler::finishProcedure(MacAddress peer, Tid tid)
+{
+ procedures.erase({peer, tid});
+}
+
+void OriginatorBlockAckAgreementHandler::markRequestAttempted(MacAddress peer, Tid tid)
+{
+ auto it = procedures.find({peer, tid});
+ if (it != procedures.end() && it->second.state == ProcedureState::REQUEST_PENDING)
+ it->second.requestAttempted = true;
+}
+
+void OriginatorBlockAckAgreementHandler::acknowledgeRequest(MacAddress peer, Tid tid, simtime_t timeout)
+{
+ auto it = procedures.find({peer, tid});
+ if (it != procedures.end() && it->second.state == ProcedureState::REQUEST_PENDING)
+ it->second.responseDeadline = simTime() + timeout;
+}
+
void OriginatorBlockAckAgreementHandler::createAgreement(const Ptr& addbaRequest)
{
OriginatorBlockAckAgreement *blockAckAgreement = new OriginatorBlockAckAgreement(addbaRequest->getReceiverAddress(), addbaRequest->getTid(), addbaRequest->getStartingSequenceNumber(), addbaRequest->getBufferSize(), addbaRequest->getAMsduSupported(), addbaRequest->getBlockAckPolicy() == 0);
auto agreementId = std::make_pair(addbaRequest->getReceiverAddress(), addbaRequest->getTid());
blockAckAgreements[agreementId] = blockAckAgreement;
+ blockAckAgreement->setDialogToken(addbaRequest->getDialogToken());
+ procedures[agreementId].state = ProcedureState::REQUEST_PENDING;
+}
+
+OriginatorBlockAckAgreement *OriginatorBlockAckAgreementHandler::detachAgreement(MacAddress peer, Tid tid)
+{
+ auto it = blockAckAgreements.find({peer, tid});
+ if (it == blockAckAgreements.end())
+ return nullptr;
+ auto agreement = it->second;
+ blockAckAgreements.erase(it);
+ return agreement;
+}
+
+std::vector OriginatorBlockAckAgreementHandler::detachPeer(MacAddress peer)
+{
+ std::vector removed;
+ for (auto it = blockAckAgreements.begin(); it != blockAckAgreements.end();) {
+ if (it->first.first == peer) {
+ removed.push_back(it->second);
+ it = blockAckAgreements.erase(it);
+ }
+ else
+ ++it;
+ }
+ for (auto it = procedures.begin(); it != procedures.end();) {
+ if (it->first.first == peer)
+ it = procedures.erase(it);
+ else
+ ++it;
+ }
+ return removed;
+}
+
+void OriginatorBlockAckAgreementHandler::startSetup(MacAddress peer, Tid tid, SequenceNumberCyclic sequence,
+ IOriginatorBlockAckAgreementPolicy *policy, IProcedureCallback *callback, bool aMsdu)
+{
+ auto request = buildAddbaRequest(peer, tid, sequence, policy);
+ request->setAMsduSupported(aMsdu && policy->isMsduSupported());
+ // IEEE Std 802.11-2024, Table 9-466: ordinary setup uses a nonzero dialog token.
+ auto& token = dialogTokens[{peer, tid}];
+ token = token % 255 + 1;
+ request->setDialogToken(token);
+ createAgreement(request);
+ callback->processMgmtFrame(new Packet("AddbaReq", request), request);
}
simtime_t OriginatorBlockAckAgreementHandler::computeEarliestExpirationTime()
@@ -40,15 +133,16 @@ void OriginatorBlockAckAgreementHandler::blockAckAgreementExpired(IProcedureCall
// primitive with the ReasonCode parameter having a value of TIMEOUT.
// The procedure is illustrated in Figure 10-14.
simtime_t now = simTime();
+ std::vector expired;
for (auto id : blockAckAgreements) {
auto agreement = id.second;
- if (agreement->getExpirationTime() == now) {
- MacAddress receiverAddr = id.first.first;
- Tid tid = id.first.second;
- const auto& delba = buildDelba(receiverAddr, tid, 39);
- auto delbaPacket = new Packet("Delba", delba);
- procedureCallback->processMgmtFrame(delbaPacket, delba); // 39 - TIMEOUT see: Table 8-36—Reason codes
- }
+ if (agreement->getIsAddbaResponseReceived() && agreement->getExpirationTime() <= now)
+ expired.push_back(id.first);
+ }
+ for (const auto& id : expired) {
+ const auto& delba = buildDelba(id.first, id.second, 39);
+ auto delbaPacket = new Packet("Delba", delba);
+ procedureCallback->processMgmtFrame(delbaPacket, delba);
}
scheduleInactivityTimer(agreementHandlerCallback);
}
@@ -76,7 +170,13 @@ void OriginatorBlockAckAgreementHandler::processReceivedBlockAck(const Ptr(blockAck)) {
auto agreement = getAgreement(basicBlockAck->getTransmitterAddress(), basicBlockAck->getTidInfo());
if (agreement) {
- agreement->setStartingSequenceNumber(basicBlockAck->getStartingSequenceNumber());
+ agreement->calculateExpirationTime();
+ scheduleInactivityTimer(callback);
+ }
+ }
+ else if (auto compressed = dynamicPtrCast(blockAck)) {
+ auto agreement = getAgreement(compressed->getTransmitterAddress(), compressed->getTidInfo());
+ if (agreement) {
agreement->calculateExpirationTime();
scheduleInactivityTimer(callback);
}
@@ -87,9 +187,7 @@ void OriginatorBlockAckAgreementHandler::processReceivedBlockAck(const PtrscheduleInactivityTimer(earliestExpirationTime);
+ callback->scheduleInactivityTimer();
}
OriginatorBlockAckAgreement *OriginatorBlockAckAgreementHandler::getAgreement(MacAddress receiverAddr, Tid tid)
@@ -124,12 +222,8 @@ void OriginatorBlockAckAgreementHandler::terminateAgreement(MacAddress originato
void OriginatorBlockAckAgreementHandler::processTransmittedDataFrame(Packet *packet, const Ptr& dataHeader, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IProcedureCallback *callback)
{
auto agreement = getAgreement(dataHeader->getReceiverAddress(), dataHeader->getTid());
- if (blockAckAgreementPolicy->isAddbaReqNeeded(packet, dataHeader) && agreement == nullptr) {
- auto addbaReq = buildAddbaRequest(dataHeader->getReceiverAddress(), dataHeader->getTid(), dataHeader->getSequenceNumber() + 1, blockAckAgreementPolicy);
- createAgreement(addbaReq);
- auto addbaPacket = new Packet("AddbaReq", addbaReq);
- callback->processMgmtFrame(addbaPacket, addbaReq);
- }
+ if (agreement && agreement->getIsAddbaResponseReceived())
+ agreement->recordTransmittedData(packet->getTag()->getAllocationIndex());
}
void OriginatorBlockAckAgreementHandler::processReceivedAddbaResp(const Ptr& addbaResp, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IBlockAckAgreementHandlerCallback *callback)
@@ -149,6 +243,7 @@ void OriginatorBlockAckAgreementHandler::updateAgreement(OriginatorBlockAckAgree
agreement->setIsAddbaResponseReceived(true);
agreement->setBufferSize(addbaResp->getBufferSize());
agreement->setBlockAckTimeoutValue(addbaResp->getBlockAckTimeoutValue());
+ agreement->setResponseAMsduSupported(addbaResp->getAMsduSupported());
agreement->calculateExpirationTime();
}
@@ -157,13 +252,11 @@ void OriginatorBlockAckAgreementHandler::processTransmittedAddbaReq(const PtrgetReceiverAddress(), addbaReq->getTid());
if (agreement)
agreement->setIsAddbaRequestSent(true);
- else
- throw cRuntimeError("Block Ack Agreement should have already been added");
}
void OriginatorBlockAckAgreementHandler::processTransmittedDelba(const Ptr& delba)
{
- terminateAgreement(delba->getReceiverAddress(), delba->getTid());
+ // HCF retires the agreement before it queues DELBA. ACK completes teardown.
}
void OriginatorBlockAckAgreementHandler::processReceivedDelba(const Ptr& delba, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy)
diff --git a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.h b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.h
index 4a449d17da3..af0c99d0cb7 100644
--- a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.h
+++ b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.h
@@ -21,6 +21,8 @@ class INET_API OriginatorBlockAckAgreementHandler : public IOriginatorBlockAckAg
{
protected:
std::map, OriginatorBlockAckAgreement *> blockAckAgreements;
+ std::map procedures;
+ std::map dialogTokens;
protected:
virtual const Ptr buildAddbaRequest(MacAddress receiverAddr, Tid tid, SequenceNumberCyclic startingSequenceNumber, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy);
@@ -28,10 +30,22 @@ class INET_API OriginatorBlockAckAgreementHandler : public IOriginatorBlockAckAg
virtual void updateAgreement(OriginatorBlockAckAgreement *agreement, const Ptr& addbaResp);
virtual void terminateAgreement(MacAddress originatorAddr, Tid tid);
virtual const Ptr buildDelba(MacAddress receiverAddr, Tid tid, int reasonCode);
- virtual simtime_t computeEarliestExpirationTime();
virtual void scheduleInactivityTimer(IBlockAckAgreementHandlerCallback *callback);
public:
+ virtual simtime_t computeEarliestExpirationTime() override;
+ const std::map& getAgreements() const override { return blockAckAgreements; }
+ const std::map& getProcedures() const override { return procedures; }
+ virtual void noteEligibleDataTransmission(MacAddress peer, Tid tid) override;
+ virtual void beginSetupIntent(MacAddress peer, Tid tid) override;
+ virtual void beginTeardown(MacAddress peer, Tid tid) override;
+ virtual void failTeardown(MacAddress peer, Tid tid) override;
+ virtual void finishProcedure(MacAddress peer, Tid tid) override;
+ virtual void markRequestAttempted(MacAddress peer, Tid tid) override;
+ virtual void acknowledgeRequest(MacAddress peer, Tid tid, simtime_t timeout) override;
+ OriginatorBlockAckAgreement *detachAgreement(MacAddress peer, Tid tid) override;
+ std::vector detachPeer(MacAddress peer) override;
+ void startSetup(MacAddress peer, Tid tid, SequenceNumberCyclic sequence, IOriginatorBlockAckAgreementPolicy *policy, IProcedureCallback *callback, bool aMsdu) override;
virtual ~OriginatorBlockAckAgreementHandler();
virtual void processTransmittedAddbaReq(const Ptr& addbaReq) override;
virtual void processTransmittedDataFrame(Packet *packet, const Ptr& dataHeader, IOriginatorBlockAckAgreementPolicy *blockAckAgreementPolicy, IProcedureCallback *callback) override;
diff --git a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.cc b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.cc
index 44b4c0874fa..1a03dd26ef2 100644
--- a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.cc
+++ b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.cc
@@ -9,6 +9,7 @@
#include "inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreement.h"
#include "inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.h"
+#include "inet/linklayer/ieee80211/mgmt/Ieee80211MgmtFrame_m.h"
namespace inet {
namespace ieee80211 {
@@ -24,15 +25,25 @@ void OriginatorBlockAckAgreementPolicy::initialize(int stage)
aMsduSupported = par("aMsduSupported");
maximumAllowedBufferSize = par("maximumAllowedBufferSize");
blockAckTimeoutValue = par("blockAckTimeoutValue");
- // TODO addbaFailureTimeout = par("addbaFailureTimeout");
+ addbaFailureTimeout = par("addbaFailureTimeout");
+ if (addbaFailureTimeout <= SIMTIME_ZERO || addbaFailureTimeout == SIMTIME_MAX)
+ throw cRuntimeError("addbaFailureTimeout must be positive and finite");
+ if (maximumAllowedBufferSize <= 0 || blockAckTimeoutValue < SIMTIME_ZERO)
+ throw cRuntimeError("Invalid Block Ack buffer size or inactivity timeout");
WATCH(blockAckReqThreshold);
}
}
simtime_t OriginatorBlockAckAgreementPolicy::computeAddbaFailureTimeout() const
{
- // TODO ADDBAFailureTimeout -- 6.3.29.2.2 Semantics of the service primitive
- throw cRuntimeError("Unimplemented");
+ return addbaFailureTimeout;
+}
+
+bool OriginatorBlockAckAgreementPolicy::isProfileAllowed(BlockAckVariant variant, bool localHt, bool peerHt) const
+{
+ if (variant == BlockAckVariant::COMPRESSED && (!localHt || !peerHt))
+ EV_DEBUG << "Compressed Block Ack setup refused: " << (!localHt ? "local HT state is absent" : "peer HT state is absent") << "; DATA uses Normal Ack.\n";
+ return variant == BlockAckVariant::BASIC || (localHt && peerHt);
}
bool OriginatorBlockAckAgreementPolicy::isAddbaReqNeeded(Packet *packet, const Ptr& header)
@@ -42,8 +53,16 @@ bool OriginatorBlockAckAgreementPolicy::isAddbaReqNeeded(Packet *packet, const P
bool OriginatorBlockAckAgreementPolicy::isAddbaReqAccepted(const Ptr& addbaResp, OriginatorBlockAckAgreement *agreement)
{
- ASSERT(agreement);
- return true;
+ // IEEE Std 802.11-2024, 10.25.2 and Table 9-466: match the pending setup and its nonzero dialog token.
+ // Immediate Block Ack and the 64-entry bound are INET profile limits for historical Basic compatibility.
+ return agreement != nullptr && !agreement->getIsAddbaResponseReceived() &&
+ addbaResp->getTransmitterAddress() == agreement->getReceiverAddr() &&
+ addbaResp->getTid() == agreement->getTid() &&
+ addbaResp->getDialogToken() > 0 && addbaResp->getDialogToken() <= 255 &&
+ addbaResp->getDialogToken() == agreement->getDialogToken() &&
+ addbaResp->getStatusCode() == SC_SUCCESSFUL && addbaResp->getBlockAckPolicy() &&
+ addbaResp->getBufferSize() > 0 && addbaResp->getBufferSize() <= 64 &&
+ addbaResp->getBlockAckTimeoutValue() >= SIMTIME_ZERO;
}
bool OriginatorBlockAckAgreementPolicy::isDelbaAccepted(const Ptr& delba)
diff --git a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.h b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.h
index b762cc29828..b0b7e9cce24 100644
--- a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.h
+++ b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.h
@@ -32,6 +32,7 @@ class INET_API OriginatorBlockAckAgreementPolicy : public ModeSetListener, publi
virtual void initialize(int stage) override;
public:
+ virtual bool isProfileAllowed(BlockAckVariant variant, bool localHt, bool peerHt) const override;
virtual bool isAddbaReqNeeded(Packet *packet, const Ptr& header) override;
virtual bool isAddbaReqAccepted(const Ptr& addbaResp, OriginatorBlockAckAgreement *agreement) override;
virtual bool isDelbaAccepted(const Ptr& delba) override;
diff --git a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.ned b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.ned
index bbb569b4b20..ab9970f883f 100644
--- a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.ned
+++ b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementPolicy.ned
@@ -21,8 +21,9 @@ simple OriginatorBlockAckAgreementPolicy extends SimpleModule like IOriginatorBl
bool delayedAckPolicySupported = default(false);
bool aMsduSupported = default(true);
- int maximumAllowedBufferSize = default(64);
- double blockAckTimeoutValue @unit(s) = default(0s); // 0 means that it depends on the originator
+ int maximumAllowedBufferSize = default(64); // The ADDBA Request uses this advisory buffer size; the recipient response determines the accepted allocation.
+ double blockAckTimeoutValue @unit(s) = default(0s); // Zero disables inactivity expiration.
+ double addbaFailureTimeout @unit(s) = default(100ms); // Response wait after request ACK.
@display("i=block/control");
}
diff --git a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckProcedure.cc b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckProcedure.cc
index d02e412b644..ee1960c3fb0 100644
--- a/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckProcedure.cc
+++ b/src/inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckProcedure.cc
@@ -12,13 +12,11 @@ namespace ieee80211 {
const Ptr OriginatorBlockAckProcedure::buildCompressedBlockAckReqFrame(const MacAddress& receiverAddress, Tid tid, SequenceNumberCyclic startingSequenceNumber) const
{
- throw cRuntimeError("Unsupported feature");
- // TODO implement
- // auto blockAckReq = makeShared();
- // blockAckReq->setReceiverAddress(receiverAddress);
- // blockAckReq->setStartingSequenceNumber(startingSequenceNumber);
- // blockAckReq->setTidInfo(tid);
- // return blockAckReq;
+ auto blockAckReq = makeShared();
+ blockAckReq->setReceiverAddress(receiverAddress);
+ blockAckReq->setStartingSequenceNumber(startingSequenceNumber);
+ blockAckReq->setTidInfo(tid);
+ return blockAckReq;
}
const Ptr OriginatorBlockAckProcedure::buildBasicBlockAckReqFrame(const MacAddress& receiverAddress, Tid tid, SequenceNumberCyclic startingSequenceNumber) const
diff --git a/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreement.h b/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreement.h
index 40a4186fead..db10d2d489b 100644
--- a/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreement.h
+++ b/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreement.h
@@ -9,6 +9,7 @@
#define __INET_RECIPIENTBLOCKACKAGREEMENT_H
#include "inet/linklayer/ieee80211/mac/blockack/BlockAckRecord.h"
+#include "inet/linklayer/ieee80211/mac/contract/BlockAckVariant.h"
namespace inet {
namespace ieee80211 {
@@ -23,6 +24,9 @@ class INET_API RecipientBlockAckAgreement : public cObject
simtime_t blockAckTimeoutValue = 0;
bool isAddbaResponseSent = false;
simtime_t expirationTime = -1;
+ SequenceNumberCyclic highestReceivedSequence;
+ int highestReceivedFragment = 0;
+ BlockAckVariant variant = BlockAckVariant::BASIC;
public:
RecipientBlockAckAgreement(MacAddress originatorAddress, Tid tid, SequenceNumberCyclic startingSequenceNumber, int bufferSize, simtime_t blockAckTimeoutValue);
@@ -33,6 +37,23 @@ class INET_API RecipientBlockAckAgreement : public cObject
virtual BlockAckRecord *getBlockAckRecord() const { return blockAckRecord; }
virtual simtime_t getBlockAckTimeoutValue() const { return blockAckTimeoutValue; }
virtual int getBufferSize() const { return bufferSize; }
+ BlockAckVariant getVariant() const { return variant; }
+ void setVariant(BlockAckVariant value) {
+ variant = value;
+ if (variant == BlockAckVariant::COMPRESSED)
+ blockAckRecord->initializeCompressedWindow(startingSequenceNumber, bufferSize);
+ }
+ void recordReceived(const Ptr& header) {
+ auto sequence = header->getSequenceNumber();
+ if (!highestReceivedSequence.isSpecified() || highestReceivedSequence < sequence) {
+ highestReceivedSequence = sequence;
+ highestReceivedFragment = header->getFragmentNumber();
+ }
+ else if (highestReceivedSequence == sequence)
+ highestReceivedFragment = std::max(highestReceivedFragment, int(header->getFragmentNumber()));
+ }
+ const SequenceNumberCyclic& getHighestReceivedSequence() const { return highestReceivedSequence; }
+ int getHighestReceivedFragment() const { return highestReceivedFragment; }
virtual SequenceNumberCyclic getStartingSequenceNumber() const { return startingSequenceNumber; }
virtual void addbaResposneSent() { isAddbaResponseSent = true; }
diff --git a/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.cc b/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.cc
index 6219ac77140..717368a5a46 100644
--- a/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.cc
+++ b/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.cc
@@ -8,6 +8,7 @@
#include "inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.h"
#include "inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreement.h"
+#include "inet/linklayer/ieee80211/mgmt/Ieee80211MgmtFrame_m.h"
namespace inet {
namespace ieee80211 {
@@ -24,9 +25,7 @@ simtime_t RecipientBlockAckAgreementHandler::computeEarliestExpirationTime()
void RecipientBlockAckAgreementHandler::scheduleInactivityTimer(IBlockAckAgreementHandlerCallback *callback)
{
- simtime_t earliestExpirationTime = computeEarliestExpirationTime();
- if (earliestExpirationTime != SIMTIME_MAX)
- callback->scheduleInactivityTimer(earliestExpirationTime);
+ callback->scheduleInactivityTimer();
}
// The inactivity timer at a recipient is reset when MPDUs corresponding to the TID for which the Block Ack
@@ -35,12 +34,15 @@ void RecipientBlockAckAgreementHandler::scheduleInactivityTimer(IBlockAckAgreeme
//
void RecipientBlockAckAgreementHandler::qosFrameReceived(const Ptr& qosHeader, IBlockAckAgreementHandlerCallback *callback)
{
+ // IEEE Std 802.11-2024, 11.5.4: matching Block Ack-policy DATA refreshes inactivity, including a duplicate.
if (qosHeader->getAckPolicy() == AckPolicy::BLOCK_ACK) { // TODO + Implicit Block Ack
Tid tid = qosHeader->getTid();
MacAddress originatorAddr = qosHeader->getTransmitterAddress();
auto agreement = getAgreement(tid, originatorAddr);
- if (agreement)
+ if (agreement) {
+ agreement->calculateExpirationTime();
scheduleInactivityTimer(callback);
+ }
}
}
@@ -51,15 +53,16 @@ void RecipientBlockAckAgreementHandler::blockAckAgreementExpired(IProcedureCallb
// primitive with the ReasonCode parameter having a value of TIMEOUT.
// The procedure is illustrated in Figure 10-14.
simtime_t now = simTime();
+ std::vector> expired;
for (auto id : blockAckAgreements) {
auto agreement = id.second;
- if (agreement->getExpirationTime() == now) {
- MacAddress receiverAddr = id.first.first;
- Tid tid = id.first.second;
- const auto& delba = buildDelba(receiverAddr, tid, 39);
- auto delbaPacket = new Packet("Delba", delba);
- procedureCallback->processMgmtFrame(delbaPacket, delba); // 39 - TIMEOUT see: Table 8-36—Reason codes
- }
+ if (agreement->getExpirationTime() <= now)
+ expired.push_back(id.first);
+ }
+ for (const auto& id : expired) {
+ const auto& delba = buildDelba(id.first, id.second, 39);
+ auto delbaPacket = new Packet("Delba", delba);
+ procedureCallback->processMgmtFrame(delbaPacket, delba);
}
scheduleInactivityTimer(agreementHandlerCallback);
}
@@ -110,8 +113,10 @@ const Ptr RecipientBlockAckAgreementHandler::buildAddbaR
Tid tid = addbaRequest->getTid();
addbaResponse->setTid(tid);
addbaResponse->setBlockAckPolicy(!addbaRequest->getBlockAckPolicy() && blockAckAgreementPolicy->delayedBlockAckPolicySupported() ? false : true);
- addbaResponse->setBufferSize(addbaRequest->getBufferSize() <= blockAckAgreementPolicy->getMaximumAllowedBufferSize() ? addbaRequest->getBufferSize() : blockAckAgreementPolicy->getMaximumAllowedBufferSize());
- addbaResponse->setBlockAckTimeoutValue(blockAckAgreementPolicy->getBlockAckTimeoutValue() == 0 ? blockAckAgreementPolicy->getBlockAckTimeoutValue() : addbaRequest->getBlockAckTimeoutValue());
+ addbaResponse->setDialogToken(addbaRequest->getDialogToken());
+ // IEEE Std 802.11-2024, 10.25.2: the request allocation and timeout are advisory.
+ addbaResponse->setBufferSize(std::min(64, blockAckAgreementPolicy->getMaximumAllowedBufferSize()));
+ addbaResponse->setBlockAckTimeoutValue(blockAckAgreementPolicy->getBlockAckTimeoutValue());
addbaResponse->setAMsduSupported(blockAckAgreementPolicy->aMsduSupported());
return addbaResponse;
}
@@ -124,8 +129,6 @@ void RecipientBlockAckAgreementHandler::updateAgreement(const Ptrsecond;
agreement->addbaResposneSent();
}
- else
- throw cRuntimeError("Agreement is not found");
}
void RecipientBlockAckAgreementHandler::terminateAgreement(MacAddress originatorAddr, Tid tid)
@@ -155,26 +158,62 @@ void RecipientBlockAckAgreementHandler::processTransmittedAddbaResp(const Ptr& addbaRequest, IRecipientBlockAckAgreementPolicy *blockAckAgreementPolicy, IProcedureCallback *callback)
{
EV_INFO << "Processing Addba Request from " << addbaRequest->getTransmitterAddress() << endl;
- if (blockAckAgreementPolicy->isAddbaReqAccepted(addbaRequest)) {
+ auto id = std::make_pair(addbaRequest->getTransmitterAddress(), addbaRequest->getTid());
+ auto addbaResponse = buildAddbaResponse(addbaRequest, blockAckAgreementPolicy);
+ bool accepted = blockAckAgreementPolicy->isAddbaReqAccepted(addbaRequest) &&
+ blockAckAgreements.count(id) == 0 && pendingTeardowns.count(id) == 0;
+ addbaResponse->setStatusCode(accepted ? SC_SUCCESSFUL : SC_UNSPECIFIED);
+ if (accepted) {
EV_DETAIL << "Addba Request has been accepted. Creating a new Block Ack Agreement." << endl;
- auto agreement = addAgreement(addbaRequest);
+ auto parameters = makeShared(*addbaRequest);
+ parameters->setBufferSize(addbaResponse->getBufferSize());
+ parameters->setBlockAckTimeoutValue(addbaResponse->getBlockAckTimeoutValue());
+ auto agreement = addAgreement(parameters);
EV_DETAIL << "Agreement is added with the following parameters: " << *agreement << endl;
EV_DETAIL << "Building Addba Response" << endl;
- auto addbaResponse = buildAddbaResponse(addbaRequest, blockAckAgreementPolicy);
- auto addbaResponsePacket = new Packet("AddbaResponse", addbaResponse);
- callback->processMgmtFrame(addbaResponsePacket, addbaResponse);
}
+ callback->processMgmtFrame(new Packet("AddbaResponse", addbaResponse), addbaResponse);
+}
+
+RecipientBlockAckAgreement *RecipientBlockAckAgreementHandler::detachAgreement(MacAddress peer, Tid tid)
+{
+ auto it = blockAckAgreements.find({peer, tid});
+ if (it == blockAckAgreements.end())
+ return nullptr;
+ auto agreement = it->second;
+ blockAckAgreements.erase(it);
+ return agreement;
+}
+
+std::vector RecipientBlockAckAgreementHandler::detachPeer(MacAddress peer)
+{
+ std::vector removed;
+ for (auto it = blockAckAgreements.begin(); it != blockAckAgreements.end();) {
+ if (it->first.first == peer) {
+ removed.push_back(it->second);
+ it = blockAckAgreements.erase(it);
+ }
+ else
+ ++it;
+ }
+ for (auto it = pendingTeardowns.begin(); it != pendingTeardowns.end();) {
+ if (it->first == peer)
+ it = pendingTeardowns.erase(it);
+ else
+ ++it;
+ }
+ return removed;
}
void RecipientBlockAckAgreementHandler::processTransmittedDelba(const Ptr& delba)
{
- terminateAgreement(delba->getReceiverAddress(), delba->getTid());
+ // HCF retains teardown state until the Action reaches a terminal result.
}
void RecipientBlockAckAgreementHandler::processReceivedDelba(const Ptr& delba, IRecipientBlockAckAgreementPolicy *blockAckAgreementPolicy)
{
if (blockAckAgreementPolicy->isDelbaAccepted(delba))
- terminateAgreement(delba->getReceiverAddress(), delba->getTid());
+ terminateAgreement(delba->getTransmitterAddress(), delba->getTid());
}
RecipientBlockAckAgreementHandler::~RecipientBlockAckAgreementHandler()
diff --git a/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.h b/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.h
index 1e65a1557ef..ac93c8366e6 100644
--- a/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.h
+++ b/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementHandler.h
@@ -26,6 +26,7 @@ class INET_API RecipientBlockAckAgreementHandler : public IRecipientBlockAckAgre
{
protected:
std::map, RecipientBlockAckAgreement *> blockAckAgreements;
+ std::set> pendingTeardowns;
protected:
virtual void terminateAgreement(MacAddress originatorAddr, Tid tid);
@@ -33,10 +34,16 @@ class INET_API RecipientBlockAckAgreementHandler : public IRecipientBlockAckAgre
virtual void updateAgreement(const Ptr& addbaResponse);
virtual const Ptr buildAddbaResponse(const Ptr& addbaRequest, IRecipientBlockAckAgreementPolicy *blockAckAgreementPolicy);
virtual const Ptr buildDelba(MacAddress receiverAddr, Tid tid, int reasonCode);
- virtual simtime_t computeEarliestExpirationTime();
virtual void scheduleInactivityTimer(IBlockAckAgreementHandlerCallback *callback);
public:
+ virtual simtime_t computeEarliestExpirationTime() override;
+ const std::map, RecipientBlockAckAgreement *>& getAgreements() const override { return blockAckAgreements; }
+ bool isTeardownPending(MacAddress peer, Tid tid) const override { return pendingTeardowns.count({peer, tid}) != 0; }
+ void beginTeardown(MacAddress peer, Tid tid) override { pendingTeardowns.insert({peer, tid}); }
+ void finishTeardown(MacAddress peer, Tid tid) override { pendingTeardowns.erase({peer, tid}); }
+ RecipientBlockAckAgreement *detachAgreement(MacAddress peer, Tid tid) override;
+ std::vector detachPeer(MacAddress peer) override;
virtual ~RecipientBlockAckAgreementHandler();
virtual void processTransmittedAddbaResp(const Ptr& addbaResp, IBlockAckAgreementHandlerCallback *callback) override;
virtual void processReceivedAddbaRequest(const Ptr& addbaRequest, IRecipientBlockAckAgreementPolicy *blockAckAgreementPolicy, IProcedureCallback *callback) override;
diff --git a/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementPolicy.cc b/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementPolicy.cc
index 73813feaa5d..33360fcfc32 100644
--- a/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementPolicy.cc
+++ b/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementPolicy.cc
@@ -19,12 +19,29 @@ void RecipientBlockAckAgreementPolicy::initialize(int stage)
isAMsduSupported = par("aMsduSupported");
maximumAllowedBufferSize = par("maximumAllowedBufferSize");
blockAckTimeoutValue = par("blockAckTimeoutValue");
+ if (maximumAllowedBufferSize <= 0 || blockAckTimeoutValue < SIMTIME_ZERO)
+ throw cRuntimeError("Invalid Block Ack buffer size or inactivity timeout");
}
}
bool RecipientBlockAckAgreementPolicy::isAddbaReqAccepted(const Ptr& addbaReq)
{
- return true;
+ // IEEE Std 802.11-2024, 10.25.2 and Table 9-466: validate ordinary setup; the requested buffer size is advisory.
+ // INET supports immediate Block Ack for historical Basic compatibility and selects at most 64 entries in the response.
+ return !addbaReq->getTransmitterAddress().isUnspecified() &&
+ !addbaReq->getTransmitterAddress().isMulticast() &&
+ addbaReq->getTid() >= 0 && addbaReq->getTid() <= 15 &&
+ addbaReq->getDialogToken() > 0 && addbaReq->getDialogToken() <= 255 &&
+ addbaReq->getBlockAckPolicy() && addbaReq->getBufferSize() >= 0 &&
+ addbaReq->getBufferSize() <= 1023 && addbaReq->getStartingSequenceNumber().isSpecified() &&
+ addbaReq->getBlockAckTimeoutValue() >= SIMTIME_ZERO;
+}
+
+bool RecipientBlockAckAgreementPolicy::isProfileAllowed(BlockAckVariant variant, bool localHt, bool peerHt) const
+{
+ if (variant == BlockAckVariant::COMPRESSED && (!localHt || !peerHt))
+ EV_DEBUG << "Compressed Block Ack setup refused: " << (!localHt ? "local HT state is absent" : "peer HT state is absent") << "; DATA uses Normal Ack.\n";
+ return variant == BlockAckVariant::BASIC || (localHt && peerHt);
}
bool RecipientBlockAckAgreementPolicy::isDelbaAccepted(const Ptr& delba)
diff --git a/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementPolicy.h b/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementPolicy.h
index 995fcc29fdb..d74bfd4240b 100644
--- a/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementPolicy.h
+++ b/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementPolicy.h
@@ -28,6 +28,7 @@ class INET_API RecipientBlockAckAgreementPolicy : public SimpleModule, public IR
virtual void initialize(int stage) override;
public:
+ virtual bool isProfileAllowed(BlockAckVariant variant, bool localHt, bool peerHt) const override;
virtual bool isAddbaReqAccepted(const Ptr& addbaReq) override;
virtual bool isDelbaAccepted(const Ptr& delba) override;
diff --git a/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementPolicy.ned b/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementPolicy.ned
index 8c41285b009..6fb403594cf 100644
--- a/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementPolicy.ned
+++ b/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckAgreementPolicy.ned
@@ -20,7 +20,7 @@ simple RecipientBlockAckAgreementPolicy extends SimpleModule like IRecipientBloc
bool delayedAckPolicySupported = default(false);
bool aMsduSupported = default(true);
int maximumAllowedBufferSize = default(64);
- double blockAckTimeoutValue @unit(s) = default(0s); // 0 means that it depends on the originator
+ double blockAckTimeoutValue @unit(s) = default(0s); // Zero disables inactivity expiration.
@display("i=block/control");
}
diff --git a/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckProcedure.cc b/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckProcedure.cc
index ab7cb050e5f..61d1213020c 100644
--- a/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckProcedure.cc
+++ b/src/inet/linklayer/ieee80211/mac/blockack/RecipientBlockAckProcedure.cc
@@ -31,6 +31,14 @@ void RecipientBlockAckProcedure::processReceivedBlockAckReq(Packet *blockAckPack
callback->transmitControlResponseFrame(blockAckPacket, blockAck, blockAckPacketReq, basicBlockAckReq);
}
}
+ else if (auto compressed = dynamicPtrCast(blockAckReq)) {
+ auto agreement = blockAckAgreementHandler->getAgreement(compressed->getTidInfo(), compressed->getTransmitterAddress());
+ if (ackPolicy->isBlockAckNeeded(compressed, agreement)) {
+ auto blockAck = buildBlockAck(compressed, agreement);
+ blockAck->setDurationField(ackPolicy->computeCompressedBlockAckDurationField(blockAckPacketReq, compressed));
+ callback->transmitControlResponseFrame(new Packet("CompressedBlockAck", blockAck), blockAck, blockAckPacketReq, compressed);
+ }
+ }
else
throw cRuntimeError("Unsupported BlockAckReq");
}
@@ -65,6 +73,20 @@ const Ptr RecipientBlockAckProcedure::buildBlockAck(const Ptr
blockAck->setTidInfo(basicBlockAckReq->getTidInfo());
return blockAck;
}
+ else if (auto compressed = dynamicPtrCast(blockAckReq)) {
+ ASSERT(agreement != nullptr);
+ auto blockAck = makeShared();
+ auto record = agreement->getBlockAckRecord();
+ auto start = record->getWindowStart();
+ BitVector bitmap(0, 64);
+ for (int i = 0; i < 64; i++)
+ bitmap.setBit(i, record->getCompressedAckState(start + i));
+ blockAck->setBlockAckBitmap(bitmap);
+ blockAck->setReceiverAddress(compressed->getTransmitterAddress());
+ blockAck->setStartingSequenceNumber(start);
+ blockAck->setTidInfo(compressed->getTidInfo());
+ return blockAck;
+ }
else
throw cRuntimeError("Unsupported Block Ack Request");
}
diff --git a/src/inet/linklayer/ieee80211/mac/blockackreordering/BlockAckReordering.cc b/src/inet/linklayer/ieee80211/mac/blockackreordering/BlockAckReordering.cc
index 6cffea3c37d..78f06a2d725 100644
--- a/src/inet/linklayer/ieee80211/mac/blockackreordering/BlockAckReordering.cc
+++ b/src/inet/linklayer/ieee80211/mac/blockackreordering/BlockAckReordering.cc
@@ -12,11 +12,50 @@
namespace inet {
namespace ieee80211 {
-BlockAckReordering::ReleasedFrames BlockAckReordering::processReceivedQoSFrame(RecipientBlockAckAgreement *agreement, Packet *dataPacket, const Ptr& dataHeader)
+BlockAckReordering::ReleasedFrames BlockAckReordering::detachAgreement(MacAddress peer, Tid tid,
+ bool deliverComplete, std::vector& discarded)
+{
+ auto it = receiveBuffers.find({tid, peer});
+ if (it == receiveBuffers.end())
+ return {};
+ auto buffer = it->second;
+ receiveBuffers.erase(it);
+ ReleasedFrames released;
+ auto start = buffer->getNextExpectedSequenceNumber();
+ for (int i = 0; i < buffer->getBufferSize(); i++) {
+ auto sequence = start + i;
+ auto position = buffer->getBuffer().find(sequence.get());
+ if (position == buffer->getBuffer().end())
+ continue;
+ auto fragments = position->second;
+ if (deliverComplete && isComplete(fragments))
+ released.push_back({sequence.get(), fragments});
+ else
+ discarded.insert(discarded.end(), fragments.begin(), fragments.end());
+ buffer->removeFrame(sequence);
+ }
+ delete buffer;
+ return released;
+}
+
+void BlockAckReordering::clearPeer(MacAddress peer)
+{
+ for (auto it = receiveBuffers.begin(); it != receiveBuffers.end();) {
+ if (it->first.second == peer) {
+ delete it->second;
+ it = receiveBuffers.erase(it);
+ }
+ else
+ ++it;
+ }
+}
+
+BlockAckReordering::ReleasedFrames BlockAckReordering::processReceivedQoSFrame(RecipientBlockAckAgreement *agreement, Packet *dataPacket, const Ptr& dataHeader, bool interframe)
{
ReceiveBuffer *receiveBuffer = createReceiveBufferIfNecessary(agreement);
auto sequenceNumber = dataHeader->getSequenceNumber();
auto windowStart = receiveBuffer->getNextExpectedSequenceNumber();
+ bool insideWindow = windowStart <= sequenceNumber && sequenceNumber < windowStart + receiveBuffer->getBufferSize();
ReleasedFrames releasedFrames;
// IEEE Std 802.11-2024, 10.25.6.6.2.1(b,c): advance for newer DATA beyond the window; discard older DATA.
// The shared reorder service also retains Basic fragment reassembly as an INET compatibility choice.
@@ -31,8 +70,15 @@ BlockAckReordering::ReleasedFrames BlockAckReordering::processReceivedQoSFrame(R
// This allows the recipient to delete the Block Ack if the originator does not switch
// back to using Block Ack.
if (receiveBuffer->insertFrame(dataPacket, dataHeader)) {
- if (dataHeader->getAckPolicy() == BLOCK_ACK)
+ if (dataHeader->getAckPolicy() == BLOCK_ACK && agreement->getVariant() == BlockAckVariant::BASIC)
agreement->blockAckPolicyFrameReceived(dataHeader);
+ auto record = agreement->getBlockAckRecord();
+ // IEEE Std 802.11-2024, 10.25.6.6.2.1(a)(2)(v) and 10.25.6.6.1: release the complete prefix in order.
+ if (agreement->getVariant() == BlockAckVariant::COMPRESSED && insideWindow && windowStart < sequenceNumber &&
+ interframe && record->hasCompleteReceipt(record->getWindowStart() - 1)) {
+ auto preceding = advanceReceiveBuffer(agreement, receiveBuffer, sequenceNumber);
+ releasedFrames.insert(releasedFrames.end(), preceding.begin(), preceding.end());
+ }
// IEEE Std 802.11-2024, 10.25.6.6.2.1(a)(2): release every consecutive complete unit at the window start.
auto consecutiveFrames = collectConsecutiveCompleteFollowingMpdus(receiveBuffer, receiveBuffer->getNextExpectedSequenceNumber());
releaseReceiveBuffer(agreement, receiveBuffer, consecutiveFrames);
@@ -54,6 +100,14 @@ BlockAckReordering::ReleasedFrames BlockAckReordering::processReceivedBlockAckRe
return ReleasedFrames();
startingSequenceNumber = basicReq->getStartingSequenceNumber();
}
+ else if (auto compressedReq = dynamicPtrCast(blockAckReq)) {
+ if (compressedReq->getFragmentNumber() != 0 || compressedReq->getMultiTid() || !compressedReq->getCompressedBitmap() ||
+ compressedReq->getTidInfo() < 0 || compressedReq->getTidInfo() > 15 || !compressedReq->getStartingSequenceNumber().isSpecified() ||
+ compressedReq->getChunkLength() != LENGTH_BLOCKACKREQ - B(4))
+ return ReleasedFrames();
+ startingSequenceNumber = compressedReq->getStartingSequenceNumber();
+ agreement->getBlockAckRecord()->advanceCompressedWindow(startingSequenceNumber);
+ }
else {
throw cRuntimeError("Multi-Tid BlockAckReq is currently an unimplemented feature");
}
@@ -171,7 +225,8 @@ void BlockAckReordering::passedUp(RecipientBlockAckAgreement *agreement, Receive
receiveBuffer->setNextExpectedSequenceNumber(sequenceNumber + 1);
receiveBuffer->dropFramesUntil(sequenceNumber);
receiveBuffer->removeFrame(sequenceNumber);
- agreement->getBlockAckRecord()->removeAckStates(sequenceNumber);
+ if (agreement->getVariant() == BlockAckVariant::BASIC)
+ agreement->getBlockAckRecord()->removeAckStates(sequenceNumber);
}
std::vector BlockAckReordering::getEarliestCompleteMsduOrAMsduIfExists(ReceiveBuffer *receiveBuffer)
diff --git a/src/inet/linklayer/ieee80211/mac/blockackreordering/BlockAckReordering.h b/src/inet/linklayer/ieee80211/mac/blockackreordering/BlockAckReordering.h
index 42215dd9b40..fdfbd29e226 100644
--- a/src/inet/linklayer/ieee80211/mac/blockackreordering/BlockAckReordering.h
+++ b/src/inet/linklayer/ieee80211/mac/blockackreordering/BlockAckReordering.h
@@ -50,9 +50,11 @@ class INET_API BlockAckReordering
public:
virtual ~BlockAckReordering();
+ ReleasedFrames detachAgreement(MacAddress peer, Tid tid, bool deliverComplete, std::vector& discarded);
+ void clearPeer(MacAddress peer);
void processReceivedDelba(const Ptr& delba);
- ReleasedFrames processReceivedQoSFrame(RecipientBlockAckAgreement *agreement, Packet *dataPacket, const Ptr& dataHeader);
+ ReleasedFrames processReceivedQoSFrame(RecipientBlockAckAgreement *agreement, Packet *dataPacket, const Ptr& dataHeader, bool interframe = false);
ReleasedFrames processReceivedBlockAckReq(RecipientBlockAckAgreement *agreement, const Ptr& blockAckReq);
};
diff --git a/src/inet/linklayer/ieee80211/mac/channelaccess/Edca.h b/src/inet/linklayer/ieee80211/mac/channelaccess/Edca.h
index b7b9e4d6bc2..4b038c0c914 100644
--- a/src/inet/linklayer/ieee80211/mac/channelaccess/Edca.h
+++ b/src/inet/linklayer/ieee80211/mac/channelaccess/Edca.h
@@ -30,11 +30,10 @@ class INET_API Edca : public SimpleModule
virtual int numInitStages() const override { return NUM_INIT_STAGES; }
virtual void initialize(int stage) override;
- virtual AccessCategory mapTidToAc(Tid tid);
-
public:
virtual ~Edca();
+ virtual AccessCategory mapTidToAc(Tid tid);
virtual AccessCategory classifyFrame(const Ptr& header);
virtual Edcaf *getEdcaf(AccessCategory ac) const { return edcafs[ac]; }
EdcaTransmitLifetimeHandler *getLifetimeHandler() const { return lifetimeHandler; }
diff --git a/src/inet/linklayer/ieee80211/mac/channelaccess/Edcaf.cc b/src/inet/linklayer/ieee80211/mac/channelaccess/Edcaf.cc
index 5dc48059089..cb0e9d6549b 100644
--- a/src/inet/linklayer/ieee80211/mac/channelaccess/Edcaf.cc
+++ b/src/inet/linklayer/ieee80211/mac/channelaccess/Edcaf.cc
@@ -165,6 +165,11 @@ bool Edcaf::isInternalCollision()
return collisionController->isInternalCollision(this);
}
+bool Edcaf::hasEligibleFrames() const
+{
+ return frameEligibility ? frameEligibility() : !pendingQueue->isEmpty() || inProgressFrames->getLength() != 0;
+}
+
int Edcaf::getCwMax(AccessCategory ac, int aCwMax, int aCwMin)
{
switch (ac) {
diff --git a/src/inet/linklayer/ieee80211/mac/channelaccess/Edcaf.h b/src/inet/linklayer/ieee80211/mac/channelaccess/Edcaf.h
index c2d716ade33..b9a77e9c051 100644
--- a/src/inet/linklayer/ieee80211/mac/channelaccess/Edcaf.h
+++ b/src/inet/linklayer/ieee80211/mac/channelaccess/Edcaf.h
@@ -49,6 +49,7 @@ class INET_API Edcaf : public IChannelAccess, public IContention::ICallback, pub
InProgressFrames *inProgressFrames = nullptr;
bool owning = false;
+ std::function frameEligibility;
simtime_t slotTime = -1;
simtime_t sifs = -1;
@@ -102,6 +103,8 @@ class INET_API Edcaf : public IChannelAccess, public IContention::ICallback, pub
// Edcaf
virtual bool isOwning() { return owning; }
virtual bool isInternalCollision();
+ void setFrameEligibility(std::function predicate) { frameEligibility = predicate; }
+ bool hasEligibleFrames() const;
virtual AccessCategory getAccessCategory() { return ac; }
};
diff --git a/src/inet/linklayer/ieee80211/mac/contention/EdcaCollisionController.cc b/src/inet/linklayer/ieee80211/mac/contention/EdcaCollisionController.cc
index 33b94c93563..4b3172e039f 100644
--- a/src/inet/linklayer/ieee80211/mac/contention/EdcaCollisionController.cc
+++ b/src/inet/linklayer/ieee80211/mac/contention/EdcaCollisionController.cc
@@ -36,9 +36,9 @@ bool EdcaCollisionController::isInternalCollision(Edcaf *edcaf)
simtime_t now = simTime();
AccessCategory accessCategory = edcaf->getAccessCategory();
auto hasFrames = [](const Edcaf *candidate) {
- return !candidate->getPendingQueue()->isEmpty() || candidate->getInProgressFrames()->getLength() != 0;
+ return candidate->hasEligibleFrames();
};
- // An expired empty queue cannot initiate a TXOP or cause an internal collision.
+ // Deferred or expired DATA cannot initiate a TXOP or cause an internal collision.
// Keep the winner's in-progress frame visible until its transmission completes.
if (txStartTimes[accessCategory] == now && hasFrames(edcaf)) {
for (int ac = accessCategory + 1; ac < 4; ac++) {
diff --git a/src/inet/linklayer/ieee80211/mac/contract/BlockAckVariant.h b/src/inet/linklayer/ieee80211/mac/contract/BlockAckVariant.h
new file mode 100644
index 00000000000..f02e1033036
--- /dev/null
+++ b/src/inet/linklayer/ieee80211/mac/contract/BlockAckVariant.h
@@ -0,0 +1,14 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+
+#ifndef __INET_BLOCKACKVARIANT_H
+#define __INET_BLOCKACKVARIANT_H
+
+namespace inet {
+namespace ieee80211 {
+
+enum class BlockAckVariant { BASIC, COMPRESSED };
+
+} // namespace ieee80211
+} // namespace inet
+
+#endif
diff --git a/src/inet/linklayer/ieee80211/mac/contract/IBlockAckAgreementHandlerCallback.h b/src/inet/linklayer/ieee80211/mac/contract/IBlockAckAgreementHandlerCallback.h
index cc4a7b86b92..3fefe1666c0 100644
--- a/src/inet/linklayer/ieee80211/mac/contract/IBlockAckAgreementHandlerCallback.h
+++ b/src/inet/linklayer/ieee80211/mac/contract/IBlockAckAgreementHandlerCallback.h
@@ -18,7 +18,8 @@ class INET_API IBlockAckAgreementHandlerCallback
public:
virtual ~IBlockAckAgreementHandlerCallback() {}
- virtual void scheduleInactivityTimer(simtime_t timeout) = 0;
+ // The coordinator recomputes the earliest absolute deadline across both agreement roles.
+ virtual void scheduleInactivityTimer() = 0;
};
} // namespace ieee80211
diff --git a/src/inet/linklayer/ieee80211/mac/contract/IBlockAckAgreementLifecycle.h b/src/inet/linklayer/ieee80211/mac/contract/IBlockAckAgreementLifecycle.h
new file mode 100644
index 00000000000..46e49e22386
--- /dev/null
+++ b/src/inet/linklayer/ieee80211/mac/contract/IBlockAckAgreementLifecycle.h
@@ -0,0 +1,22 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+
+#ifndef __INET_IBLOCKACKAGREEMENTLIFECYCLE_H
+#define __INET_IBLOCKACKAGREEMENTLIFECYCLE_H
+
+#include "inet/linklayer/common/MacAddress.h"
+
+namespace inet {
+namespace ieee80211 {
+
+// Management supplies the old peer identity before association replacement or loss.
+class INET_API IBlockAckAgreementLifecycle
+{
+ public:
+ virtual ~IBlockAckAgreementLifecycle() {}
+ virtual void resetPeerAgreements(const MacAddress& peer) = 0;
+};
+
+} // namespace ieee80211
+} // namespace inet
+
+#endif
diff --git a/src/inet/linklayer/ieee80211/mac/contract/IFrameSequenceHandler.h b/src/inet/linklayer/ieee80211/mac/contract/IFrameSequenceHandler.h
index ee9d4d37465..d2002a5ffdc 100644
--- a/src/inet/linklayer/ieee80211/mac/contract/IFrameSequenceHandler.h
+++ b/src/inet/linklayer/ieee80211/mac/contract/IFrameSequenceHandler.h
@@ -48,6 +48,8 @@ class INET_API IFrameSequenceHandler
virtual void transmissionComplete() = 0;
virtual bool isSequenceRunning() = 0;
virtual void handleStartRxTimeout() = 0;
+ // End a response wait after recipient processing. The sequence suppresses failure for retired DATA or management frames.
+ virtual void interruptFrameSequence() = 0;
};
} // namespace ieee80211
diff --git a/src/inet/linklayer/ieee80211/mac/contract/IOriginatorBlockAckAgreementHandler.h b/src/inet/linklayer/ieee80211/mac/contract/IOriginatorBlockAckAgreementHandler.h
index 4a01e89b6c3..7161304e0ad 100644
--- a/src/inet/linklayer/ieee80211/mac/contract/IOriginatorBlockAckAgreementHandler.h
+++ b/src/inet/linklayer/ieee80211/mac/contract/IOriginatorBlockAckAgreementHandler.h
@@ -22,7 +22,27 @@ namespace ieee80211 {
class INET_API IOriginatorBlockAckAgreementHandler
{
public:
+ typedef std::pair AgreementId;
+ enum class ProcedureState { SETUP_INTENT, REQUEST_PENDING, TEARDOWN_PENDING, RECOVERY_NEEDED, WAITING_FOR_SUCCESSOR };
+ struct Procedure {
+ ProcedureState state = ProcedureState::SETUP_INTENT;
+ bool requestAttempted = false;
+ simtime_t responseDeadline = SIMTIME_MAX;
+ };
virtual ~IOriginatorBlockAckAgreementHandler() {}
+ virtual const std::map& getAgreements() const = 0;
+ virtual const std::map& getProcedures() const = 0;
+ virtual simtime_t computeEarliestExpirationTime() = 0;
+ virtual OriginatorBlockAckAgreement *detachAgreement(MacAddress peer, Tid tid) = 0;
+ virtual std::vector detachPeer(MacAddress peer) = 0;
+ virtual void noteEligibleDataTransmission(MacAddress peer, Tid tid) = 0;
+ virtual void beginSetupIntent(MacAddress peer, Tid tid) = 0;
+ virtual void beginTeardown(MacAddress peer, Tid tid) = 0;
+ virtual void failTeardown(MacAddress peer, Tid tid) = 0;
+ virtual void finishProcedure(MacAddress peer, Tid tid) = 0;
+ virtual void markRequestAttempted(MacAddress peer, Tid tid) = 0;
+ virtual void acknowledgeRequest(MacAddress peer, Tid tid, simtime_t timeout) = 0;
+ virtual void startSetup(MacAddress peer, Tid tid, SequenceNumberCyclic sequence, IOriginatorBlockAckAgreementPolicy *policy, IProcedureCallback *callback, bool aMsdu) = 0;
virtual void processReceivedBlockAck(const Ptr& blockAck, IBlockAckAgreementHandlerCallback *callback) = 0;
virtual void processTransmittedAddbaReq(const Ptr& addbaReq) = 0;
diff --git a/src/inet/linklayer/ieee80211/mac/contract/IOriginatorBlockAckAgreementPolicy.h b/src/inet/linklayer/ieee80211/mac/contract/IOriginatorBlockAckAgreementPolicy.h
index 40ea82aa95e..c6e1ddd9638 100644
--- a/src/inet/linklayer/ieee80211/mac/contract/IOriginatorBlockAckAgreementPolicy.h
+++ b/src/inet/linklayer/ieee80211/mac/contract/IOriginatorBlockAckAgreementPolicy.h
@@ -9,6 +9,7 @@
#define __INET_IORIGINATORBLOCKACKAGREEMENTPOLICY_H
#include "inet/common/packet/Packet.h"
+#include "inet/linklayer/ieee80211/mac/contract/BlockAckVariant.h"
#include "inet/linklayer/ieee80211/mac/Ieee80211Frame_m.h"
namespace inet {
@@ -21,6 +22,8 @@ class INET_API IOriginatorBlockAckAgreementPolicy
public:
virtual ~IOriginatorBlockAckAgreementPolicy() {}
+ virtual bool isProfileAllowed(BlockAckVariant variant, bool localHt, bool peerHt) const = 0;
+
virtual bool isAddbaReqNeeded(Packet *packet, const Ptr& header) = 0;
virtual bool isAddbaReqAccepted(const Ptr& addbaResp, OriginatorBlockAckAgreement *agreement) = 0;
virtual bool isDelbaAccepted(const Ptr& delba) = 0;
diff --git a/src/inet/linklayer/ieee80211/mac/contract/IOriginatorQoSAckPolicy.h b/src/inet/linklayer/ieee80211/mac/contract/IOriginatorQoSAckPolicy.h
index 94a583a111a..641e21e8ecd 100644
--- a/src/inet/linklayer/ieee80211/mac/contract/IOriginatorQoSAckPolicy.h
+++ b/src/inet/linklayer/ieee80211/mac/contract/IOriginatorQoSAckPolicy.h
@@ -10,6 +10,7 @@
#include "inet/linklayer/ieee80211/mac/Ieee80211Frame_m.h"
#include "inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreement.h"
+#include "inet/linklayer/ieee80211/mac/contract/IOriginatorBlockAckAgreementHandler.h"
#include "inet/linklayer/ieee80211/mac/originator/TxopProcedure.h"
#include "inet/linklayer/ieee80211/mac/queue/InProgressFrames.h"
@@ -23,9 +24,9 @@ class INET_API IOriginatorQoSAckPolicy
virtual bool isAckNeeded(const Ptr& header) const = 0;
virtual AckPolicy computeAckPolicy(Packet *packet, const Ptr& header, OriginatorBlockAckAgreement *agreement) const = 0;
- virtual bool isBlockAckReqNeeded(InProgressFrames *inProgressFrames, TxopProcedure *txopProcedure) const = 0;
+ virtual bool isBlockAckReqNeeded(InProgressFrames *inProgressFrames, TxopProcedure *txopProcedure, IOriginatorBlockAckAgreementHandler *agreements) const = 0;
virtual bool isBlockAckPolicyEligibleFrame(Packet *packet, const Ptr& header) const = 0;
- virtual std::tuple computeBlockAckReqParameters(InProgressFrames *inProgressFrames, TxopProcedure *txopProcedure) const = 0;
+ virtual std::tuple computeBlockAckReqParameters(InProgressFrames *inProgressFrames, TxopProcedure *txopProcedure, IOriginatorBlockAckAgreementHandler *agreements) const = 0;
virtual simtime_t getAckTimeout(Packet *packet, const Ptr& dataOrMgmtHeader) const = 0;
virtual simtime_t getBlockAckTimeout(Packet *packet, const Ptr& blockAckReq) const = 0;
diff --git a/src/inet/linklayer/ieee80211/mac/contract/IOriginatorQosMacDataService.h b/src/inet/linklayer/ieee80211/mac/contract/IOriginatorQosMacDataService.h
new file mode 100644
index 00000000000..04f0ecda71b
--- /dev/null
+++ b/src/inet/linklayer/ieee80211/mac/contract/IOriginatorQosMacDataService.h
@@ -0,0 +1,48 @@
+//
+// Copyright (C) 2026 OpenSim Ltd.
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+
+#ifndef __INET_IORIGINATORQOSMACDATASERVICE_H
+#define __INET_IORIGINATORQOSMACDATASERVICE_H
+
+#include
+
+#include "inet/linklayer/ieee80211/mac/Ieee80211Frame_m.h"
+#include "inet/linklayer/ieee80211/mac/contract/IOriginatorMacDataService.h"
+
+namespace inet {
+namespace ieee80211 {
+
+/** Owns a reversible DATA preview. Source packet identities refer to frames still owned by the queue. */
+struct INET_API PreparedQosData : public cObject
+{
+ std::unique_ptr packet;
+ queueing::IPacketQueue *queue = nullptr;
+ std::vector sourceIds;
+ bool aggregate = false;
+
+ ~PreparedQosData() override;
+ void setPacket(Packet *value);
+ Packet *releasePacket();
+};
+
+class INET_API IOriginatorQosMacDataService : public IOriginatorMacDataService
+{
+ public:
+ // Preview borrows the frame without extraction or sequence assignment.
+ virtual Packet *peekEligibleFrame(queueing::IPacketQueue *queue) const = 0;
+ virtual SequenceNumberCyclic getNextSequenceNumber(MacAddress peer, Tid tid) const = 0;
+ virtual bool hasAMsduAggregation() const = 0;
+ // These QoS DATA methods separate reversible preparation from extraction and sequence assignment.
+ virtual std::unique_ptr prepareFrame(queueing::IPacketQueue *queue, bool allowAggregation) = 0;
+ virtual bool isPreparationValid(const PreparedQosData& preparation, simtime_t start) const = 0;
+ // Success transfers the preview to the caller. Failure retains the preview and all queued sources.
+ virtual Packet *commitPreparedFrame(PreparedQosData& preparation) = 0;
+};
+
+} // namespace ieee80211
+} // namespace inet
+
+#endif
diff --git a/src/inet/linklayer/ieee80211/mac/contract/IQosRateSelection.h b/src/inet/linklayer/ieee80211/mac/contract/IQosRateSelection.h
index 34a38ac1d9a..10c35f613a0 100644
--- a/src/inet/linklayer/ieee80211/mac/contract/IQosRateSelection.h
+++ b/src/inet/linklayer/ieee80211/mac/contract/IQosRateSelection.h
@@ -32,6 +32,9 @@ class INET_API IQosRateSelection
virtual const physicallayer::IIeee80211Mode *computeResponseCtsFrameMode(Packet *packet, const Ptr& rtsFrame, ResponseQueryRole role) = 0;
virtual const physicallayer::IIeee80211Mode *computeResponseAckFrameMode(Packet *packet, const Ptr& dataOrMgmtHeader, ResponseQueryRole role) = 0;
virtual const physicallayer::IIeee80211Mode *computeResponseBlockAckFrameMode(Packet *packet, const Ptr& blockAckReq, ResponseQueryRole role) = 0;
+ // HCF derives startsTxop from the current frame sequence before this query.
+ virtual const physicallayer::IIeee80211Mode *computeCompressedBlockAckReqMode(Packet *packet,
+ const Ptr& blockAckReq, bool startsTxop) = 0;
virtual const physicallayer::IIeee80211Mode *computeMode(Packet *packet, const Ptr& header, TxopProcedure *txopProcedure) = 0;
};
diff --git a/src/inet/linklayer/ieee80211/mac/contract/IRecipientBlockAckAgreementHandler.h b/src/inet/linklayer/ieee80211/mac/contract/IRecipientBlockAckAgreementHandler.h
index 357745c3ddb..5c4beb3fe04 100644
--- a/src/inet/linklayer/ieee80211/mac/contract/IRecipientBlockAckAgreementHandler.h
+++ b/src/inet/linklayer/ieee80211/mac/contract/IRecipientBlockAckAgreementHandler.h
@@ -23,6 +23,13 @@ class INET_API IRecipientBlockAckAgreementHandler
{
public:
virtual ~IRecipientBlockAckAgreementHandler() {}
+ virtual simtime_t computeEarliestExpirationTime() = 0;
+ virtual const std::map, RecipientBlockAckAgreement *>& getAgreements() const = 0;
+ virtual RecipientBlockAckAgreement *detachAgreement(MacAddress peer, Tid tid) = 0;
+ virtual std::vector detachPeer(MacAddress peer) = 0;
+ virtual bool isTeardownPending(MacAddress peer, Tid tid) const = 0;
+ virtual void beginTeardown(MacAddress peer, Tid tid) = 0;
+ virtual void finishTeardown(MacAddress peer, Tid tid) = 0;
virtual void processTransmittedAddbaResp(const Ptr& addbaResp, IBlockAckAgreementHandlerCallback *callback) = 0;
virtual void processReceivedAddbaRequest(const Ptr& addbaRequest, IRecipientBlockAckAgreementPolicy *blockAckAgreementPolicy, IProcedureCallback *callback) = 0;
diff --git a/src/inet/linklayer/ieee80211/mac/contract/IRecipientBlockAckAgreementPolicy.h b/src/inet/linklayer/ieee80211/mac/contract/IRecipientBlockAckAgreementPolicy.h
index 48009a67161..2f429ef7169 100644
--- a/src/inet/linklayer/ieee80211/mac/contract/IRecipientBlockAckAgreementPolicy.h
+++ b/src/inet/linklayer/ieee80211/mac/contract/IRecipientBlockAckAgreementPolicy.h
@@ -9,6 +9,7 @@
#define __INET_IRECIPIENTBLOCKACKAGREEMENTPOLICY_H
#include "inet/linklayer/ieee80211/mac/Ieee80211Frame_m.h"
+#include "inet/linklayer/ieee80211/mac/contract/BlockAckVariant.h"
namespace inet {
namespace ieee80211 {
@@ -18,6 +19,8 @@ class INET_API IRecipientBlockAckAgreementPolicy
public:
virtual ~IRecipientBlockAckAgreementPolicy() {}
+ virtual bool isProfileAllowed(BlockAckVariant variant, bool localHt, bool peerHt) const = 0;
+
virtual bool isAddbaReqAccepted(const Ptr& addbaReq) = 0;
virtual bool isDelbaAccepted(const Ptr& delba) = 0;
diff --git a/src/inet/linklayer/ieee80211/mac/contract/IRecipientQosAckPolicy.h b/src/inet/linklayer/ieee80211/mac/contract/IRecipientQosAckPolicy.h
index ccf47cc5610..42aaecc9db8 100644
--- a/src/inet/linklayer/ieee80211/mac/contract/IRecipientQosAckPolicy.h
+++ b/src/inet/linklayer/ieee80211/mac/contract/IRecipientQosAckPolicy.h
@@ -26,6 +26,8 @@ class INET_API IRecipientQosAckPolicy
virtual simtime_t computeAckDurationField(Packet *packet, const Ptr& header) const = 0;
virtual simtime_t computeBasicBlockAckDurationField(Packet *packet, const Ptr& basicBlockAckReq) const = 0;
+ // The request selects the response variant and its actual airtime.
+ virtual simtime_t computeCompressedBlockAckDurationField(Packet *packet, const Ptr& blockAckReq) const = 0;
};
} // namespace ieee80211
diff --git a/src/inet/linklayer/ieee80211/mac/contract/IRecipientQosMacDataService.h b/src/inet/linklayer/ieee80211/mac/contract/IRecipientQosMacDataService.h
index 8a3ff4c0b7c..7aded713794 100644
--- a/src/inet/linklayer/ieee80211/mac/contract/IRecipientQosMacDataService.h
+++ b/src/inet/linklayer/ieee80211/mac/contract/IRecipientQosMacDataService.h
@@ -23,8 +23,11 @@ class INET_API IRecipientQosMacDataService
public:
virtual ~IRecipientQosMacDataService() {}
+ virtual std::vector retireAgreement(RecipientBlockAckAgreement *agreement, bool associationReset) = 0;
+ virtual void clearPeer(MacAddress peer) = 0;
- virtual std::vector dataFrameReceived(Packet *dataPacket, const Ptr& dataHeader, IRecipientBlockAckAgreementHandler *blockAckAgreementHandler) = 0;
+ // HCF supplies the observed SIFS or permitted RIFS relation for this frame.
+ virtual std::vector dataFrameReceived(Packet *dataPacket, const Ptr& dataHeader, IRecipientBlockAckAgreementHandler *blockAckAgreementHandler, bool interframe = false) = 0;
virtual std::vector controlFrameReceived(Packet *controlPacket, const Ptr& controlHeader, IRecipientBlockAckAgreementHandler *blockAckAgreementHandler) = 0;
virtual std::vector managementFrameReceived(Packet *mgmtPacket, const Ptr& mgmtHeader) = 0;
};
diff --git a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc
index a05c4f1d269..601eb39739e 100644
--- a/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc
+++ b/src/inet/linklayer/ieee80211/mac/coordinationfunction/Hcf.cc
@@ -10,6 +10,7 @@
#include "inet/common/ModuleAccess.h"
#include "inet/common/Simsignals.h"
#include "inet/linklayer/ieee80211/mac/Ieee80211Mac.h"
+#include "inet/linklayer/ieee80211/mac/Ieee80211DataSequenceTag_m.h"
#include "inet/linklayer/ieee80211/mac/contract/FrameTransmissionDetails_m.h"
#include "inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckAgreementHandler.h"
#include "inet/linklayer/ieee80211/mac/blockack/OriginatorBlockAckProcedure.h"
@@ -17,9 +18,17 @@
#include "inet/linklayer/ieee80211/mac/framesequence/FrameSequenceStep.h"
#include "inet/linklayer/ieee80211/mac/framesequence/HcfFs.h"
#include "inet/linklayer/ieee80211/mac/rateselection/RateSelection.h"
+#include "inet/linklayer/ieee80211/mac/rateselection/Ieee80211PeerModeSelection.h"
#include "inet/linklayer/ieee80211/mac/recipient/RecipientAckProcedure.h"
#include "inet/linklayer/ieee80211/mgmt/Ieee80211MgmtTransactionTag_m.h"
+#include "inet/linklayer/ieee80211/mgmt/Ieee80211MgmtFrame_m.h"
+#include "inet/linklayer/ieee80211/mib/Ieee80211Mib.h"
+#include "inet/queueing/contract/ISelectivePacketCollection.h"
#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Tag_m.h"
+#include "inet/physicallayer/wireless/ieee80211/packetlevel/Ieee80211Transmission.h"
+#include "inet/physicallayer/wireless/common/contract/packetlevel/IRadioMedium.h"
+#include "inet/physicallayer/wireless/common/contract/packetlevel/IReception.h"
+#include "inet/physicallayer/wireless/common/contract/packetlevel/SignalTag_m.h"
namespace inet {
namespace ieee80211 {
@@ -43,12 +52,12 @@ void Hcf::initialize(int stage)
blockAckVariant = BlockAckVariant::COMPRESSED;
else
throw cRuntimeError("Unknown blockAckVariant \"%s\": expected \"basic\" or \"compressed\"", configuredVariant);
- if (blockAckVariant == BlockAckVariant::COMPRESSED)
- throw cRuntimeError("Compressed Block Ack exchanges are not implemented");
mac = check_and_cast(getContainingNicModule(this)->getSubmodule("mac"));
+ mib = check_and_cast(mac->getModuleByPath(mac->par("mibModule")));
startRxTimer = new cMessage("startRxTimeout");
inactivityTimer = new cMessage("blockAckInactivityTimer");
dataLifetimeTimer = new cMessage("dataLifetimeTimer");
+ addbaResponseTimer = new cMessage("addbaResponseTimer");
edca = check_and_cast(getSubmodule("edca"));
hcca = check_and_cast(getSubmodule("hcca"));
tx = check_and_cast(getModuleByPath(par("txModule")));
@@ -59,6 +68,7 @@ void Hcf::initialize(int stage)
rateSelection = check_and_cast(getSubmodule("rateSelection"));
frameSequenceHandler = new FrameSequenceHandler();
WATCH_EXPR("frameSequenceInfo", getFrameSequenceInfo());
+ WATCH_EXPR("numPendingInterframeReceptions", pendingReceptions.size());
originatorDataService = check_and_cast(getSubmodule(("originatorMacDataService")));
recipientDataService = check_and_cast(getSubmodule("recipientMacDataService"));
originatorAckPolicy = check_and_cast(getSubmodule("originatorAckPolicy"));
@@ -77,16 +87,47 @@ void Hcf::initialize(int stage)
}
}
else if (stage == INITSTAGE_LAST) {
+ if (blockAckVariant == BlockAckVariant::COMPRESSED && par("isBlockAckSupported").boolValue()) {
+ interframeRadio = check_and_cast(mac->gate("lowerLayerOut")->getNextGate()->getOwnerModule());
+ auto radioModule = check_and_cast(interframeRadio.get());
+ for (auto signal : {receptionStartedSignal, receptionEndedSignal, transmissionStartedSignal, transmissionEndedSignal, IRadio::receptionStateChangedSignal})
+ radioModule->subscribe(signal, this);
+ }
+ if (par("isBlockAckSupported").boolValue()) {
+ EV_INFO << "Block Ack profile: " << (blockAckVariant == BlockAckVariant::BASIC ? "basic" : "compressed") << endl;
+ if (blockAckVariant == BlockAckVariant::COMPRESSED) {
+ if (originatorBlockAckAgreementPolicy && originatorBlockAckAgreementPolicy->isDelayedAckPolicySupported())
+ throw cRuntimeError("Compressed Block Ack does not support delayedAckPolicySupported in %s.originatorBlockAckAgreementPolicy", getFullPath().c_str());
+ if (recipientBlockAckAgreementPolicy && recipientBlockAckAgreementPolicy->delayedBlockAckPolicySupported())
+ throw cRuntimeError("Compressed Block Ack does not support delayedAckPolicySupported in %s.recipientBlockAckAgreementPolicy", getFullPath().c_str());
+ if (!mib->isHtOperationSupported())
+ EV_WARN << "Compressed Block Ack has no usable local HT support; DATA uses Normal Ack. Set blockAckVariant = basic for Basic compatibility.\n";
+ }
+ }
// Edca resolves its Edcaf array at the link-layer stage. Install the
// queue signal listeners after all child initialization has completed.
auto lifetimeHandler = edca->getLifetimeHandler();
lifetimeHandler->setDeadlineChangedCallback([this]() { scheduleDataLifetimeTimer(); });
check_and_cast(originatorDataService)->setLifetimeHandler(
lifetimeHandler, [this]() { expireDataFrames(); });
+ check_and_cast(originatorDataService)->setEligibility(
+ [this](Packet *packet) { return isFrameEligible(packet, false); },
+ [this](Packet *packet) { return isAMsduPermitted(packet); });
+ check_and_cast(originatorDataService)->setFragmentationPermission([this](Packet *packet) {
+ auto data = dynamicPtrCast(packet->peekAtFront());
+ if (blockAckVariant != BlockAckVariant::COMPRESSED || !data || data->getType() != ST_DATA_WITH_QOS || !originatorBlockAckAgreementHandler)
+ return true;
+ auto agreement = originatorBlockAckAgreementHandler->getAgreement(data->getReceiverAddress(), data->getTid());
+ return !agreement || !agreement->getIsAddbaResponseReceived();
+ });
+ mib->subscribe(Ieee80211Mib::rateStateChangedSignal, this);
for (int ac = 0; ac < AC_NUMCATEGORIES; ac++) {
+ edca->getEdcaf(static_cast(ac))->setFrameEligibility(
+ [this, ac]() { return hasEligibleFrameToTransmit(static_cast(ac)); });
check_and_cast(edca->getEdcaf(static_cast(ac))->getPendingQueue())->subscribe(packetDroppedSignal, this);
auto frames = edca->getEdcaf(static_cast(ac))->getInProgressFrames();
frames->setLifetimeHandler(lifetimeHandler);
+ frames->setTransmissionEligibility([this](Packet *packet) { return isFrameEligible(packet, true); });
frames->subscribe(InProgressFrames::packetDequeuedSignal, this);
}
}
@@ -179,6 +220,8 @@ void Hcf::expireDataFrames(bool preserveSequence)
PacketDropDetails details;
details.setReason(LIFETIME_EXPIRED);
emit(packetDroppedSignal, packet, &details);
+ if (inProgress)
+ firstDataDiscarded(header);
if (!inProgress)
delete packet;
}
@@ -189,6 +232,7 @@ void Hcf::expireDataFrames(bool preserveSequence)
frames->clearDroppedFrames();
}
scheduleDataLifetimeTimer();
+ advanceOriginatorWindows();
}
void Hcf::forEachChild(cVisitor *v)
@@ -200,8 +244,24 @@ void Hcf::forEachChild(cVisitor *v)
void Hcf::handleMessage(cMessage *msg)
{
- if (msg == dataLifetimeTimer)
+ if (msg == addbaResponseTimer) {
+ std::vector> expired;
+ for (const auto& entry : originatorBlockAckAgreementHandler->getProcedures()) {
+ if (entry.second.responseDeadline <= simTime())
+ expired.push_back(entry.first);
+ }
+ for (const auto& id : expired)
+ beginTeardown(id.first, id.second, true, 39);
+ scheduleAddbaResponseTimer();
+ updateContention();
+ }
+ else if (msg == dataLifetimeTimer) {
expireDataFrames();
+ if (!frameSequenceHandler->isSequenceRunning())
+ completeTransfers();
+ updateAgreementProcedures();
+ updateContention();
+ }
else if (msg == startRxTimer) {
if (!isReceptionInProgress()) {
frameSequenceHandler->handleStartRxTimeout();
@@ -209,8 +269,19 @@ void Hcf::handleMessage(cMessage *msg)
}
else if (msg == inactivityTimer) {
if (originatorBlockAckAgreementHandler && recipientBlockAckAgreementHandler) {
- originatorBlockAckAgreementHandler->blockAckAgreementExpired(this, this);
- recipientBlockAckAgreementHandler->blockAckAgreementExpired(this, this);
+ std::vector> expired;
+ for (const auto& entry : originatorBlockAckAgreementHandler->getAgreements()) {
+ if (entry.second->getIsAddbaResponseReceived() && entry.second->getExpirationTime() <= simTime())
+ expired.push_back({entry.first.first, entry.first.second, true});
+ }
+ for (const auto& entry : recipientBlockAckAgreementHandler->getAgreements()) {
+ if (entry.second->getExpirationTime() <= simTime())
+ expired.push_back({entry.first.first, entry.first.second, false});
+ }
+ for (const auto& entry : expired)
+ beginTeardown(std::get<0>(entry), std::get<1>(entry), std::get<2>(entry), 39);
+ scheduleInactivityTimer();
+ updateContention();
}
else
throw cRuntimeError("Unknown event");
@@ -255,7 +326,8 @@ void Hcf::processUpperFrame(Packet *packet, const PtrgetType() == ST_DATA_WITH_QOS)
edca->getLifetimeHandler()->registerFrame(packet, ac);
pendingQueue->enqueuePacket(packet);
- if (!pendingQueue->isEmpty()) {
+ updateAgreementProcedures();
+ if (!pendingQueue->isEmpty() && !handlingAgreementAction && hasFrameToTransmit(ac)) {
auto edcaf = edca->getChannelOwner();
if (edcaf == nullptr || edcaf->getAccessCategory() != ac) {
EV_DETAIL << "Requesting channel for access category " << printAccessCategory(ac) << endl;
@@ -266,7 +338,59 @@ void Hcf::processUpperFrame(Packet *packet, const Ptr(interframeRadio.get()) && signalID == receptionStartedSignal) {
+ auto reception = check_and_cast(obj);
+ auto transmission = reception->getTransmission();
+ std::set active;
+ for (auto ongoing : interframeRadio->getReceptionsInProgress())
+ active.insert(ongoing->getId());
+ for (auto it = pendingReceptions.begin(); it != pendingReceptions.end();) {
+ if (!it->second.completed && !active.count(it->first))
+ it = pendingReceptions.erase(it);
+ else
+ ++it;
+ }
+ PendingReception pending;
+ auto medium = interframeRadio->getMedium();
+ pending.timing = medium->getTransmissionTimingSnapshot(transmission->getId());
+ auto wifi = dynamic_cast(transmission);
+ pending.ht = wifi && wifi->getMode()->getModulationClass() == IIeee80211Mode::ModulationClass::HT;
+ if (wifi)
+ pending.extension = wifi->getSignalExtensionDuration();
+ auto gap = medium->computePrecedingSignalGap(transmission->getId(), interframeHistory.timing);
+ if (gap && (interframeHistory.mpdu || interframeHistory.ack)) {
+ // IEEE Std 802.11-2024, 10.3.2.3.3: this model uses coverage class zero.
+ pending.sifs = std::abs((*gap - modeSet->getSifsTime()).raw()) <= modeSet->getSlotTime().raw() / 10;
+ if (interframeHistory.mpdu && interframeHistory.ht && pending.ht && interframeHistory.noSifsResponse &&
+ interframeHistory.timing.transmitterRadioId == pending.timing.transmitterRadioId && interframeHistory.extension) {
+ auto rifs = wifi->getMode()->getRifsTime();
+ // IEEE Std 802.11-2024, 10.3.2.3.2 and 10.3.8: RIFS excludes the actual preceding extension.
+ pending.rifs = std::abs((*gap + *interframeHistory.extension - rifs).raw()) <= rifs.raw() / 10;
+ pending.precedingReceiver = interframeHistory.receiver;
+ }
+ }
+ interframeHistory = {};
+ pendingReceptions.insert_or_assign(transmission->getId(), std::move(pending));
+ }
+ else if (interframeRadio && source == dynamic_cast(interframeRadio.get()) && signalID == receptionEndedSignal) {
+ auto reception = check_and_cast(obj);
+ auto pending = pendingReceptions.find(reception->getTransmission()->getId());
+ if (pending != pendingReceptions.end())
+ pending->second.completed = true;
+ }
+ else if (interframeRadio && source == dynamic_cast(interframeRadio.get()) && signalID == transmissionStartedSignal)
+ clearInterframeHistory();
+ else if (interframeRadio && source == dynamic_cast(interframeRadio.get()) && signalID == transmissionEndedSignal) {
+ clearInterframeHistory();
+ auto transmission = check_and_cast(obj);
+ if (completedTransmissionWasAck) {
+ interframeHistory.timing = interframeRadio->getMedium()->getTransmissionTimingSnapshot(transmission->getId());
+ interframeHistory.ack = true;
+ }
+ completedTransmissionWasAck = false;
+ }
+ else if (signalID == InProgressFrames::packetDequeuedSignal) {
Enter_Method("packetDequeued");
edca->getLifetimeHandler()->forgetFrame(check_and_cast(obj)->getId());
}
@@ -274,6 +398,7 @@ void Hcf::receiveSignal(cComponent *source, simsignal_t signalID, cObject *obj,
Enter_Method("%s", cComponent::getSignalName(signalID));
auto packet = check_and_cast(obj);
edca->getLifetimeHandler()->forgetFrame(packet->getId());
+ actionTerminated(packet, false);
if (packet->findTag() != nullptr) {
FrameTransmissionDetails transmissionDetails;
transmissionDetails.setStatus(FRAME_TRANSMISSION_STATUS_DROPPED_BEFORE_TRANSMISSION);
@@ -284,24 +409,614 @@ void Hcf::receiveSignal(cComponent *source, simsignal_t signalID, cObject *obj,
ModeSetListener::receiveSignal(source, signalID, obj, details);
}
+void Hcf::receiveSignal(cComponent *source, simsignal_t signalID, intval_t value, cObject *details)
+{
+ Enter_Method_Silent();
+ if (interframeRadio && source == dynamic_cast(interframeRadio.get()) && signalID == IRadio::receptionStateChangedSignal &&
+ value != IRadio::RECEPTION_STATE_RECEIVING) {
+ bool incomplete = false;
+ for (auto it = pendingReceptions.begin(); it != pendingReceptions.end();) {
+ if (!it->second.completed) {
+ incomplete = true;
+ it = pendingReceptions.erase(it);
+ }
+ else
+ ++it;
+ }
+ if (incomplete || value == IRadio::RECEPTION_STATE_BUSY || value == IRadio::RECEPTION_STATE_UNDEFINED)
+ interframeHistory = {};
+ }
+}
+
+void Hcf::clearInterframeHistory()
+{
+ interframeHistory = {};
+ pendingReceptions.clear();
+}
+
+bool Hcf::receiveInterframe(Packet *packet, const Ptr& header)
+{
+ auto time = packet->findTag();
+ if (!time)
+ return false;
+ auto entry = pendingReceptions.find(time->getTransmissionId());
+ if (entry == pendingReceptions.end() || !entry->second.completed)
+ return false;
+ auto pending = std::move(entry->second);
+ pendingReceptions.erase(entry);
+ // IEEE Std 802.11-2024, 10.27.3.1, 10.27.3.3, and 11.38.1: the BSS controls RIFS permission.
+ bool rifsPermitted = pending.rifs && modeSet->getPhyType() != Ieee80211ModeSet::PhyType::VHT &&
+ (mib->mode == Ieee80211Mib::INDEPENDENT || mib->mode == Ieee80211Mib::MESH || mib->getHtOperation().rifsMode);
+ bool related = pending.sifs || (pending.rifs && rifsPermitted && pending.precedingReceiver == header->getReceiverAddress());
+ interframeHistory.timing = std::move(pending.timing);
+ interframeHistory.mpdu = true;
+ interframeHistory.ht = pending.ht;
+ interframeHistory.receiver = header->getReceiverAddress();
+ interframeHistory.extension = pending.extension;
+ auto dataOrMgmt = dynamicPtrCast(header);
+ interframeHistory.noSifsResponse = dataOrMgmt ? !recipientAckPolicy->isAckNeeded(dataOrMgmt) :
+ !dynamicPtrCast(header) && !dynamicPtrCast(header);
+ return related;
+}
+
void Hcf::scheduleStartRxTimer(simtime_t timeout)
{
Enter_Method("scheduleStartRxTimer");
scheduleAfter(timeout, startRxTimer);
}
-void Hcf::scheduleInactivityTimer(simtime_t timeout)
+void Hcf::scheduleInactivityTimer()
{
Enter_Method("scheduleInactivityTimer");
- rescheduleAfter(timeout, inactivityTimer);
+ // IEEE Std 802.11-2024, 11.5.4 requires inactivity expiration. HCF reads each role's absolute deadline.
+ simtime_t deadline = SIMTIME_MAX;
+ if (originatorBlockAckAgreementHandler && recipientBlockAckAgreementHandler)
+ deadline = std::min(originatorBlockAckAgreementHandler->computeEarliestExpirationTime(),
+ recipientBlockAckAgreementHandler->computeEarliestExpirationTime());
+ cancelEvent(inactivityTimer);
+ if (deadline != SIMTIME_MAX)
+ scheduleAt(std::max(simTime(), deadline), inactivityTimer);
+}
+
+void Hcf::scheduleAddbaResponseTimer()
+{
+ simtime_t deadline = SIMTIME_MAX;
+ if (originatorBlockAckAgreementHandler) {
+ for (const auto& entry : originatorBlockAckAgreementHandler->getProcedures())
+ deadline = std::min(deadline, entry.second.responseDeadline);
+ }
+ cancelEvent(addbaResponseTimer);
+ if (deadline != SIMTIME_MAX)
+ scheduleAt(std::max(simTime(), deadline), addbaResponseTimer);
+}
+
+bool Hcf::isProfileAllowed(MacAddress peer, bool originator) const
+{
+ auto state = mib->findPeerHtState(peer);
+ bool peerHt = state && state->valid && (originator ? state->negotiatedCapabilities.localTxPeerRx.valid :
+ state->negotiatedCapabilities.localRxPeerTx.valid);
+ return originator ? originatorBlockAckAgreementPolicy->isProfileAllowed(blockAckVariant, mib->isHtOperationSupported(), peerHt) :
+ recipientBlockAckAgreementPolicy->isProfileAllowed(blockAckVariant, mib->isHtOperationSupported(), peerHt);
+}
+
+bool Hcf::hasObsoleteActionInFlight(MacAddress peer, Tid tid, bool originator) const
+{
+ if (finishingSequence || !frameSequenceHandler->isSequenceRunning())
+ return false;
+ auto context = frameSequenceHandler->getContext();
+ for (int i = 0; i < context->getNumSteps(); i++) {
+ auto step = dynamic_cast(context->getStep(i));
+ if (!step)
+ continue;
+ auto packet = step->getFrameToTransmit();
+ if (!obsoleteActions.count(packet->getId()))
+ continue;
+ auto header = packet->peekAtFront();
+ if (header->getReceiverAddress() != peer)
+ continue;
+ if (auto request = dynamicPtrCast(header)) {
+ if (originator && request->getTid() == tid)
+ return true;
+ }
+ else if (auto response = dynamicPtrCast(header)) {
+ if (!originator && response->getTid() == tid)
+ return true;
+ }
+ else if (auto delba = dynamicPtrCast(header)) {
+ if (delba->getInitiator() == originator && delba->getTid() == tid)
+ return true;
+ }
+ }
+ return false;
+}
+
+bool Hcf::isFrameEligible(Packet *packet, bool numbered) const
+{
+ if (obsoleteActions.count(packet->getId()))
+ return false;
+ auto data = dynamicPtrCast(packet->peekAtFront());
+ if (!data || data->getType() != ST_DATA_WITH_QOS || !originatorBlockAckAgreementHandler)
+ return true;
+ auto peer = data->getReceiverAddress();
+ auto tid = data->getTid();
+ if (hasObsoleteActionInFlight(peer, tid, true))
+ return false;
+ const auto& procedures = originatorBlockAckAgreementHandler->getProcedures();
+ auto it = procedures.find({peer, tid});
+ if (it == procedures.end())
+ return true;
+ if (it->second.state == IOriginatorBlockAckAgreementHandler::ProcedureState::WAITING_FOR_SUCCESSOR)
+ return true;
+ return numbered && it->second.state == IOriginatorBlockAckAgreementHandler::ProcedureState::SETUP_INTENT;
+}
+
+bool Hcf::isAMsduPermitted(Packet *packet) const
+{
+ auto data = dynamicPtrCast(packet->peekAtFront());
+ if (!data || !originatorBlockAckAgreementHandler)
+ return true;
+ auto agreement = originatorBlockAckAgreementHandler->getAgreement(data->getReceiverAddress(), data->getTid());
+ return !agreement || !agreement->getIsAddbaResponseReceived() || agreement->isAMsduPermitted();
+}
+
+bool Hcf::hasQueuedData(MacAddress peer, Tid tid) const
+{
+ auto matches = [this, peer, tid](Packet *packet) {
+ auto data = dynamicPtrCast(packet->peekAtFront());
+ return data && data->getType() == ST_DATA_WITH_QOS && data->getReceiverAddress() == peer && data->getTid() == tid &&
+ !edca->getLifetimeHandler()->isExpired(packet);
+ };
+ // Check the TID's category first. Other categories retain support for custom classification.
+ int first = edca->mapTidToAc(tid);
+ for (int offset = 0; offset < AC_NUMCATEGORIES; offset++) {
+ int ac = (first + offset) % AC_NUMCATEGORIES;
+ auto queue = edca->getEdcaf(static_cast(ac))->getPendingQueue();
+ if (auto selected = dynamic_cast(queue)) {
+ try {
+ if (selected->peekEligiblePacket(matches))
+ return true;
+ continue;
+ }
+ catch (const queueing::UnsupportedPacketSelection&) {
+ // Existence checks also support queues without optional selected extraction.
+ }
+ }
+ for (int i = 0; i < queue->getNumPackets(); i++) {
+ if (matches(queue->getPacket(i)))
+ return true;
+ }
+ }
+ return false;
+}
+
+bool Hcf::hasNumberedData(MacAddress peer, Tid tid) const
+{
+ for (int ac = 0; ac < AC_NUMCATEGORIES; ac++) {
+ auto frames = edca->getEdcaf(static_cast(ac))->getInProgressFrames();
+ for (int i = 0; i < frames->getLength(); i++) {
+ auto data = dynamicPtrCast(frames->getFrames(i)->peekAtFront());
+ if (data && data->getReceiverAddress() == peer && data->getTid() == tid)
+ return true;
+ }
+ }
+ return false;
+}
+
+void Hcf::updateAgreementProcedures()
+{
+ if (!originatorBlockAckAgreementHandler || updatingProcedures)
+ return;
+ updatingProcedures = true;
+ auto procedures = originatorBlockAckAgreementHandler->getProcedures();
+ for (const auto& entry : procedures) {
+ auto peer = entry.first.first;
+ auto tid = entry.first.second;
+ auto state = entry.second.state;
+ using State = IOriginatorBlockAckAgreementHandler::ProcedureState;
+ bool queued = hasQueuedData(peer, tid);
+ // INET setup policy waits for a successor after the first eligible DATA transmission.
+ if (state == State::WAITING_FOR_SUCCESSOR) {
+ if (!queued)
+ continue;
+ originatorBlockAckAgreementHandler->beginSetupIntent(peer, tid);
+ state = State::SETUP_INTENT;
+ }
+ if (state == State::SETUP_INTENT) {
+ if (!queued)
+ originatorBlockAckAgreementHandler->finishProcedure(peer, tid);
+ else if (!hasNumberedData(peer, tid) && !hasObsoleteActionInFlight(peer, tid, true)) {
+ auto service = check_and_cast(originatorDataService);
+ originatorBlockAckAgreementHandler->startSetup(peer, tid, service->getNextSequenceNumber(peer, tid),
+ originatorBlockAckAgreementPolicy, this, service->hasAMsduAggregation());
+ }
+ }
+ else if (state == State::REQUEST_PENDING && !queued) {
+ if (entry.second.requestAttempted)
+ beginTeardown(peer, tid, true, 37);
+ else {
+ originatorBlockAckAgreementHandler->finishProcedure(peer, tid);
+ retireAgreement(peer, tid, true);
+ }
+ }
+ else if (state == State::RECOVERY_NEEDED && queued)
+ beginTeardown(peer, tid, true, 37);
+ }
+ updatingProcedures = false;
+ scheduleAddbaResponseTimer();
+}
+
+void Hcf::updateContention()
+{
+ if (handlingAgreementAction)
+ return;
+ for (int ac = 0; ac < AC_NUMCATEGORIES; ac++) {
+ auto edcaf = edca->getEdcaf(static_cast(ac));
+ if (edcaf->isOwning())
+ continue;
+ if (hasFrameToTransmit(static_cast(ac)))
+ edcaf->requestChannel(this);
+ }
+}
+
+void Hcf::advanceOriginatorWindows()
+{
+ if (!originatorBlockAckAgreementHandler)
+ return;
+ auto service = check_and_cast(originatorDataService);
+ for (const auto& entry : originatorBlockAckAgreementHandler->getAgreements()) {
+ auto agreement = entry.second;
+ if (!agreement->getIsAddbaResponseReceived())
+ continue;
+ auto next = service->getNextSequenceNumber(entry.first.first, entry.first.second);
+ std::vector unresolvedSequences;
+ for (int ac = 0; ac < AC_NUMCATEGORIES; ac++) {
+ auto frames = edca->getEdcaf(static_cast(ac))->getInProgressFrames();
+ for (int i = 0; i < frames->getLength(); i++) {
+ auto data = dynamicPtrCast(frames->getFrames(i)->peekAtFront());
+ if (data && data->getType() == ST_DATA_WITH_QOS &&
+ data->getReceiverAddress() == entry.first.first && data->getTid() == entry.first.second)
+ unresolvedSequences.push_back(data->getSequenceNumber());
+ }
+ }
+ // IEEE Std 802.11-2024, 10.25.6.7.1: resolved positions permit transmit-window progress.
+ agreement->advanceWindow(next, unresolvedSequences);
+ }
+}
+
+void Hcf::completeTransfers()
+{
+ if (!originatorBlockAckAgreementHandler)
+ return;
+ std::vector> completed;
+ for (const auto& entry : originatorBlockAckAgreementHandler->getAgreements()) {
+ auto agreement = entry.second;
+ if (agreement->getIsAddbaResponseReceived() && agreement->getFirstDataTransmitted() &&
+ !hasNumberedData(entry.first.first, entry.first.second) && !hasQueuedData(entry.first.first, entry.first.second))
+ completed.push_back(entry.first);
+ }
+ // IEEE Std 802.11-2024, 10.25.4; INET uses the accepted queue predicate at an exchange boundary.
+ for (const auto& id : completed)
+ beginTeardown(id.first, id.second, true, 37);
+}
+
+void Hcf::discardAgreementData(MacAddress peer, Tid tid, bool all, int64_t highestTransmittedDataIndex)
+{
+ if (frameSequenceHandler->isSequenceRunning()) {
+ auto prepared = frameSequenceHandler->getContext()->getQoSContext()->preparedData.get();
+ if (prepared) {
+ auto header = prepared->frame->peekAtFront();
+ if (header->getReceiverAddress() == peer && header->getTid() == tid)
+ prepared->cancelled = true;
+ }
+ }
+ for (int ac = 0; ac < AC_NUMCATEGORIES; ac++) {
+ auto edcaf = edca->getEdcaf(static_cast(ac));
+ auto frames = edcaf->getInProgressFrames();
+ std::vector discard;
+ for (int i = 0; i < frames->getLength(); i++) {
+ auto packet = frames->getFrames(i);
+ auto data = dynamicPtrCast(packet->peekAtFront());
+ bool transmitted = data && edcaf->getAckHandler()->getQoSDataAckStatus(data) != QosAckHandler::Status::FRAME_NOT_YET_TRANSMITTED;
+ if (data && frameSequenceHandler->isSequenceRunning()) {
+ auto context = frameSequenceHandler->getContext();
+ for (int j = 0; j < context->getNumSteps(); j++) {
+ if (auto step = dynamic_cast(context->getStep(j)))
+ transmitted |= step->getFrameToTransmit() == packet;
+ }
+ }
+ // IEEE Std 802.11-2024, 11.5.3.4: the next MPDU must follow the highest transmitted sequence.
+ // Include unsent fragments even if ACK processing already removed an earlier fragment.
+ // Local allocation indices preserve order across gaps and wire sequence wraparound.
+ if (data && data->getReceiverAddress() == peer && data->getTid() == tid &&
+ (all || transmitted || (highestTransmittedDataIndex >= 0 &&
+ packet->getTag()->getAllocationIndex() <= highestTransmittedDataIndex)))
+ discard.push_back(packet);
+ }
+ for (auto packet : discard) {
+ auto data = packet->peekAtFront();
+ edcaf->getAckHandler()->dropFrame(data);
+ edcaf->getRecoveryProcedure()->dropFrame(data);
+ frames->dropFrame(packet);
+ PacketDropDetails details;
+ details.setReason(OTHER_PACKET_DROP);
+ emit(packetDroppedSignal, packet, &details);
+ }
+ if (all) {
+ auto queue = edcaf->getPendingQueue();
+ discard.clear();
+ for (int i = 0; i < queue->getNumPackets(); i++) {
+ auto packet = queue->getPacket(i);
+ auto data = dynamicPtrCast(packet->peekAtFront());
+ if (data && data->getReceiverAddress() == peer && data->getTid() == tid)
+ discard.push_back(packet);
+ }
+ for (auto packet : discard) {
+ queue->removePacket(packet);
+ take(packet);
+ edca->getLifetimeHandler()->forgetFrame(packet->getId());
+ PacketDropDetails details;
+ details.setReason(OTHER_PACKET_DROP);
+ emit(packetDroppedSignal, packet, &details);
+ delete packet;
+ }
+ }
+ if (!frameSequenceHandler->isSequenceRunning() || frameSequenceHandler->getContext()->getInProgressFrames() != frames)
+ frames->clearDroppedFrames();
+ }
+}
+
+void Hcf::firstDataDiscarded(const Ptr& header)
+{
+ if (!originatorBlockAckAgreementHandler)
+ return;
+ auto agreement = originatorBlockAckAgreementHandler->getAgreement(header->getReceiverAddress(), header->getTid());
+ if (agreement && agreement->getIsAddbaResponseReceived() && !agreement->getFirstDataTransmitted() &&
+ agreement->getStartingSequenceNumber() == header->getSequenceNumber())
+ beginTeardown(header->getReceiverAddress(), header->getTid(), true, 37);
+}
+
+void Hcf::invalidateActions(MacAddress peer, Tid tid, bool originator)
+{
+ std::set invalid;
+ std::set attempted;
+ std::set rtsRecoveryOwned;
+ for (auto it = ownedActions.begin(); it != ownedActions.end();) {
+ if (it->second.peer == peer && it->second.tid == tid && it->second.originator == originator) {
+ invalid.insert(it->first);
+ if (it->second.attempted)
+ attempted.insert(it->first);
+ if (it->second.rtsRecoveryOwned)
+ rtsRecoveryOwned.insert(it->first);
+ obsoleteActions.insert(it->first);
+ it = ownedActions.erase(it);
+ }
+ else
+ ++it;
+ }
+ for (int ac = 0; ac < AC_NUMCATEGORIES; ac++) {
+ auto edcaf = edca->getEdcaf(static_cast(ac));
+ auto queue = edcaf->getPendingQueue();
+ std::vector removed;
+ for (int i = 0; i < queue->getNumPackets(); i++) {
+ if (invalid.count(queue->getPacket(i)->getId()))
+ removed.push_back(queue->getPacket(i));
+ }
+ for (auto packet : removed) {
+ queue->removePacket(packet);
+ take(packet);
+ obsoleteActions.erase(packet->getId());
+ delete packet;
+ }
+ auto frames = edcaf->getInProgressFrames();
+ removed.clear();
+ for (int i = 0; i < frames->getLength(); i++) {
+ if (invalid.count(frames->getFrames(i)->getId()))
+ removed.push_back(frames->getFrames(i));
+ }
+ for (auto packet : removed) {
+ auto header = packet->peekAtFront();
+ edcaf->getAckHandler()->dropFrame(header);
+ if (attempted.count(packet->getId()))
+ edcaf->getMgmtAndNonQoSRecoveryProcedure()->dropFrame(packet, header);
+ if (rtsRecoveryOwned.count(packet->getId()))
+ edcaf->getMgmtAndNonQoSRecoveryProcedure()->dropRtsProtectedFrame(header);
+ frames->dropFrame(packet);
+ if (!frameSequenceHandler->isSequenceRunning() || frameSequenceHandler->getContext()->getInProgressFrames() != frames)
+ obsoleteActions.erase(packet->getId());
+ }
+ if (!frameSequenceHandler->isSequenceRunning() || frameSequenceHandler->getContext()->getInProgressFrames() != frames)
+ frames->clearDroppedFrames();
+ }
+}
+
+void Hcf::retireAgreement(MacAddress peer, Tid tid, bool originator, bool associationReset)
+{
+ if (!originator)
+ clearInterframeHistory();
+ if (originator) {
+ auto agreement = originatorBlockAckAgreementHandler->detachAgreement(peer, tid);
+ invalidateActions(peer, tid, originator);
+ discardAgreementData(peer, tid, associationReset, agreement ? agreement->getHighestTransmittedDataIndex() : -1);
+ if (agreement) {
+ if (agreement->getIsAddbaResponseReceived())
+ emit(blockAckAgreementDeletedSignal, agreement);
+ delete agreement;
+ }
+ }
+ else {
+ auto agreement = recipientBlockAckAgreementHandler->detachAgreement(peer, tid);
+ invalidateActions(peer, tid, originator);
+ if (agreement) {
+ sendUp(recipientDataService->retireAgreement(agreement, associationReset));
+ emit(blockAckAgreementDeletedSignal, agreement);
+ delete agreement;
+ }
+ }
+ scheduleInactivityTimer();
+}
+
+void Hcf::beginTeardown(MacAddress peer, Tid tid, bool originator, int reasonCode)
+{
+ if (!originatorBlockAckAgreementHandler || !recipientBlockAckAgreementHandler)
+ return;
+ if (originator) {
+ const auto& procedures = originatorBlockAckAgreementHandler->getProcedures();
+ auto it = procedures.find({peer, tid});
+ if (it != procedures.end() && it->second.state == IOriginatorBlockAckAgreementHandler::ProcedureState::TEARDOWN_PENDING)
+ return;
+ originatorBlockAckAgreementHandler->beginTeardown(peer, tid);
+ }
+ else {
+ if (recipientBlockAckAgreementHandler->isTeardownPending(peer, tid))
+ return;
+ recipientBlockAckAgreementHandler->beginTeardown(peer, tid);
+ }
+ retireAgreement(peer, tid, originator);
+ auto delba = makeShared();
+ delba->setReceiverAddress(peer);
+ delba->setTid(tid);
+ delba->setInitiator(originator);
+ delba->setReasonCode(reasonCode);
+ processMgmtFrame(new Packet("Delba", delba), delba);
+ scheduleAddbaResponseTimer();
+}
+
+void Hcf::actionTerminated(Packet *packet, bool acknowledged)
+{
+ auto it = ownedActions.find(packet->getId());
+ if (it == ownedActions.end())
+ return;
+ auto action = it->second;
+ ownedActions.erase(it);
+ if (action.rtsRecoveryOwned)
+ edca->getEdcaf(AC_VO)->getMgmtAndNonQoSRecoveryProcedure()->dropRtsProtectedFrame(packet->peekAtFront());
+ if (action.kind == OwnedAction::REQUEST) {
+ auto agreement = originatorBlockAckAgreementHandler->getAgreement(action.peer, action.tid);
+ if (!agreement || agreement->getIsAddbaResponseReceived())
+ return;
+ if (acknowledged)
+ originatorBlockAckAgreementHandler->acknowledgeRequest(action.peer, action.tid,
+ originatorBlockAckAgreementPolicy->computeAddbaFailureTimeout());
+ else if (action.attempted)
+ // INET recovery policy confirms peer teardown before another setup.
+ beginTeardown(action.peer, action.tid, true, 37);
+ else {
+ originatorBlockAckAgreementHandler->finishProcedure(action.peer, action.tid);
+ retireAgreement(action.peer, action.tid, true);
+ }
+ }
+ else if (action.kind == OwnedAction::DELBA) {
+ if (action.originator) {
+ if (acknowledged)
+ originatorBlockAckAgreementHandler->finishProcedure(action.peer, action.tid);
+ else {
+ // INET recovery policy discards this batch after failed DELBA delivery.
+ originatorBlockAckAgreementHandler->failTeardown(action.peer, action.tid);
+ discardAgreementData(action.peer, action.tid, true);
+ }
+ }
+ else
+ recipientBlockAckAgreementHandler->finishTeardown(action.peer, action.tid);
+ }
+ scheduleAddbaResponseTimer();
+ if (action.kind == OwnedAction::DELBA || (action.kind == OwnedAction::REQUEST && !acknowledged)) {
+ updateAgreementProcedures();
+ updateContention();
+ }
+}
+
+void Hcf::resetPeer(MacAddress peer)
+{
+ clearInterframeHistory();
+ Enter_Method("resetPeer");
+ if (!originatorBlockAckAgreementHandler)
+ return;
+ bool previous = handlingAgreementAction;
+ handlingAgreementAction = true;
+ auto originators = originatorBlockAckAgreementHandler->detachPeer(peer);
+ auto recipients = recipientBlockAckAgreementHandler->detachPeer(peer);
+ for (int tid = 0; tid < 16; tid++) {
+ invalidateActions(peer, tid, true);
+ invalidateActions(peer, tid, false);
+ discardAgreementData(peer, tid, true);
+ }
+ for (auto agreement : recipients)
+ sendUp(recipientDataService->retireAgreement(agreement, true));
+ recipientDataService->clearPeer(peer);
+ for (auto agreement : originators) {
+ if (agreement->getIsAddbaResponseReceived())
+ emit(blockAckAgreementDeletedSignal, agreement);
+ delete agreement;
+ }
+ for (auto agreement : recipients) {
+ emit(blockAckAgreementDeletedSignal, agreement);
+ delete agreement;
+ }
+ handlingAgreementAction = previous;
+ scheduleInactivityTimer();
+ scheduleAddbaResponseTimer();
+}
+
+void Hcf::receiveSignal(cComponent *source, simsignal_t signalID, bool value, cObject *details)
+{
+ if (signalID != Ieee80211Mib::rateStateChangedSignal)
+ return;
+ Enter_Method("rateStateChanged");
+ // This cleanup prepares Compressed support. Basic agreements do not depend on HT admission state.
+ if (blockAckVariant == BlockAckVariant::BASIC || !originatorBlockAckAgreementHandler)
+ return;
+ std::vector> retired;
+ for (const auto& entry : originatorBlockAckAgreementHandler->getAgreements()) {
+ if (!isProfileAllowed(entry.first.first, true))
+ retired.push_back({entry.first.first, entry.first.second, true});
+ }
+ for (const auto& entry : recipientBlockAckAgreementHandler->getAgreements()) {
+ if (!isProfileAllowed(entry.first.first, false))
+ retired.push_back({entry.first.first, entry.first.second, false});
+ }
+ for (const auto& entry : retired)
+ beginTeardown(std::get<0>(entry), std::get<1>(entry), std::get<2>(entry), 37);
+ updateContention();
}
void Hcf::processLowerFrame(Packet *packet, const Ptr& header)
{
Enter_Method("processLowerFrame(%s)", packet->getName());
take(packet);
+ if (auto bar = dynamicPtrCast(header)) {
+ if (!validateBlockAckRequest(packet, bar)) {
+ interframeHistory = {};
+ if (auto time = packet->findTag())
+ pendingReceptions.erase(time->getTransmissionId());
+ delete packet;
+ return;
+ }
+ }
+ bool interframe = interframeRadio && receiveInterframe(packet, header);
EV_INFO << "Processing lower frame: " << packet->getName() << endl;
auto edcaf = edca->getChannelOwner();
+ auto action = dynamicPtrCast(header);
+ bool blockAckAction = dynamicPtrCast(header) ||
+ dynamicPtrCast(header) || dynamicPtrCast(header);
+ if (action && blockAckAction && isForUs(header) && edcaf && frameSequenceHandler->isSequenceRunning() &&
+ dynamic_cast(frameSequenceHandler->getContext()->getLastStep())) {
+ handlingAgreementAction = true;
+ emit(packetReceivedFromPeerSignal, packet);
+ std::unique_ptr responseSource(packet->dup());
+ auto accepted = recipientDataService->managementFrameReceived(packet, action);
+ for (auto frame : accepted) {
+ take(frame);
+ dispatchAgreementAction(frame->peekAtFront());
+ }
+ frameSequenceHandler->interruptFrameSequence();
+ recipientAckProcedure->processReceivedFrame(responseSource.get(), action, check_and_cast(recipientAckPolicy), this);
+ for (auto frame : accepted)
+ delete frame;
+ handlingAgreementAction = false;
+ updateAgreementProcedures();
+ updateContention();
+ return;
+ }
if (edcaf && frameSequenceHandler->isSequenceRunning()) {
// TODO always call processResponse?
if ((!isForUs(header) && !startRxTimer->isScheduled()) || isForUs(header)) {
@@ -323,7 +1038,7 @@ void Hcf::processLowerFrame(Packet *packet, const Ptr&
else if (hcca->isOwning())
throw cRuntimeError("Hcca is unimplemented!");
else if (isForUs(header))
- recipientProcessReceivedFrame(packet, header);
+ recipientProcessReceivedFrame(packet, header, interframe);
else {
EV_INFO << "This frame is not for us" << std::endl;
PacketDropDetails details;
@@ -333,6 +1048,33 @@ void Hcf::processLowerFrame(Packet *packet, const Ptr&
}
}
+bool Hcf::validateBlockAckRequest(Packet *packet, const Ptr& bar) const
+{
+ // IEEE Std 802.11-2024, 9.3.1.7.2: the supported Compressed BAR has a zero Fragment Number.
+ auto basic = dynamicPtrCast(bar);
+ auto compressed = dynamicPtrCast(bar);
+ auto tid = basic ? basic->getTidInfo() : compressed ? compressed->getTidInfo() : -1;
+ bool valid = basic || compressed;
+ if (basic)
+ valid &= basic->getFragmentNumber() == 0 && !basic->getMultiTid() && !basic->getCompressedBitmap() &&
+ basic->getStartingSequenceNumber().isSpecified() && (basic->getReserved() & 3) == 0;
+ if (compressed)
+ valid &= compressed->getFragmentNumber() == 0 && !compressed->getMultiTid() && compressed->getCompressedBitmap() &&
+ compressed->getStartingSequenceNumber().isSpecified();
+ if (!valid || tid < 0 || tid > 15 || bar->getChunkLength() != LENGTH_BLOCKACKREQ - B(4) ||
+ packet->getDataLength() != LENGTH_BLOCKACKREQ || bar->getTransmitterAddress().isUnspecified() ||
+ bar->getTransmitterAddress().isMulticast() || bar->getReceiverAddress().isUnspecified() || bar->getReceiverAddress().isMulticast())
+ return false;
+ auto agreement = recipientBlockAckAgreementHandler ?
+ recipientBlockAckAgreementHandler->getAgreement(tid, bar->getTransmitterAddress()) : nullptr;
+ // ADDBA has no variant field. INET stops before any protocol update for an active conflicting profile.
+ if (bar->getReceiverAddress() == mac->getAddress() && agreement && bool(compressed) != (blockAckVariant == BlockAckVariant::COMPRESSED))
+ throw cRuntimeError("Block Ack profile conflict at %s: peer=%s TID=%d local=%s received=%s; ADDBA does not negotiate the profile. Configure matching blockAckVariant values and start a new run",
+ getFullPath().c_str(), bar->getTransmitterAddress().str().c_str(), tid,
+ blockAckVariant == BlockAckVariant::COMPRESSED ? "compressed" : "basic", compressed ? "compressed" : "basic");
+ return true;
+}
+
void Hcf::channelGranted(IChannelAccess *channelAccess)
{
Enter_Method("channelGranted");
@@ -365,7 +1107,8 @@ void Hcf::channelGranted(IChannelAccess *channelAccess)
FrameSequenceContext *Hcf::buildContext(AccessCategory ac)
{
auto edcaf = edca->getEdcaf(ac);
- auto qosContext = new QoSContext(originatorAckPolicy, originatorBlockAckProcedure, originatorBlockAckAgreementHandler, edcaf->getTxopProcedure());
+ auto qosContext = new QoSContext(originatorAckPolicy, originatorBlockAckProcedure, originatorBlockAckAgreementHandler, edcaf->getTxopProcedure(), blockAckVariant);
+ qosContext->prepareData = [this](FrameSequenceContext *context) { return prepareDataTransmission(context); };
return new FrameSequenceContext(mac->getAddress(), modeSet, edcaf->getInProgressFrames(), rtsProcedure, rtsPolicy, nullptr, qosContext);
}
@@ -385,7 +1128,35 @@ void Hcf::handleInternalCollision(std::vector internallyCollidedEdcafs)
for (auto edcaf : internallyCollidedEdcafs) {
AccessCategory ac = edcaf->getAccessCategory();
auto dataRecoveryProcedure = edcaf->getRecoveryProcedure();
- Packet *internallyCollidedFrame = edcaf->getInProgressFrames()->getFrameToTransmit();
+ if (originatorAckPolicy->isBlockAckReqNeeded(edcaf->getInProgressFrames(), edcaf->getTxopProcedure(), originatorBlockAckAgreementHandler)) {
+ dataRecoveryProcedure->controlFrameTransmissionFailed();
+ edcaf->requestChannel(this);
+ continue;
+ }
+ auto frames = edcaf->getInProgressFrames();
+ Packet *internallyCollidedFrame = nullptr;
+ bool scopedQueuedData = false;
+ if (blockAckVariant == BlockAckVariant::COMPRESSED && originatorBlockAckAgreementHandler) {
+ auto candidate = frames->peekFrameToTransmit();
+ auto data = candidate ? dynamicPtrCast(candidate->peekAtFront()) : nullptr;
+ auto agreement = data ? originatorBlockAckAgreementHandler->getAgreement(data->getReceiverAddress(), data->getTid()) : nullptr;
+ if (data && data->getType() == ST_DATA_WITH_QOS && !data->getReceiverAddress().isMulticast() &&
+ agreement && agreement->getIsAddbaResponseReceived() && !frames->containsFrame(candidate)) {
+ scopedQueuedData = true;
+ // Collision recovery needs a numbered frame, but it must not form an unadmitted aggregate.
+ auto service = check_and_cast(originatorDataService);
+ auto prepared = service->prepareFrame(edcaf->getPendingQueue(), false);
+ if (prepared) {
+ auto selected = prepared->packet->peekAtFront();
+ if (selected->getReceiverAddress() == data->getReceiverAddress() && selected->getTid() == data->getTid())
+ internallyCollidedFrame = service->commitPreparedFrame(*prepared);
+ }
+ if (internallyCollidedFrame)
+ frames->adoptPreparedFrame(internallyCollidedFrame);
+ }
+ }
+ if (!internallyCollidedFrame && !scopedQueuedData)
+ internallyCollidedFrame = frames->getFrameToTransmit();
if (internallyCollidedFrame == nullptr)
continue;
auto internallyCollidedHeader = internallyCollidedFrame->peekAtFront();
@@ -420,6 +1191,8 @@ void Hcf::handleInternalCollision(std::vector