Skip to content

ieee80211: establish Compressed BlockAck state foundations - #1272

Open
mgonzalezlopezudc wants to merge 2 commits into
inet-framework:masterfrom
mgonzalezlopezudc:feat/ieee80211-compressed-blockack-state
Open

mgonzalezlopezudc wants to merge 2 commits into
inet-framework:masterfrom
mgonzalezlopezudc:feat/ieee80211-compressed-blockack-state

Conversation

@mgonzalezlopezudc

@mgonzalezlopezudc mgonzalezlopezudc commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Compressed BlockAck needs a supported wire format and validated agreement state before the MAC can use it in a radio exchange. This change adds those foundations in one commit.

The change:

  • Implements Type 2 serialization with an eight-byte bitmap and zero Fragment Number.
  • Requires immediate HT operation, accepted peer capabilities, and an agreement size from 1 to 64.
  • Preserves receive status after data delivery, with bounded receive windows and sequence wrap.
  • Uses one HCF inactivity event for the earliest absolute deadline across both agreement directions. A zero timeout disables expiry.
  • Removes only the selected agreement direction on expiry or DELBA. Recipient termination also clears the selected receive buffer.
  • Disables automatic ADDBA initiation from ordinary data until the radio integration is complete.

The complete Compressed BlockAck radio exchange and aggregate data session remain incomplete. Radio integration still requires rate publication and exchange admission. The tests establish wire format and state behavior; they do not establish a Compressed BlockAck radio exchange.

The default isBlockAckSupported = false retains normal acknowledgments. Enabled configurations require explicit agreement setup and no longer start the former automatic session. The user guide, developer guide, migration guide, and WHATSNEW describe these limits.

The architectural surface covers the existing frame representation, serializer, agreement policies and handlers, receive record, recipient data service, and HCF. Both policies gain mibModule = default("^.^.^.mib").

External implementations of these contracts require the new pure virtual methods:

  • IOriginatorBlockAckAgreementHandler: getEarliestExpirationTime() const.
  • IRecipientBlockAckAgreementHandler: getEarliestExpirationTime() const and processReceivedBlockAckReq().
  • IBlockAckAgreementHandlerCallback: rescheduleInactivityTimer(), blockAckAgreementTerminated(cObject *), and recipientAgreementTerminated(MacAddress, Tid).
  • IRecipientQosMacDataService: clearBlockAckReceiveBuffer(MacAddress, Tid).

The existing scheduleInactivityTimer(simtime_t) method remains deprecated for one release. The change adds no production class, NED type, frame type, or feature descriptor. It changes no sealed source path or exception ledger.

Validation used a separate checkout on the same upstream base. All 32 source and test files match the saved validation manifest. The four release and guide files contain later documentation edits.

Both compilation checks passed with exit status 0:

make -j8 MODE=debug
make -j8 MODE=release

The focused tests loaded the fresh debug library. These commands passed with exit status 0: three unit cases, one module case, and three legacy protocol cases.

inet_run_unit_tests -p inet -m debug -f 'Ieee80211(CompressedBlockAck(Wire|State)|BlockAckActionWire)_1'
inet_run_module_tests -p inet -m debug -f 'Ieee80211CompressedBlockAck_1'
inet_run_protocol_tests -p inet -m debug -w '^tests/protocol/wifi$' -f '/Legacy_(DataAck|RtsCts|Fragmentation)\.test$'

This command passed from tests/fingerprint with exit status 0:

./fingerprinttest -d -m '/showcases/wireless/aggregation/' -f 'tplx' -f '~tNl' -f '~tND'

It selected NoAggregation, Aggregation, and VoicePriorityAggregation, each with run 0 and the configured seed. All three cases matched their recorded expectations. No baseline changed.

The scoped architecture check and checks of the four changed interfaces passed. Global checks reported 15 existing interface violations and 27 existing naming findings. The changed declarations introduced no naming finding. The existing frame-step bodies appear in AV-CONTRACT-02; existing package problems appear in NV-02 and NV-03.

Before submission, these checks passed against the current upstream base:

