Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 63 additions & 0 deletions WHATSNEW
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,69 @@ Notable backward incompatible changes are the following:
These changes are backward incompatible for C++ code that directly references
the old combined ICMP error indication or tag types.

7. Packet queue extraction and lifecycle callbacks

IPacketQueue now supports queue-lifecycle callbacks and predicate-based extraction.
Direct C++ implementations of IPacketQueue must implement findPacket(), the
predicate overload of dequeuePacket(), addPacketCallback(), and
removePacketCallback(), and must notify callbacks exactly once whenever a
packet is dequeued, explicitly removed, or destructively dropped. Providers
connected to CompoundPacketQueueBase must also implement IPacketExtractor so
that a compound queue preserves the provider's scheduling policy when it
extracts a matching packet. Direct C++
implementations of IPacketExtractor must replace selected-pointer extraction
with findPacket(predicate) and dequeuePacket(predicate); predicates may be
evaluated repeatedly and must be stable and side-effect free throughout one
logical selection. WrrScheduler, LabelScheduler, and PriorityScheduler accept
ordinary IPassivePacketSource inputs; they require IPacketCollection only when
collection/aggregate access is used and IPacketExtractor only when predicate
extraction is used, reporting the unsupported operation lazily.
PriorityScheduler aggregate queries no longer return -1 when an input lacks
IPacketCollection; getNumPackets() and getTotalLength() now report that
unsupported operation with cRuntimeError. Update callers that treated -1 as
an unknown aggregate size, or connect collection-capable providers.
A-MSDU policies now receive the provider-selected anchor and frame-eligibility
predicate. Additional policy-selected members are removed through exact
predicate dequeues, preserving scheduler/flow accounting without requiring
collection enumeration order to match scheduling order.
BasicMsduAggregationPolicy conservatively considers only collection members
after the anchor and never bypasses an earlier blocked same-flow member.
IPacketBuffer::ICallback also provides an optional handlePacketDropped()
notification. Shared buffers invoke it only after all victims selected by one
overload operation have been detached from their owning queues. Compound
queues propagate destructive drops through arbitrary non-queue wrappers and
nested compound queues exactly once. PacketBuffer rejects packets owned by a
cPacketQueue whose owner cannot participate in the buffer callback contract.

8. Block Ack DELBA agreement ownership

IOriginatorBlockAckAgreementHandler::processReceivedDelba(),
IOriginatorBlockAckAgreementHandler::processTransmittedDelba(), and
IRecipientBlockAckAgreementHandler::processReceivedDelba() now return the
terminated agreement as a unique_ptr. Originator ADDBA response processing
now returns a typed outcome containing the established agreement and, when
local policy vetoes a successful response, its immediately terminated local
agreement and a best-effort initiator DELBA for Hcf to enqueue after emitting
the Added and Deleted signals. Such DELBAs carry the ADDBA transaction identity
and ordinary data continues with Normal Ack. They remain eligible through the
final fragment; aborting one fragment cancels its siblings. After retry backoff,
a replacement ADDBA setup invalidates an older queued DELBA, so an unreported
disposal cannot suppress setup indefinitely and a delayed frame cannot
terminate a newer peer/TID agreement. The replacement setup reports that
obsolete identity so Hcf also removes all of its queued or in-progress packets.
Originator DELBA transmission handlers now receive the full Packet so this
identity is retained. A transaction-tagged initiator DELBA remains eligible
across MAC retries and is retired only when its final fragment is acknowledged
or the transaction is terminally aborted. Custom handler implementations and
callers must adopt processAcknowledgedDelba() and the boolean
processAbortedDelba() outcome; processTransmittedDelba() still returns the
agreement removed by an untagged DELBA, or null when none was removed.

OriginatorBlockAckAgreementPolicy now exposes the new `addbaResponseTimeout`
and `addbaRetryBackoff` NED parameters. C++ implementations of
IOriginatorBlockAckAgreementPolicy must replace computeAddbaFailureTimeout()
with getAddbaResponseTimeout() and implement computeAddbaRetryBackoff().

Notable backward compatible changes are the following:

1. IPv6 network configurator
Expand Down
9 changes: 5 additions & 4 deletions doc/src/users-guide/ch-diffserv.rst
Original file line number Diff line number Diff line change
Expand Up @@ -282,9 +282,11 @@ interface is ready to transmit one. They have several input gates and
one output gate.

Modules that are connected to the inputs of a scheduler must implement
the :cpp:`IPacketQueue` C++ interface. Schedulers also implement the
:cpp:`IPacketQueue` interface, so they can be cascaded to other
schedulers and used as the output module of :ned:`IPacketQueue`'s.
the :cpp:`IPassivePacketSource` C++ interface. Collection and predicate
extraction operations additionally require the corresponding input provider
to implement :cpp:`IPacketCollection` and :cpp:`IPacketExtractor`, respectively.
Schedulers can be cascaded and used as the output module of compound packet
queues when those additional interfaces are available.

There are several possible scheduling disciplines (first come/first
served, priority, weighted fair, weighted round-robin, deadline-based,
Expand Down Expand Up @@ -589,4 +591,3 @@ implement three different drop priorities within the class. BE packets are
stored in a drop tail queue. Packets from AFxy and BE queues are
scheduled by a WRR scheduler, which ensures that the remaining bandwidth
is allocated among the classes according to the specified weights.

23 changes: 23 additions & 0 deletions examples/wireless/qos/omnetpp.ini
Original file line number Diff line number Diff line change
Expand Up @@ -83,5 +83,28 @@ sim-time-limit = 10s
extends = MacQos
**.isBlockAckSupported = true

[Config MacQosWithTransactionalBlockAck]
description = "Exercises successful and timed-out ADDBA transactions"
extends = MacQosWithoutAggregation
sim-time-limit = 3s
Comment on lines +86 to +89

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔴 Existing Block Ack example loses its radio medium setting and can abort with an error

The new example configuration is inserted (at examples/wireless/qos/omnetpp.ini:86) between the previous last configuration and the trailing radio-medium setting lines, so the setting that used to belong to the Block Ack example silently moves to the new example.
Impact: The pre-existing Block Ack example now runs with the strict default and can stop with a runtime error when two transmissions start at the same moment; its recorded results change too.

How the ini section boundary shifts the setting

In an omnetpp.ini file, keys belong to the section they textually follow. Before this PR, the lines

# radio medium
*.radioMedium.sameTransmissionStartTimeCheck = "ignore"

were the last lines of [Config MacQosWithBlockAck]. The new [Config MacQosWithTransactionalBlockAck] block is inserted above them, so those two lines now belong to the new config and MacQosWithBlockAck falls back to the NED default error (src/inet/physicallayer/wireless/common/medium/RadioMedium.ned:44), which raises a runtime error via src/inet/physicallayer/wireless/common/medium/RadioMedium.cc:484-497. Note also that MacQosWithTransactionalBlockAck extends MacQosWithoutAggregation, so the new config would not have inherited the setting either; this reassignment is accidental. The existing fingerprint row for MacQosWithBlockAck in tests/fingerprint/examples.csv:661 is left unchanged.

Prompt for agents
The two trailing lines of examples/wireless/qos/omnetpp.ini ('# radio medium' and '*.radioMedium.sameTransmissionStartTimeCheck = "ignore"') were part of [Config MacQosWithBlockAck] because ini keys belong to the preceding section. The newly added [Config MacQosWithTransactionalBlockAck] section was inserted before them, so MacQosWithBlockAck lost the setting and now uses the RadioMedium default 'error', which can abort the simulation. Restore the setting to MacQosWithBlockAck (e.g. append the new config after those lines, or explicitly duplicate/hoist the radioMedium assignment where it is actually needed).
Open in Devin Review

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


# Use one voice flow so each wireless hop has a single peer/TID data flow.
*.cliHost.numApps = 1
*.cliHost.app[0].destPort = 5000
*.cliHost.app[0].packetName = "TransactionalBlockAck"
*.cliHost.app[0].startTime = 1s
*.cliHost.app[0].stopTime = 2s
*.cliHost.app[0].sendInterval = 10ms
*.srvHost.numApps = 1
*.srvHost.app[0].localPort = 5000

# The client-to-AP transaction succeeds. The AP-to-server transaction times
# out, because the server deliberately does not support Block Ack.
*.cliHost.wlan[0].mac.hcf.isBlockAckSupported = true
*.ap.wlan[0].mac.hcf.isBlockAckSupported = true
*.srvHost.wlan[0].mac.hcf.isBlockAckSupported = false
**.mac.hcf.originatorAckPolicy.blockAckReqThreshold = 2
**.mac.hcf.originatorBlockAckAgreementPolicy.addbaResponseTimeout = 250ms

# radio medium
*.radioMedium.sameTransmissionStartTimeCheck = "ignore"
Original file line number Diff line number Diff line change
Expand Up @@ -56,40 +56,61 @@ bool BasicMsduAggregationPolicy::isEligible(Packet *packet, const Ptr<const Ieee
return true;
}

std::vector<Packet *> *BasicMsduAggregationPolicy::computeAggregateFrames(queueing::IPacketQueue *queue)
std::vector<Packet *> *BasicMsduAggregationPolicy::computeAggregateFrames(queueing::IPacketQueue *queue, Packet *candidate, const std::function<bool(const Packet *)>& isFrameEligible)
{
Enter_Method("computeAggregateFrames");
ASSERT(!queue->isEmpty());
ASSERT(candidate != nullptr);
b aMsduLength = b(0);
Ptr<const Ieee80211DataHeader> firstHeader = nullptr;
auto frames = new std::vector<Packet *>();
int candidateIndex = -1;
for (int i = 0; i < queue->getNumPackets(); i++) {
auto dataPacket = queue->getPacket(i);
if (queue->getPacket(i) == candidate) { candidateIndex = i; break; }
}
if (candidateIndex == -1)
return nullptr;
const auto& firstHeader = dynamicPtrCast<const Ieee80211DataHeader>(candidate->peekAtFront<Ieee80211DataOrMgmtHeader>());
if (firstHeader == nullptr || !isFrameEligible(candidate))
return nullptr;
auto frames = new std::vector<Packet *>();
auto hasSameFlow = [&](const Ptr<const Ieee80211DataHeader>& dataHeader) {
return dataHeader != nullptr && dataHeader->getTid() == firstHeader->getTid() &&
dataHeader->getReceiverAddress() == firstHeader->getReceiverAddress() &&
dataHeader->getTransmitterAddress() == firstHeader->getTransmitterAddress();
};
auto appendIfEligible = [&](Packet *dataPacket) {
const auto& dataHeader = dynamicPtrCast<const Ieee80211DataHeader>(dataPacket->peekAtFront<Ieee80211DataOrMgmtHeader>());
if (dataHeader == nullptr)
break;
if (firstHeader == nullptr)
firstHeader = dataHeader;
if (!hasSameFlow(dataHeader))
return true;
// IEEE Std 802.11-2024, 5.1.3: preserve the ordering of MSDUs with the
// same traffic identifier. Enumeration order is the conservative
// intra-flow order for this built-in policy. Never overtake a
// same-flow packet which is held by transaction eligibility or cannot
// fit in the current A-MSDU.
if (!isFrameEligible(dataPacket))
return false;
const auto& dataTrailer = dataPacket->peekAtBack<Ieee80211MacTrailer>(B(4));
if (!isEligible(dataPacket, staticPtrCast<const Ieee80211DataHeader>(dataHeader), dataTrailer, firstHeader, aMsduLength)) {
EV_TRACE << "Queued " << *dataPacket << " is not eligible for A-MSDU aggregation.\n";
break;
}
EV_TRACE << "Queued " << *dataPacket << " is eligible for A-MSDU aggregation.\n";
if (!isEligible(dataPacket, dataHeader, dataTrailer, firstHeader, aMsduLength))
return false;
frames->push_back(dataPacket);
aMsduLength += dataPacket->getDataLength() - dataHeader->getChunkLength() - dataTrailer->getChunkLength() + b(LENGTH_A_MSDU_SUBFRAME_HEADER); // sum of MSDU lengths + subframe header
aMsduLength += dataPacket->getDataLength() - dataHeader->getChunkLength() - dataTrailer->getChunkLength() + b(LENGTH_A_MSDU_SUBFRAME_HEADER);
return true;
};
if (!appendIfEligible(candidate)) {
delete frames;
return nullptr;
}
// Do not wrap around: providers may schedule in an order different from
// their IPacketCollection enumeration (for example reverse priority).
for (int i = candidateIndex + 1; i < queue->getNumPackets(); i++)
if (!appendIfEligible(queue->getPacket(i)))
break;
if (frames->size() <= 1 || !isAggregationPossible(frames->size(), aMsduLength.get<B>())) {
EV_DEBUG << "A-MSDU aggregation is not possible, collected " << frames->size() << " packets.\n";
delete frames;
return nullptr;
}
else {
EV_DEBUG << "A-MSDU aggregation is possible, collected " << frames->size() << " packets.\n";
return frames;
}
EV_DEBUG << "A-MSDU aggregation is possible, collected " << frames->size() << " packets.\n";
return frames;
}

} /* namespace ieee80211 */
} /* namespace inet */

Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class INET_API BasicMsduAggregationPolicy : public IMsduAggregationPolicy, publi
virtual bool isEligible(Packet *packet, const Ptr<const Ieee80211DataHeader>& header, const Ptr<const Ieee80211MacTrailer>& trailer, const Ptr<const Ieee80211DataHeader>& testHeader, b aMsduLength);

public:
virtual std::vector<Packet *> *computeAggregateFrames(queueing::IPacketQueue *queue) override;
virtual std::vector<Packet *> *computeAggregateFrames(queueing::IPacketQueue *queue, Packet *candidate, const std::function<bool(const Packet *)>& isFrameEligible) override;
};

} /* namespace ieee80211 */
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
//
// Copyright (C) 2026 OpenSim Ltd.
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//

import inet.common.INETDefs;
import inet.common.TagBase;

namespace inet::ieee80211;

// Identifies one local originator ADDBA transaction across packet transformations.
class Ieee80211AddbaTransactionTag extends TagBase
{
uint64_t transactionId;
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ namespace ieee80211 {

class OriginatorBlockAckAgreementHandler;

enum class OriginatorBlockAckAgreementState
{
PENDING,
ESTABLISHED,
};

class INET_API OriginatorBlockAckAgreement : public cObject
{
protected:
Expand All @@ -25,19 +31,24 @@ class INET_API OriginatorBlockAckAgreement : public cObject
int bufferSize = -1;
bool isAMsduSupported = false;
bool isDelayedBlockAckPolicySupported = false;
bool isAddbaResponseReceived = false;
OriginatorBlockAckAgreementState state = OriginatorBlockAckAgreementState::PENDING;
bool isAddbaRequestSent = false;
uint8_t dialogToken = 0;
uint64_t transactionId = 0;
simtime_t addbaResponseDeadline = -1;
simtime_t blockAckTimeoutValue = -1;
simtime_t expirationTime = -1;

public:
OriginatorBlockAckAgreement(MacAddress receiverAddr, Tid tid, SequenceNumberCyclic startingSequenceNumber, int bufferSize, bool isAMsduSupported, bool isDelayedBlockAckPolicySupported) :
OriginatorBlockAckAgreement(MacAddress receiverAddr, Tid tid, SequenceNumberCyclic startingSequenceNumber, int bufferSize, bool isAMsduSupported, bool isDelayedBlockAckPolicySupported, uint8_t dialogToken, uint64_t transactionId) :
receiverAddr(receiverAddr),
tid(tid),
startingSequenceNumber(startingSequenceNumber),
bufferSize(bufferSize),
isAMsduSupported(isAMsduSupported),
isDelayedBlockAckPolicySupported(isDelayedBlockAckPolicySupported)
isDelayedBlockAckPolicySupported(isDelayedBlockAckPolicySupported),
dialogToken(dialogToken),
transactionId(transactionId)
{
}

Expand All @@ -46,21 +57,26 @@ class INET_API OriginatorBlockAckAgreement : public cObject
virtual int getBufferSize() const { return bufferSize; }
virtual SequenceNumberCyclic getStartingSequenceNumber() { return startingSequenceNumber; }
virtual void setStartingSequenceNumber(SequenceNumberCyclic sequenceNumber) { startingSequenceNumber = sequenceNumber; }
virtual bool getIsAddbaResponseReceived() const { return isAddbaResponseReceived; }
virtual bool getIsAddbaResponseReceived() const { return state == OriginatorBlockAckAgreementState::ESTABLISHED; }
virtual bool isPending() const { return state == OriginatorBlockAckAgreementState::PENDING; }
virtual bool getIsAddbaRequestSent() const { return isAddbaRequestSent; }
virtual bool getIsAMsduSupported() const { return isAMsduSupported; }
virtual bool getIsDelayedBlockAckPolicySupported() const { return isDelayedBlockAckPolicySupported; }
virtual MacAddress getReceiverAddr() const { return receiverAddr; }
virtual Tid getTid() const { return tid; }
virtual const simtime_t getBlockAckTimeoutValue() const { return blockAckTimeoutValue; }
virtual int getNumSentBaPolicyFrames() const { return numSentBaPolicyFrames; }
virtual uint8_t getDialogToken() const { return dialogToken; }
virtual uint64_t getTransactionId() const { return transactionId; }
virtual simtime_t getAddbaResponseDeadline() const { return addbaResponseDeadline; }

virtual void setBufferSize(int bufferSize) { this->bufferSize = bufferSize; }
virtual void setIsAddbaResponseReceived(bool isAddbaResponseReceived) { this->isAddbaResponseReceived = isAddbaResponseReceived; }
virtual void setIsAddbaResponseReceived(bool isAddbaResponseReceived) { state = isAddbaResponseReceived ? OriginatorBlockAckAgreementState::ESTABLISHED : OriginatorBlockAckAgreementState::PENDING; }
virtual void setIsAddbaRequestSent(bool isAddbaRequestSent) { this->isAddbaRequestSent = isAddbaRequestSent; }
virtual void setIsAMsduSupported(bool isAMsduSupported) { this->isAMsduSupported = isAMsduSupported; }
virtual void setIsDelayedBlockAckPolicySupported(bool isDelayedBlockAckPolicySupported) { this->isDelayedBlockAckPolicySupported = isDelayedBlockAckPolicySupported; }
virtual void setBlockAckTimeoutValue(const simtime_t blockAckTimeoutValue) { this->blockAckTimeoutValue = blockAckTimeoutValue; }
virtual void setAddbaResponseDeadline(simtime_t addbaResponseDeadline) { this->addbaResponseDeadline = addbaResponseDeadline; }

virtual void baPolicyFrameSent() { numSentBaPolicyFrames++; }
virtual void calculateExpirationTime() { expirationTime = blockAckTimeoutValue == 0 ? SIMTIME_MAX : simTime() + blockAckTimeoutValue; }
Expand All @@ -71,4 +87,3 @@ class INET_API OriginatorBlockAckAgreement : public cObject
} /* namespace inet */

#endif

Loading
Loading