git diff --check upstream/master...HEAD
doc/project/enforcement/check-commits.sh upstream/master..HEAD
doc/project/enforcement/check-classification.sh upstream/master..HEAD
doc/project/enforcement/check-source-seals.sh --base upstream/master

The existing automatic-session protocol tests await the dependent integration step. The saved self-audit does not constitute an independent review.


Devin Review

Compressed BlockAck acknowledges several data frames with one bitmap.
Its independent state foundation must validate agreements before radio
integration can use them. Implement the approved steps 1 through 3.
The user excluded rate publication, exchange admission, and radio
integration from this change.

The supported profile requires immediate HT operation and accepted peer
capabilities. Receive status follows the negotiated window and survives
packet delivery. One shared event follows the earliest absolute deadline.
Expiry and DELBA remove only the selected agreement direction.
Automatic agreement initiation remains disabled until radio integration.

Both build modes pass. Three unit cases, one module case, three legacy
protocol cases, and three aggregation fingerprint cases pass. The report
records exact commands and existing global naming and interface defects.
The worktree transfer preserves the validated source and test bytes.
No recorded test expectation changes.

Change: src.ieee80211 | behavior.add | test whatsnew migration | ieee80211-compressed-blockack

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Devin Review found 5 potential issues.

1 flag not posted on this PR by your GitHub settings — view it in Devin Review. (Configure)

Devin Review

Comment on lines +25 to +26
if (dataHeader->getAckPolicy() == BLOCK_ACK)
agreement->blockAckPolicyFrameReceived(dataHeader);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Discarded data gets acknowledged

When distant data arrives, blockAckPolicyFrameReceived records receipt before ReceiveBuffer::insertFrame rejects it. The recipient acknowledges data it discarded.

Learn more

The receive record tracks acknowledgment status, while the reorder buffer retains packets for delivery. Their windows move independently: a distant sequence advances the record, but insertFrame rejects the packet outside its unchanged nextExpectedSequenceNumber window. Recording acknowledgment before that rejection tells the sender its discarded data arrived successfully.

Example: With both windows starting at 0 and size 64, sequence 100 advances the record to 37. The reorder buffer still starts at 0, rejects sequence 100, and deletes it; getAckState(100, 0) returns true.

Recommended fix: Keep record and reorder-buffer window movement consistent when accepting a distant sequence, or record acknowledgment only for packets that the recipient actually retains or delivers. Add a test for an arrival beyond the existing reorder buffer window.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

else {
throw cRuntimeError("Multi-Tid BlockAckReq is currently an unimplemented feature");
}
agreement->getBlockAckRecord()->blockAckReqReceived(startingSequenceNumber);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 First request desynchronizes receive windows

When a BAR arrives before data, blockAckReqReceived advances the record without creating a reorder buffer. Later data uses the original agreement start and gets discarded outside the stale reorder window.

Learn more

A recipient has an acknowledgment window in BlockAckRecord and a separate packet window in ReceiveBuffer. createReceiveBufferIfNecessary initializes the latter from the agreement's original starting sequence. When a BAR precedes all data, no buffer exists, so advancing only the record leaves future packet reception behind its current position.

Example: An agreement begins at sequence 0 with size 64. A BAR requests sequence 100 before data arrives, advancing the record to 100; the first sequence-100 packet creates a reorder buffer starting at 0 and is rejected.

Recommended fix: Synchronize the reorder buffer's starting/next-expected sequence with BAR window movement even when no buffer exists, or initialize newly created buffers from the current receive-record window. Test BAR-before-data with a sequence jump beyond the negotiated window.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +186 to +190
if (blockAckAgreementPolicy->isAddbaReqAccepted(addbaRequest) &&
getAgreement(addbaRequest->getTid(), addbaRequest->getTransmitterAddress()) == nullptr)
addAgreement(addbaRequest, response);
else
response->setStatusCode(37); // REFUSED

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 Failed ADDBA response blocks negotiation

If an ADDBA response never transmits, getAgreement makes every retried request return REFUSED. Pending agreements never expire, so that peer and TID cannot negotiate again.

Learn more

An accepted ADDBA request installs an inactive recipient agreement immediately. The agreement becomes active only after processTransmittedAddbaResp, but getEarliestExpirationTime and blockAckAgreementExpired ignore inactive agreements. The existence check then refuses all subsequent requests, including a retry following a failed response transmission.

Example: Peer A requests TID 5; the recipient accepts but exhausts transmission retries on its ADDBA response. Peer A retries the request and receives REFUSED forever, while the inactive agreement remains installed.

Recommended fix: Define a failure/cancellation path for pending responses and allow valid retransmissions or replacement of pending agreements. Ensure a pending agreement cannot remain indefinitely after the response has been abandoned.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +42 to +45
return fragmentNumber == 0 &&
(sequenceNumber == startingSequenceNumber || startingSequenceNumber < sequenceNumber) &&
sequenceNumber < startingSequenceNumber + windowSize &&
containsKey(acknowledgmentState, SequenceControlField(sequenceNumber.get(), fragmentNumber));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Delivered packets lose old-window acknowledgment

When a Basic BAR starts before startingSequenceNumber, getAckState returns false for previously delivered packets. The resulting bitmap asks the sender to retransmit them.

Learn more

The receive record retains acknowledgment status only for its current sequence window. buildBlockAck queries getAckState for all 64 sequence positions from the request's start. The previous implementation returned true for positions older than its retained status; the new window guard instead returns false and encodes missing data for already received packets.

Example: The record advances from sequence 10 to 20 after delivering sequence 10. A Basic BAR starting at 10 asks for sequence 10; the bitmap now marks it missing instead of acknowledged.

Recommended fix: Return acknowledged status for sequences known to precede the current receive window, while retaining the false result for missing positions inside the window. Cover an older BAR across sequence wrap in a focused test.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

Comment on lines +27 to +30
if (header->getTransmitterAddress() != originatorAddress || header->getTid() != tid ||
header->getType() != ST_DATA_WITH_QOS || header->getAckPolicy() != BLOCK_ACK ||
header->getFragmentNumber() != 0 || header->getMoreFragments() || header->isIncorrect())
return;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Fragmented data loses Basic BlockAck status

For fragmented BlockAck data, blockAckPolicyFrameReceived ignores every later fragment and every nonfinal first fragment. Basic BlockAck bitmaps then mark received fragments missing.

Learn more

The previous receive record stored acknowledgment by sequence and fragment number. The existing buildBlockAck still constructs Basic BlockAck bitmaps with 16 fragment bits per sequence. Rejecting all fragments prevents those bitmaps from reflecting received fragmented MPDUs, even though the new Compressed BlockAck profile is unfragmented.

Example: A Basic BlockAck agreement receives fragment 0 with moreFragments=true and fragment 1 with moreFragments=false. Neither fragment enters the record, and both bitmap bits remain zero.

Recommended fix: Preserve fragment-specific status for Basic BlockAck agreements, or explicitly restrict the receive-record API and callers to Compressed BlockAck while keeping the legacy Basic record behavior. Test fragmented Basic BlockAck reception.

Devin Review


Was this helpful? React with 👍 or 👎 to provide feedback.

The reorder buffer can discard data after the receipt record sets its
BlockAck bit. The mixed-policy test receives Normal Ack sequence 100
and Block Ack sequence 10 from windows at 0. The old frame gains
receipt status despite discard. Move the reorder window before admission.
Update receipt state for each valid related Data frame.
Keep delivery and inactivity refresh separate from receipt status.

An ADDBA response can disappear before its first transmission callback.
The inactive agreement then refuses every repeated request.
Reuse a matching pending transaction or replace it for a new dialog token.
Reject stale response tokens before activation of the replacement.

Timeout expiry removes local state before its DELBA enters the queue.
A new agreement can start before that DELBA transmits.
Its late callback must preserve the replacement agreement and receive buffer.
Cancel obsolete unreferenced timeout frames through the queue, ACK,
and recovery owners. Preserve active direct and RTS frame references.

The debug build passes. Three unit tests and one module test pass.
Three legacy protocol tests and three aggregation fingerprints pass.
No recorded expectation changes. Compressed BAR integration remains pending.

Change: src.ieee80211 | behavior.change.fix | test whatsnew | ieee80211-compressed-blockack

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant