From 75099f19086a3bb63e00c6f251649b16efdb4718 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Fri, 11 Sep 2026 07:15:25 +0200 Subject: [PATCH 1/4] ieee80211: fix: Block Ack action wire encoding ADDBA and DELBA parameter sets used MSB-first bit packing and big-endian numeric fields, so symmetric round trips concealed incorrect wire bytes. Use the little-endian layouts in IEEE Std 802.11-2024 9.4.1.13-16 and 9.6.4.2-4, including the zero fragment number in ADDBA requests. Retain DELBA's common Action fields when decoding and exclude the FCS, which is carried separately, from its declared header length. Independent golden-octet tests cover both directions and parameter boundaries. The corrected Action bytes change MacQosWithBlockAck run 0's serialized data fingerprint from 7147-4f5b to ade8-e6a9 (~tND). Event and length fingerprints remain unchanged; carry that expectation with this fix. Also carry the five data-fingerprint expectations affected by this encoding correction: blockack NoFragmentation, Fragmentation and MixedTraffic run 0, fragmentation HCFfragblockack run 0, and adhoc qos MacQos run 1. Reversing only the serializer correction recovers all five prior values; event and length fingerprints are unchanged. Change: src.ieee80211.mac | behavior.change.fix | fingerprint test --- .../ieee80211/mac/Ieee80211Frame.msg | 2 +- .../mac/Ieee80211MacHeaderSerializer.cc | 75 ++++++----- tests/fingerprint/examples.csv | 4 +- tests/fingerprint/showcases.csv | 8 +- tests/unit/Ieee80211BlockAckActionWire_1.test | 127 ++++++++++++++++++ 5 files changed, 175 insertions(+), 41 deletions(-) create mode 100644 tests/unit/Ieee80211BlockAckActionWire_1.test diff --git a/src/inet/linklayer/ieee80211/mac/Ieee80211Frame.msg b/src/inet/linklayer/ieee80211/mac/Ieee80211Frame.msg index 033ee7141be..ebae6a54dac 100644 --- a/src/inet/linklayer/ieee80211/mac/Ieee80211Frame.msg +++ b/src/inet/linklayer/ieee80211/mac/Ieee80211Frame.msg @@ -312,7 +312,7 @@ class Ieee80211AddbaResponse extends Ieee80211ActionFrame // class Ieee80211Delba extends Ieee80211ActionFrame { - chunkLength = LENGTH_DELBA; + chunkLength = LENGTH_DELBA - B(4); // FCS is carried by Ieee80211MacTrailer category = 3; // Category field is set to 3 (representing DELBA). (1 byte) blockAckAction = 2; // Block Ack Action field is set to 2 (representing DELBA). (1 byte) diff --git a/src/inet/linklayer/ieee80211/mac/Ieee80211MacHeaderSerializer.cc b/src/inet/linklayer/ieee80211/mac/Ieee80211MacHeaderSerializer.cc index fab687ca125..531336d2537 100644 --- a/src/inet/linklayer/ieee80211/mac/Ieee80211MacHeaderSerializer.cc +++ b/src/inet/linklayer/ieee80211/mac/Ieee80211MacHeaderSerializer.cc @@ -64,6 +64,12 @@ uint16_t packSequenceControl(uint8_t fragmentNumber, uint16_t sequenceNumber) return (fragmentNumber & 0xF) | ((sequenceNumber & 0xFFF) << 4); } +uint16_t packBlockAckParameters(bool aMsduSupported, bool blockAckPolicy, uint8_t tid, uint16_t bufferSize) +{ + // IEEE Std 802.11-2024, 9.4.1.13, Figure 9-151. + return aMsduSupported | (blockAckPolicy << 1) | ((tid & 0xF) << 2) | ((bufferSize & 0x3FF) << 6); +} + void writeSequenceControl(MemoryOutputStream& stream, uint8_t fragmentNumber, uint16_t sequenceNumber) { stream.writeUint16Le(packSequenceControl(fragmentNumber, sequenceNumber)); @@ -195,13 +201,11 @@ void Ieee80211MacHeaderSerializer::serializeFields(MemoryOutputStream& stream, c auto addbaRequest = dynamicPtrCast(chunk); stream.writeByte(addbaRequest->getBlockAckAction()); stream.writeByte(addbaRequest->getDialogToken()); - stream.writeBit(addbaRequest->getAMsduSupported()); - stream.writeBit(addbaRequest->getBlockAckPolicy()); - stream.writeUint4(addbaRequest->getTid()); - stream.writeNBitsOfUint64Be(addbaRequest->getBufferSize(), 10); - stream.writeUint16Be(addbaRequest->getBlockAckTimeoutValue().inUnit(SIMTIME_US) / 1024); - stream.writeUint4(addbaRequest->get_fragmentNumber()); - stream.writeNBitsOfUint64Be(addbaRequest->getStartingSequenceNumber().get(), 12); + stream.writeUint16Le(packBlockAckParameters(addbaRequest->getAMsduSupported(), + addbaRequest->getBlockAckPolicy(), addbaRequest->getTid(), addbaRequest->getBufferSize())); + stream.writeUint16Le(addbaRequest->getBlockAckTimeoutValue().inUnit(SIMTIME_US) / 1024); + // IEEE Std 802.11-2024, 9.6.4.2: Fragment Number is zero. + writeSequenceControl(stream, 0, addbaRequest->getStartingSequenceNumber().get()); ASSERT(stream.getLength() - startPos == addbaRequest->getChunkLength()); break; } @@ -209,22 +213,20 @@ void Ieee80211MacHeaderSerializer::serializeFields(MemoryOutputStream& stream, c auto addbaResponse = dynamicPtrCast(chunk); stream.writeByte(addbaResponse->getBlockAckAction()); stream.writeByte(addbaResponse->getDialogToken()); - stream.writeUint16Be(addbaResponse->getStatusCode()); - stream.writeBit(addbaResponse->getAMsduSupported()); - stream.writeBit(addbaResponse->getBlockAckPolicy()); - stream.writeUint4(addbaResponse->getTid()); - stream.writeNBitsOfUint64Be(addbaResponse->getBufferSize(), 10); - stream.writeUint16Be(addbaResponse->getBlockAckTimeoutValue().inUnit(SIMTIME_US) / 1024); + stream.writeUint16Le(addbaResponse->getStatusCode()); + stream.writeUint16Le(packBlockAckParameters(addbaResponse->getAMsduSupported(), + addbaResponse->getBlockAckPolicy(), addbaResponse->getTid(), addbaResponse->getBufferSize())); + stream.writeUint16Le(addbaResponse->getBlockAckTimeoutValue().inUnit(SIMTIME_US) / 1024); ASSERT(stream.getLength() - startPos == addbaResponse->getChunkLength()); break; } case 2: { auto delba = dynamicPtrCast(chunk); stream.writeByte(delba->getBlockAckAction()); - stream.writeNBitsOfUint64Be(delba->getReserved(), 11); - stream.writeBit(delba->getInitiator()); - stream.writeUint4(delba->getTid()); - stream.writeUint16Be(delba->getReasonCode()); + // IEEE Std 802.11-2024, 9.4.1.16, Figure 9-154. + stream.writeUint16Le((delba->getReserved() & 0x7FF) | + (delba->getInitiator() << 11) | ((delba->getTid() & 0xF) << 12)); + stream.writeUint16Le(delba->getReasonCode()); ASSERT(stream.getLength() - startPos == delba->getChunkLength()); break; } @@ -437,13 +439,15 @@ const Ptr Ieee80211MacHeaderSerializer::deserializeFields(MemoryInputStre copyActionFrameFields(addbaRequest, actionFrame); addbaRequest->setBlockAckAction(blockAckAction); addbaRequest->setDialogToken(stream.readByte()); - addbaRequest->setAMsduSupported(stream.readBit()); - addbaRequest->setBlockAckPolicy(stream.readBit()); - addbaRequest->setTid(stream.readUint4()); - addbaRequest->setBufferSize(stream.readNBitsToUint64Be(10)); - addbaRequest->setBlockAckTimeoutValue(SimTime(stream.readUint16Be() * 1024, SIMTIME_US)); - addbaRequest->set_fragmentNumber(stream.readUint4()); - addbaRequest->setStartingSequenceNumber(SequenceNumberCyclic(stream.readNBitsToUint64Be(12))); + auto parameters = stream.readUint16Le(); + addbaRequest->setAMsduSupported(parameters & 1); + addbaRequest->setBlockAckPolicy(parameters & 2); + addbaRequest->setTid((parameters >> 2) & 0xF); + addbaRequest->setBufferSize(parameters >> 6); + addbaRequest->setBlockAckTimeoutValue(SimTime(stream.readUint16Le() * 1024, SIMTIME_US)); + readSequenceControl(stream, fragmentNumber, sequenceNumber); + addbaRequest->set_fragmentNumber(fragmentNumber); + addbaRequest->setStartingSequenceNumber(sequenceNumber); return addbaRequest; } case 1: { @@ -452,22 +456,25 @@ const Ptr Ieee80211MacHeaderSerializer::deserializeFields(MemoryInputStre copyActionFrameFields(addbaResponse, actionFrame); addbaResponse->setBlockAckAction(blockAckAction); addbaResponse->setDialogToken(stream.readByte()); - addbaResponse->setStatusCode(stream.readUint16Be()); - addbaResponse->setAMsduSupported(stream.readBit()); - addbaResponse->setBlockAckPolicy(stream.readBit()); - addbaResponse->setTid(stream.readUint4()); - addbaResponse->setBufferSize(stream.readNBitsToUint64Be(10)); - addbaResponse->setBlockAckTimeoutValue(SimTime(stream.readUint16Be() * 1024, SIMTIME_US)); + addbaResponse->setStatusCode(stream.readUint16Le()); + auto parameters = stream.readUint16Le(); + addbaResponse->setAMsduSupported(parameters & 1); + addbaResponse->setBlockAckPolicy(parameters & 2); + addbaResponse->setTid((parameters >> 2) & 0xF); + addbaResponse->setBufferSize(parameters >> 6); + addbaResponse->setBlockAckTimeoutValue(SimTime(stream.readUint16Le() * 1024, SIMTIME_US)); return addbaResponse; } case 2: { auto delba = makeShared(); copyBasicFields(delba, macHeader); + copyActionFrameFields(delba, actionFrame); delba->setBlockAckAction(blockAckAction); - delba->setReserved(stream.readNBitsToUint64Be(11)); - delba->setInitiator(stream.readBit()); - delba->setTid(stream.readUint4()); - delba->setReasonCode(stream.readUint16Be()); + auto parameters = stream.readUint16Le(); + delba->setReserved(parameters & 0x7FF); + delba->setInitiator(parameters & 0x800); + delba->setTid(parameters >> 12); + delba->setReasonCode(stream.readUint16Le()); return delba; } default: diff --git a/tests/fingerprint/examples.csv b/tests/fingerprint/examples.csv index 696ba2cb33b..9f917565cbc 100644 --- a/tests/fingerprint/examples.csv +++ b/tests/fingerprint/examples.csv @@ -7,7 +7,7 @@ # /examples/adhoc/ieee80211/, -f omnetpp.ini -c Ping2 -r 0, 100s, 0000-0000/tplx;0000-0000/~tNl;0000-0000/~tND, ERROR, wireless adhoc # [Config Ping2] # interactive config, needed a *.numHosts parameter /examples/adhoc/qos/, -f omnetpp.ini -c MacNonQos -r 0, 10s, 5749-0281/tplx;f38a-cb93/~tNl;fd9b-683f/~tND;0eb8-3e3b/tyf, PASS, wireless adhoc Ipv4 /examples/adhoc/qos/, -f omnetpp.ini -c MacQos -r 0, 10s, 1486-cbae/tplx;521b-977a/~tNl;91c2-9aa9/~tND;2c1e-66e5/tyf, PASS, wireless adhoc Ipv4 -/examples/adhoc/qos/, -f omnetpp.ini -c MacQos -r 1, 10s, 783f-d09a/tplx;13d1-ec15/~tNl;5aaf-cc7c/~tND;a587-3a4d/tyf, PASS, wireless adhoc Ipv4 +/examples/adhoc/qos/, -f omnetpp.ini -c MacQos -r 1, 10s, 783f-d09a/tplx;13d1-ec15/~tNl;a50d-9ad8/~tND;a587-3a4d/tyf, PASS, wireless adhoc Ipv4 /examples/adhoc/qos/, -f omnetpp.ini -c Fragmentation, 10s, 9c66-e6f0/tplx;cc9e-d1a6/~tNl;53c8-e1b0/~tND;33b7-00f9/tyf, PASS, wireless adhoc Ipv4 /examples/adhoc/qos/, -f omnetpp.ini -c MsduAggregation, 10s, 85bd-85c6/tplx;22f6-085d/~tNl;dc0d-4e19/~tND;df04-18f7/tyf, PASS, wireless adhoc Ipv4 @@ -658,7 +658,7 @@ /examples/wireless/qos/, -f omnetpp.ini -c MacQos -r 0, 10s, 31b0-6212/tplx;82ec-9fde/~tNl;540d-c0a4/~tND;9f3c-4512/tyf, PASS, wireless Ipv4 /examples/wireless/qos/, -f omnetpp.ini -c MacQosWithoutAggregation -r 0, 10s, acd6-0108/tplx;b339-294a/~tNl;75d4-11f9/~tND;a4c3-19bf/tyf, PASS, wireless Ipv4 /examples/wireless/qos/, -f omnetpp.ini -c MacQosWithRtsCts -r 0, 10s, 9ece-fbfb/tplx;c1af-29ff/~tNl;8fe3-d7a6/~tND;b2d6-1329/tyf, PASS, wireless Ipv4 -/examples/wireless/qos/, -f omnetpp.ini -c MacQosWithBlockAck -r 0, 10s, d094-b008/tplx;173a-e3fb/~tNl;7147-4f5b/~tND;08b5-d005/tyf, PASS, wireless Ipv4 +/examples/wireless/qos/, -f omnetpp.ini -c MacQosWithBlockAck -r 0, 10s, d094-b008/tplx;173a-e3fb/~tNl;ade8-e6a9/~tND;08b5-d005/tyf, PASS, wireless Ipv4 /examples/wireless/ratecontrol/, -f omnetpp.ini -c Mac -r 0, 100s, bf30-2f13/tplx;7b2f-653d/~tNl;6e1e-3b7b/~tND;19fe-8b0e/tyf, PASS, wireless diff --git a/tests/fingerprint/showcases.csv b/tests/fingerprint/showcases.csv index ef401f4edb1..36c08f4804e 100644 --- a/tests/fingerprint/showcases.csv +++ b/tests/fingerprint/showcases.csv @@ -199,9 +199,9 @@ /showcases/wireless/analogmodel/, -f omnetpp.ini -c Distance -r 0, 2.5s, 1e75-270e/tplx;6d7a-d84c/~tNl;b380-6cd5/~tND;5575-fd8f/tyf, PASS, wireless Ipv4 /showcases/wireless/analogmodel/, -f omnetpp.ini -c Noise -r 0, 0.1s, dd08-a63c/tplx;e167-9c84/~tNl;643d-41a6/~tND;0d1f-df73/tyf, PASS, wireless Ipv4 -/showcases/wireless/blockack/, -f omnetpp.ini -c NoFragmentation -r 0, 1s, aa2d-5d35/tplx;2094-1f2a/~tNl;1470-1e1b/~tND, PASS, wireless Ipv4 -/showcases/wireless/blockack/, -f omnetpp.ini -c Fragmentation -r 0, 1s, 7ae9-e07d/tplx;db8b-3b81/~tNl;9c41-dc97/~tND, PASS, wireless Ipv4 -/showcases/wireless/blockack/, -f omnetpp.ini -c MixedTraffic -r 0, 1s, 462d-10c7/tplx;727b-d26a/~tNl;62c4-cbc2/~tND, PASS, wireless Ipv4 +/showcases/wireless/blockack/, -f omnetpp.ini -c NoFragmentation -r 0, 1s, aa2d-5d35/tplx;2094-1f2a/~tNl;8b71-c964/~tND, PASS, wireless Ipv4 +/showcases/wireless/blockack/, -f omnetpp.ini -c Fragmentation -r 0, 1s, 7ae9-e07d/tplx;db8b-3b81/~tNl;9b1c-22eb/~tND, PASS, wireless Ipv4 +/showcases/wireless/blockack/, -f omnetpp.ini -c MixedTraffic -r 0, 1s, 462d-10c7/tplx;727b-d26a/~tNl;153d-3bdf/~tND, PASS, wireless Ipv4 /showcases/wireless/crosstalk/, -f omnetpp.ini -c CompletelyOverlappingFrequencyBands -r 0, 1s, d0d7-43e0/tplx;867a-07a4/~tNl;df78-8445/~tND;3ea3-43da/tyf, PASS, wireless Ipv4 /showcases/wireless/crosstalk/, -f omnetpp.ini -c IndependentFrequencyBandsOneRadioMediumModule -r 0, 1s, 70c6-72b6/tplx;cf96-5e4d/~tNl;3cba-ae59/~tND;d1b2-9fd4/tyf, PASS, wireless Ipv4 @@ -267,7 +267,7 @@ /showcases/wireless/fragmentation/, -f omnetpp.ini -c DCFnofrag -r 0, 1s, 52b9-628f/tplx;3fec-74a2/~tNl;6073-4582/~tND;8871-1dd1/tyf, PASS, wireless Ipv4 /showcases/wireless/fragmentation/, -f omnetpp.ini -c DCFfrag -r 0, 1s, 57ee-7ddf/tplx;dab9-5e8d/~tNl;7f9b-00fc/~tND;f985-34fb/tyf, PASS, wireless Ipv4 /showcases/wireless/fragmentation/, -f omnetpp.ini -c HCFfrag -r 0, 1s, f7a5-cf0a/tplx;a24a-a070/~tNl;7743-bba4/~tND;335d-6687/tyf, PASS, wireless Ipv4 -/showcases/wireless/fragmentation/, -f omnetpp.ini -c HCFfragblockack -r 0, 1s, 0702-c692/tplx;1bf9-b035/~tNl;9a04-4420/~tND;6f6e-b101/tyf, PASS, wireless Ipv4 +/showcases/wireless/fragmentation/, -f omnetpp.ini -c HCFfragblockack -r 0, 1s, 0702-c692/tplx;1bf9-b035/~tNl;f819-daf7/~tND;6f6e-b101/tyf, PASS, wireless Ipv4 /showcases/wireless/handover/, -f omnetpp.ini -c General -r 0, 250s, b287-6940/tplx;45c3-a292/~tNl;99ae-332d/~tND;02e9-9ad3/tyf, PASS, wireless diff --git a/tests/unit/Ieee80211BlockAckActionWire_1.test b/tests/unit/Ieee80211BlockAckActionWire_1.test new file mode 100644 index 00000000000..737be23e537 --- /dev/null +++ b/tests/unit/Ieee80211BlockAckActionWire_1.test @@ -0,0 +1,127 @@ +%description: +IEEE Std 802.11-2024 9.2.2, 9.4.1.13-16 and 9.6.4.2-4: independently encode and +parse golden ADDBA Request, ADDBA Response and DELBA octets, including common Action fields. + +%includes: +#include "inet/common/packet/Packet.h" +#include "inet/common/packet/chunk/BytesChunk.h" +#include "inet/linklayer/ieee80211/mac/Ieee80211Frame_m.h" + +%global: +using namespace inet; +using namespace inet::ieee80211; + +static std::vector actionBytes(const std::vector& body) +{ + std::vector bytes = { + 0xD0, 0x00, 0x34, 0x12, + 0x02, 0, 0, 0, 0, 1, + 0x02, 0, 0, 0, 0, 2, + 0x02, 0, 0, 0, 0, 3, + 0x37, 0x12 + }; + bytes.insert(bytes.end(), body.begin(), body.end()); + return bytes; +} + +template +static Ptr makeAction() +{ + auto header = makeShared(); + header->setDurationField(SimTime(0x1234, SIMTIME_US)); + header->setReceiverAddress(MacAddress("02:00:00:00:00:01")); + header->setTransmitterAddress(MacAddress("02:00:00:00:00:02")); + header->setAddress3(MacAddress("02:00:00:00:00:03")); + header->setFragmentNumber(7); + header->setSequenceNumber(SequenceNumberCyclic(0x123)); + return header; +} + +template +static Ptr checkAction(const Ptr& header, const std::vector& body) +{ + auto expected = actionBytes(body); + Packet encoded("encoded", header); + ASSERT(header->getChunkLength() == B(expected.size())); + ASSERT(encoded.peekDataAsBytes()->getBytes() == expected); + + // Construct raw bytes independently; do not reuse the encoded packet or its serializer cache. + Packet raw("raw", makeShared(expected)); + auto parsed = raw.popAtFront(); + ASSERT(raw.getDataLength() == B(0)); + ASSERT(parsed->getChunkLength() == B(expected.size())); + ASSERT(parsed->getDurationField() == header->getDurationField()); + ASSERT(parsed->getReceiverAddress() == header->getReceiverAddress()); + ASSERT(parsed->getTransmitterAddress() == header->getTransmitterAddress()); + ASSERT(parsed->getAddress3() == header->getAddress3()); + ASSERT(parsed->getFragmentNumber() == 7); + ASSERT(parsed->getSequenceNumber() == SequenceNumberCyclic(0x123)); + ASSERT(parsed->getCategory() == 3); + ASSERT(parsed->getBlockAckAction() == header->getBlockAckAction()); + return parsed; +} + +%activity: +{ + auto header = makeAction(); + header->setDialogToken(9); + header->setAMsduSupported(true); + header->setBlockAckPolicy(true); + header->setTid(5); + header->setBufferSize(64); + header->setBlockAckTimeoutValue(SimTime(0x1234 * 1024, SIMTIME_US)); + header->setStartingSequenceNumber(SequenceNumberCyclic(0xABC)); + auto parsed = checkAction(header, {3, 0, 9, 0x17, 0x10, 0x34, 0x12, 0xC0, 0xAB}); + ASSERT(parsed->getDialogToken() == 9); + ASSERT(parsed->getAMsduSupported() && parsed->getBlockAckPolicy()); + ASSERT(parsed->getTid() == 5 && parsed->getBufferSize() == 64); + ASSERT(parsed->getBlockAckTimeoutValue() == header->getBlockAckTimeoutValue()); + ASSERT(parsed->get_fragmentNumber() == 0); + ASSERT(parsed->getStartingSequenceNumber() == SequenceNumberCyclic(0xABC)); +} +{ + auto header = makeAction(); + header->setDialogToken(9); + header->setStatusCode(37); + header->setAMsduSupported(false); + header->setBlockAckPolicy(true); + header->setTid(15); + header->setBufferSize(1023); + header->setBlockAckTimeoutValue(SimTime(65535 * 1024, SIMTIME_US)); + auto parsed = checkAction(header, {3, 1, 9, 0x25, 0, 0xFE, 0xFF, 0xFF, 0xFF}); + ASSERT(parsed->getDialogToken() == 9 && parsed->getStatusCode() == 37); + ASSERT(!parsed->getAMsduSupported() && parsed->getBlockAckPolicy()); + ASSERT(parsed->getTid() == 15 && parsed->getBufferSize() == 1023); + ASSERT(parsed->getBlockAckTimeoutValue() == header->getBlockAckTimeoutValue()); +} +{ + auto header = makeAction(); + header->setBlockAckPolicy(true); + header->setStartingSequenceNumber(SequenceNumberCyclic(0)); + auto parsed = checkAction(header, {3, 0, 1, 2, 0, 0, 0, 0, 0}); + ASSERT(!parsed->getAMsduSupported() && parsed->getBlockAckPolicy()); + ASSERT(parsed->getTid() == 0 && parsed->getBufferSize() == 0); + ASSERT(parsed->getBlockAckTimeoutValue() == SIMTIME_ZERO); + ASSERT(parsed->getStartingSequenceNumber() == SequenceNumberCyclic(0)); +} +{ + auto header = makeAction(); + header->setInitiator(true); + header->setTid(5); + header->setReasonCode(37); + auto parsed = checkAction(header, {3, 2, 0, 0x58, 0x25, 0}); + ASSERT(parsed->getReserved() == 0 && parsed->getInitiator()); + ASSERT(parsed->getTid() == 5 && parsed->getReasonCode() == 37); +} +{ + auto header = makeAction(); + header->setTid(15); + header->setReasonCode(39); + auto parsed = checkAction(header, {3, 2, 0, 0xF0, 0x27, 0}); + ASSERT(parsed->getReserved() == 0 && !parsed->getInitiator()); + ASSERT(parsed->getTid() == 15 && parsed->getReasonCode() == 39); +} +EV << "Block Ack Action fields match IEEE golden octets.\n"; + +%contains: stdout +Block Ack Action fields match IEEE golden octets. From b4a6bd8f22a78da8b288f6f9d066929c379075b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Fri, 11 Sep 2026 07:16:43 +0200 Subject: [PATCH 2/4] ieee80211: fix: HT MPDU delimiter wire encoding HT delimiters placed the MPDU length in the wrong wire bit positions. Encode all 12 length bits in B4-B15 as specified by IEEE Std 802.11-2024 9.7.1, and reject lengths that cannot be represented in that field. The delimiter previously carried a zero CRC and accepted any CRC or signature. Compute the CRC defined by 9.7.2 and mark corrupt delimiters incorrect. Independent golden bytes exercise boundary lengths and each corrupt delimiter octet, avoiding symmetric round-trip false positives. Change: src.ieee80211.Ieee80211MpduSubframeHeaderSerializer | behavior.change.fix | test --- .../mac/Ieee80211MacHeaderSerializer.cc | 33 ++++++---- tests/unit/Ieee80211MpduDelimiterWire_1.test | 61 +++++++++++++++++++ ...ee80211MpduSubframeHeaderSerializer_1.test | 4 +- 3 files changed, 84 insertions(+), 14 deletions(-) create mode 100644 tests/unit/Ieee80211MpduDelimiterWire_1.test diff --git a/src/inet/linklayer/ieee80211/mac/Ieee80211MacHeaderSerializer.cc b/src/inet/linklayer/ieee80211/mac/Ieee80211MacHeaderSerializer.cc index 531336d2537..6dfab5ad552 100644 --- a/src/inet/linklayer/ieee80211/mac/Ieee80211MacHeaderSerializer.cc +++ b/src/inet/linklayer/ieee80211/mac/Ieee80211MacHeaderSerializer.cc @@ -7,6 +7,7 @@ #include "inet/linklayer/ieee80211/mac/Ieee80211MacHeaderSerializer.h" +#include "inet/common/checksum/Checksum.h" #include "inet/common/packet/serializer/ChunkSerializerRegistry.h" namespace inet { @@ -70,6 +71,13 @@ uint16_t packBlockAckParameters(bool aMsduSupported, bool blockAckPolicy, uint8_ return aMsduSupported | (blockAckPolicy << 1) | ((tid & 0xF) << 2) | ((bufferSize & 0x3FF) << 6); } +uint8_t computeMpduDelimiterCrc(uint16_t delimiter) +{ + // IEEE Std 802.11-2024, 9.7.2: x^8 + x^2 + x + 1, complemented remainder. + const uint8_t bytes[] = {uint8_t(delimiter), uint8_t(delimiter >> 8)}; + return generic_crc8(bytes, sizeof(bytes), 0x07, 0xFF, true, true, 0xFF); +} + void writeSequenceControl(MemoryOutputStream& stream, uint8_t fragmentNumber, uint16_t sequenceNumber) { stream.writeUint16Le(packSequenceControl(fragmentNumber, sequenceNumber)); @@ -132,24 +140,25 @@ const Ptr Ieee80211MsduSubframeHeaderSerializer::deserializeFields(Memory void Ieee80211MpduSubframeHeaderSerializer::serializeFields(MemoryOutputStream& stream, const Ptr& chunk) const { auto mpduSubframe = dynamicPtrCast(chunk); - stream.writeUint4(0); - stream.writeUint4(mpduSubframe->getLength() >> 8); - stream.writeUint8(mpduSubframe->getLength() & 0xFF); - stream.writeByte(0); + // IEEE Std 802.11-2024, 9.7.1, Figures 9-1329/9-1330: HT length occupies B4-B15. + int length = mpduSubframe->getLength(); + if (length < 0 || length > 0xFFF) + throw cRuntimeError("HT MPDU delimiter length is outside the 12-bit range: %d", length); + uint16_t delimiter = length << 4; + stream.writeUint16Le(delimiter); + stream.writeByte(computeMpduDelimiterCrc(delimiter)); stream.writeByte(0x4E); } const Ptr Ieee80211MpduSubframeHeaderSerializer::deserializeFields(MemoryInputStream& stream, const std::type_info&) const { auto mpduSubframe = makeShared(); - stream.readUint4(); - // IEEE Std 802.11-2024, 9.7.1, Table 9-659 and Figure 9-1330: - // in an HT PPDU, MPDU Length Low is 12 bits and MPDU Length High is reserved. - int length = stream.readUint4() << 8; - length |= stream.readUint8(); - mpduSubframe->setLength(length); - stream.readByte(); - stream.readByte(); + auto delimiter = stream.readUint16Le(); + mpduSubframe->setLength(delimiter >> 4); + auto crc = stream.readByte(); + auto signature = stream.readByte(); + if (crc != computeMpduDelimiterCrc(delimiter) || signature != 0x4E) + mpduSubframe->markIncorrect(); return mpduSubframe; } diff --git a/tests/unit/Ieee80211MpduDelimiterWire_1.test b/tests/unit/Ieee80211MpduDelimiterWire_1.test new file mode 100644 index 00000000000..52672a65b36 --- /dev/null +++ b/tests/unit/Ieee80211MpduDelimiterWire_1.test @@ -0,0 +1,61 @@ +%description: +IEEE Std 802.11-2024 9.7.1/9.7.2: HT delimiter length, CRC and signature match +independent golden bytes. Corrupted delimiters are marked incorrect; oversized lengths are rejected. + +%includes: +#include "inet/common/packet/Packet.h" +#include "inet/common/packet/chunk/BytesChunk.h" +#include "inet/linklayer/ieee80211/mac/Ieee80211Frame_m.h" + +%global: +using namespace inet; +using namespace inet::ieee80211; + +%activity: +// Golden CRCs obtained by polynomial long division by x^8+x^2+x+1 over the +// B0-first input, with all-one initial remainder, then complement and wire bit ordering. +const int lengths[] = {0, 1, 255, 256, 0xABC, 4095}; +const std::vector> expected = { + {0x00, 0x00, 0x14, 0x4E}, + {0x10, 0x00, 0x01, 0x4E}, + {0xF0, 0x0F, 0xAC, 0x4E}, + {0x00, 0x10, 0x08, 0x4E}, + {0xC0, 0xAB, 0x4C, 0x4E}, + {0xF0, 0xFF, 0x18, 0x4E} +}; +for (size_t i = 0; i < expected.size(); i++) { + auto header = makeShared(); + header->setLength(lengths[i]); + Packet output("delimiter", header); + ASSERT(output.peekDataAsBytes()->getBytes() == expected[i]); + Packet input("golden", makeShared(expected[i])); + auto parsed = input.popAtFront(); + ASSERT(parsed->getLength() == lengths[i]); + ASSERT(parsed->getChunkLength() == B(4)); + ASSERT(!parsed->isIncorrect() && !parsed->isIncomplete()); + ASSERT(input.getDataLength() == B(0)); +} +for (int byteIndex : {0, 1, 2, 3}) { + auto corrupt = expected[4]; + corrupt[byteIndex] ^= 1; + Packet input("corrupt", makeShared(corrupt)); + auto parsed = input.popAtFront(B(4), Chunk::PF_ALLOW_INCORRECT); + ASSERT(parsed->isIncorrect()); +} +for (int length : {-1, 4096}) { + auto header = makeShared(); + header->setLength(length); + Packet output("invalid-length", header); + bool rejected = false; + try { + output.peekDataAsBytes(); + } + catch (const cRuntimeError&) { + rejected = true; + } + ASSERT(rejected); +} +EV << "HT delimiters match IEEE golden octets and reject corrupt representations.\n"; + +%contains: stdout +HT delimiters match IEEE golden octets and reject corrupt representations. diff --git a/tests/unit/Ieee80211MpduSubframeHeaderSerializer_1.test b/tests/unit/Ieee80211MpduSubframeHeaderSerializer_1.test index 601e4c45f3d..cabcf21723b 100644 --- a/tests/unit/Ieee80211MpduSubframeHeaderSerializer_1.test +++ b/tests/unit/Ieee80211MpduSubframeHeaderSerializer_1.test @@ -33,8 +33,8 @@ for (int length : {0, 255, 256, 0xABC, 4095}) { checkDecodedLength(bytes, length); } -// Fixed input in the existing encoder's format, independent of the round trip. -checkDecodedLength({0x0A, 0xBC, 0x00, 0x4E}, 0xABC); +// Fixed HT delimiter bytes, independent of the round trip. +checkDecodedLength({0xC0, 0xAB, 0x4C, 0x4E}, 0xABC); EV << "MPDU subframe lengths preserved across the 8-bit boundary.\n"; From 1322a9884350d34d1c00f41ee3e03114718f3301 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Fri, 11 Sep 2026 07:19:54 +0200 Subject: [PATCH 3/4] ieee80211: fix: management timestamp consumption Beacon and Probe Response decoding fed the TSF field into a SimTime raw tick value even though the model does not retain that timestamp. TSF is an unsigned microsecond counter under IEEE Std 802.11-2024 9.4.1.10 and 11.1.3.1, not a simulation-resolution tick count. Consume the eight wire octets without constructing an unused SimTime. Exercise both management frames with ordinary and full-range uint64 TSF bytes, checking that the following fields remain intact. Change: src.ieee80211.Ieee80211MgmtFrameSerializer | behavior.change.fix | test --- .../mgmt/Ieee80211MgmtFrameSerializer.cc | 10 ++++++---- tests/unit/Ieee80211MgmtFrameSerializer_1.test | 17 +++++++++++++++++ 2 files changed, 23 insertions(+), 4 deletions(-) diff --git a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtFrameSerializer.cc b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtFrameSerializer.cc index 09f3ffbd05b..f874908b927 100644 --- a/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtFrameSerializer.cc +++ b/src/inet/linklayer/ieee80211/mgmt/Ieee80211MgmtFrameSerializer.cc @@ -758,8 +758,9 @@ const Ptr Ieee80211MgmtFrameSerializer::deserializeFields(MemoryInputStre else if (typeInfo == typeid(Ieee80211BeaconFrame)) { auto frame = makeShared(); - simtime_t timetstamp; - timetstamp.setRaw(stream.readUint64Le()); // TODO store timestamp + // IEEE Std 802.11-2024, 9.4.1.10 and 11.1.3.1: unsigned TSF counter in microseconds. + // The model does not retain TSF; no conversion to simulation-resolution ticks is needed. + stream.readUint64Le(); frame->setBeaconInterval(SimTime((int64_t)stream.readUint16Le() * 1024, SIMTIME_US)); stream.readUint16Le(); // Capability @@ -775,8 +776,9 @@ const Ptr Ieee80211MgmtFrameSerializer::deserializeFields(MemoryInputStre else if (typeInfo == typeid(Ieee80211ProbeResponseFrame)) { auto frame = makeShared(); - simtime_t timestamp; - timestamp.setRaw(stream.readUint64Le()); // TODO store timestamp + // IEEE Std 802.11-2024, 9.4.1.10 and 11.1.3.1: unsigned TSF counter in microseconds. + // The model does not retain TSF; no conversion to simulation-resolution ticks is needed. + stream.readUint64Le(); frame->setBeaconInterval(SimTime((int64_t)stream.readUint16Le() * 1024, SIMTIME_US)); stream.readUint16Le(); diff --git a/tests/unit/Ieee80211MgmtFrameSerializer_1.test b/tests/unit/Ieee80211MgmtFrameSerializer_1.test index f387d6b4c81..18e3638f495 100644 --- a/tests/unit/Ieee80211MgmtFrameSerializer_1.test +++ b/tests/unit/Ieee80211MgmtFrameSerializer_1.test @@ -455,6 +455,23 @@ ASSERT(unsupportedTypeRejected); wait(SimTime(0x12345678, SIMTIME_US) - simTime()); +// IEEE Std 802.11-2024, 9.4.1.10 and 11.1.3.1. TSF is an unsigned microsecond +// counter, not a simulation tick count. Since TSF is not retained, even values +// beyond SimTime's range must be consumed without disturbing subsequent fields. +for (const auto& timestampBytes : std::vector>{ + {0x40, 0x42, 0x0F, 0, 0, 0, 0, 0}, + {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}}) { + auto body = timestampBytes; + const std::vector fields = {0x34, 0x12, 0, 0, 0, 3, 'a', 'b', 'c', 1, 1, 0x0C}; + body.insert(body.end(), fields.begin(), fields.end()); + auto decodedBeacon = deserializeBody(body); + auto decodedProbe = deserializeBody(body); + ASSERT(decodedBeacon->getBeaconInterval() == SimTime(0x1234 * 1024, SIMTIME_US)); + ASSERT(decodedProbe->getBeaconInterval() == decodedBeacon->getBeaconInterval()); + ASSERT(std::string(decodedBeacon->getSSID()) == "abc"); + ASSERT(std::string(decodedProbe->getSSID()) == "abc"); +} + auto beacon = makeShared(); beacon->setSSID("abc"); beacon->setBeaconInterval(SimTime(0x1234 * 1024, SIMTIME_US)); From 0ff8df40f95411d4d65aedf222c53bdbb7263eba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20Gonz=C3=A1lez=20L=C3=B3pez?= Date: Fri, 11 Sep 2026 07:24:32 +0200 Subject: [PATCH 4/4] ieee80211: fix: A-MSDU header addresses and lengths A-MSDU construction omitted the transmitter address used for the ACK recipient and left the outer BSSID fields zero. Populate the common TA and the Basic A-MSDU BSSID fields according to IEEE Std 802.11-2024 9.3.2.1.2, Table 9-60. The modeled AP-to-AP case uses the transmitting AP address for the BSSID. No-DS deaggregation also omitted Address3, causing the restored header to serialize a zero BSSID. Preserve the outer BSSID in that direction. Account for Address4 in aggregation and deaggregation header lengths. Exercise all four DS combinations with independent outer-address bytes, ACK-target checks, header serialization and payload/address restoration. Corrected serialized headers change the following examples/wireless/qos run 0 ~tND expectations; event and length fingerprints remain unchanged: MacQos: 540d-c0a4 -> 9839-8fb0 MacQosWithRtsCts: 8fe3-d7a6 -> 4757-c17b MacQosWithBlockAck: ade8-e6a9 -> 6541-bd60 Also carry wireless qos showcase Qos run 0's data fingerprint, changing 10fc-7bb0 to 1f84-a860 (~tND). Reversing only this A-MSDU correction recovers the prior value; event and length fingerprints are unchanged. Change: src.ieee80211.mac.aggregation | behavior.change.fix | fingerprint test --- .../mac/aggregation/MsduAggregation.cc | 17 ++- .../mac/aggregation/MsduDeaggregation.cc | 4 + tests/fingerprint/examples.csv | 6 +- tests/fingerprint/showcases.csv | 2 +- tests/unit/Ieee80211MsduAggregation_1.test | 110 ++++++++++++++++++ 5 files changed, 134 insertions(+), 5 deletions(-) create mode 100644 tests/unit/Ieee80211MsduAggregation_1.test diff --git a/src/inet/linklayer/ieee80211/mac/aggregation/MsduAggregation.cc b/src/inet/linklayer/ieee80211/mac/aggregation/MsduAggregation.cc index 8303a81c0ea..4f2a4dc1f99 100644 --- a/src/inet/linklayer/ieee80211/mac/aggregation/MsduAggregation.cc +++ b/src/inet/linklayer/ieee80211/mac/aggregation/MsduAggregation.cc @@ -84,9 +84,24 @@ Packet *MsduAggregation::aggregateFrames(std::vector *frames) amsduHeader->setFromDS(fromDS); amsduHeader->setAMsduPresent(true); amsduHeader->setReceiverAddress(ra); + // IEEE Std 802.11-2024, 9.3.2.2.1: aggregation preserves the common RA and TA. + amsduHeader->setTransmitterAddress(firstHeader->getTransmitterAddress()); amsduHeader->setTid(tid); amsduHeader->addChunkLength(QOSCONTROL_PART_LENGTH); - // TODO set addr3 and addr4 according to fromDS and toDS. + // IEEE Std 802.11-2024, 9.3.2.1.1, Figure 9-119. + if (toDS && fromDS) + amsduHeader->addChunkLength(B(6)); + // IEEE Std 802.11-2024, 9.3.2.1.2, Table 9-60: Basic A-MSDUs carry the + // BSSID in Address3 and, when present, Address4. In the AP-to-AP case, + // the BSSID is the address of the transmitting AP. + if (fromDS) + amsduHeader->setAddress3(firstHeader->getTransmitterAddress()); + else if (toDS) + amsduHeader->setAddress3(ra); + else + amsduHeader->setAddress3(firstHeader->getAddress3()); + if (toDS && fromDS) + amsduHeader->setAddress4(firstHeader->getTransmitterAddress()); aggregatedFrame->insertAtFront(amsduHeader); aggregatedFrame->insertAtBack(makeShared()); aggregatedFrame->setName(aggregatedName.c_str()); diff --git a/src/inet/linklayer/ieee80211/mac/aggregation/MsduDeaggregation.cc b/src/inet/linklayer/ieee80211/mac/aggregation/MsduDeaggregation.cc index 5e4cf176158..bd4b3e82321 100644 --- a/src/inet/linklayer/ieee80211/mac/aggregation/MsduDeaggregation.cc +++ b/src/inet/linklayer/ieee80211/mac/aggregation/MsduDeaggregation.cc @@ -19,6 +19,7 @@ void MsduDeaggregation::setExplodedFrameAddress(const Ptr& if (fromDS == 0 && toDS == 0) { // STA to STA header->setTransmitterAddress(aMsduHeader->getTransmitterAddress()); header->setReceiverAddress(aMsduHeader->getReceiverAddress()); + header->setAddress3(aMsduHeader->getAddress3()); } else if (fromDS == 1 && toDS == 0) { // AP to STA header->setTransmitterAddress(aMsduHeader->getTransmitterAddress()); @@ -64,6 +65,9 @@ std::vector *MsduDeaggregation::deaggregateFrame(Packet *aggregatedFra header->addChunkLength(QOSCONTROL_PART_LENGTH); header->setToDS(amsduHeader->getToDS()); header->setFromDS(amsduHeader->getFromDS()); + // IEEE Std 802.11-2024, 9.3.2.1.1, Figure 9-119: Address4 adds six octets. + if (header->getToDS() && header->getFromDS()) + header->addChunkLength(B(6)); header->setTid(tid); header->setSequenceNumber(SequenceNumberCyclic(0)); setExplodedFrameAddress(header, msduSubframeHeader, amsduHeader); diff --git a/tests/fingerprint/examples.csv b/tests/fingerprint/examples.csv index 9f917565cbc..11192d39c06 100644 --- a/tests/fingerprint/examples.csv +++ b/tests/fingerprint/examples.csv @@ -655,10 +655,10 @@ /examples/wireless/power/, -f omnetpp.ini -c General -r 0, 100s, 6fae-d558/tplx;b8ea-b2fc/~tNl;5ea8-2cea/~tND, PASS, wireless Ipv4 /examples/wireless/qos/, -f omnetpp.ini -c MacNonQos -r 0, 10s, f252-8a4b/tplx;481d-4747/~tNl;5962-e45d/~tND;6097-a429/tyf, PASS, wireless Ipv4 -/examples/wireless/qos/, -f omnetpp.ini -c MacQos -r 0, 10s, 31b0-6212/tplx;82ec-9fde/~tNl;540d-c0a4/~tND;9f3c-4512/tyf, PASS, wireless Ipv4 +/examples/wireless/qos/, -f omnetpp.ini -c MacQos -r 0, 10s, 31b0-6212/tplx;82ec-9fde/~tNl;9839-8fb0/~tND;9f3c-4512/tyf, PASS, wireless Ipv4 /examples/wireless/qos/, -f omnetpp.ini -c MacQosWithoutAggregation -r 0, 10s, acd6-0108/tplx;b339-294a/~tNl;75d4-11f9/~tND;a4c3-19bf/tyf, PASS, wireless Ipv4 -/examples/wireless/qos/, -f omnetpp.ini -c MacQosWithRtsCts -r 0, 10s, 9ece-fbfb/tplx;c1af-29ff/~tNl;8fe3-d7a6/~tND;b2d6-1329/tyf, PASS, wireless Ipv4 -/examples/wireless/qos/, -f omnetpp.ini -c MacQosWithBlockAck -r 0, 10s, d094-b008/tplx;173a-e3fb/~tNl;ade8-e6a9/~tND;08b5-d005/tyf, PASS, wireless Ipv4 +/examples/wireless/qos/, -f omnetpp.ini -c MacQosWithRtsCts -r 0, 10s, 9ece-fbfb/tplx;c1af-29ff/~tNl;4757-c17b/~tND;b2d6-1329/tyf, PASS, wireless Ipv4 +/examples/wireless/qos/, -f omnetpp.ini -c MacQosWithBlockAck -r 0, 10s, d094-b008/tplx;173a-e3fb/~tNl;6541-bd60/~tND;08b5-d005/tyf, PASS, wireless Ipv4 /examples/wireless/ratecontrol/, -f omnetpp.ini -c Mac -r 0, 100s, bf30-2f13/tplx;7b2f-653d/~tNl;6e1e-3b7b/~tND;19fe-8b0e/tyf, PASS, wireless diff --git a/tests/fingerprint/showcases.csv b/tests/fingerprint/showcases.csv index 36c08f4804e..1b203169953 100644 --- a/tests/fingerprint/showcases.csv +++ b/tests/fingerprint/showcases.csv @@ -314,7 +314,7 @@ /showcases/wireless/power/, -f omnetpp.ini -c General -r 0, 100s, 498f-b665/tplx;6f50-5caf/~tNl;0ad4-1089/~tND, PASS, wireless Ipv4 /showcases/wireless/qos/, -f omnetpp.ini -c NonQos -r 0, 10s, 37fd-5401/tplx;92f7-198c/~tNl;955c-ee96/~tND;093e-1ca4/tyf, PASS, wireless Ipv4 -/showcases/wireless/qos/, -f omnetpp.ini -c Qos -r 0, 10s, 1a49-72b3/tplx;e605-d79d/~tNl;10fc-7bb0/~tND;bda9-15d1/tyf, PASS, wireless Ipv4 +/showcases/wireless/qos/, -f omnetpp.ini -c Qos -r 0, 10s, 1a49-72b3/tplx;e605-d79d/~tNl;1f84-a860/~tND;bda9-15d1/tyf, PASS, wireless Ipv4 /showcases/wireless/ratecontrol/, -f omnetpp.ini -c NoRateControl -r 0, 14s, 7ee9-503a/tplx;0816-e58f/~tNl;648e-6e84/~tND;dad3-7f89/tyf, PASS, wireless Ipv4 /showcases/wireless/ratecontrol/, -f omnetpp.ini -c AarfRateControl -r 0, 12s, a7bc-05bb/tplx;9de0-4dd3/~tNl;1209-101b/~tND;7539-d32d/tyf, PASS, wireless Ipv4 diff --git a/tests/unit/Ieee80211MsduAggregation_1.test b/tests/unit/Ieee80211MsduAggregation_1.test new file mode 100644 index 00000000000..c6b142c9e29 --- /dev/null +++ b/tests/unit/Ieee80211MsduAggregation_1.test @@ -0,0 +1,110 @@ +%description: +A-MSDU aggregation preserves TA and the resulting ACK target in all four DS directions. +Aggregation and deaggregation declare the actual 26/32-byte QoS header size and preserve +subframe payloads and source/destination addresses. IEEE Std 802.11-2024 9.3.2.1/9.3.2.2. +Outer Address3/Address4 bytes follow the Basic A-MSDU BSSID rules in Table 9-60. + +%includes: +#include "inet/common/packet/Packet.h" +#include "inet/common/packet/chunk/BytesChunk.h" +#include "inet/linklayer/ieee80211/mac/aggregation/MsduAggregation.h" +#include "inet/linklayer/ieee80211/mac/aggregation/MsduDeaggregation.h" +#include "inet/linklayer/ieee80211/mac/recipient/RecipientAckProcedure.h" +#include "inet/linklayer/ieee80211/mac/sequencenumberassignment/QoSSequenceNumberAssignment.h" + +%global: +using namespace inet; +using namespace inet::ieee80211; + +class TestAckProcedure : public RecipientAckProcedure +{ + public: + using RecipientAckProcedure::buildAck; +}; + +%activity: +const MacAddress ra("02:00:00:00:00:01"); +const MacAddress ta("02:00:00:00:00:02"); +const MacAddress address3("02:00:00:00:00:03"); +const MacAddress address4("02:00:00:00:00:04"); +MsduAggregation aggregation; +MsduDeaggregation deaggregation; +TestAckProcedure ackProcedure; +QoSSequenceNumberAssignment sequenceNumbers; +// Independently specified Address3 bytes for (ToDS, FromDS) = 00, 01, 10, 11. +// The four-address case models AP-to-AP transmission: BSSID is the transmitting AP. +const std::vector> expectedAddress3 = { + {0x02, 0, 0, 0, 0, 0x03}, + {0x02, 0, 0, 0, 0, 0x02}, + {0x02, 0, 0, 0, 0, 0x01}, + {0x02, 0, 0, 0, 0, 0x02} +}; +const std::vector expectedAddress4 = {0x02, 0, 0, 0, 0, 0x02}; +for (bool toDS : {false, true}) { + for (bool fromDS : {false, true}) { + std::vector frames; + const B headerLength(toDS && fromDS ? 32 : 26); + const std::vector> payloads = { + {1, 2, 3, 4, 5}, {6, 7, 8, 9, 10, 11} + }; + for (size_t i = 0; i < payloads.size(); i++) { + auto header = makeShared(); + header->setType(ST_DATA_WITH_QOS); + header->setToDS(toDS); + header->setFromDS(fromDS); + header->setReceiverAddress(ra); + header->setTransmitterAddress(ta); + header->setAddress3(address3); + header->setAddress4(address4); + header->setTid(5); + header->setChunkLength(headerLength); + auto frame = new Packet(i == 0 ? "first" : "second", header); + frame->insertAtBack(makeShared(payloads[i])); + frame->insertAtBack(makeShared()); + frames.push_back(frame); + } + auto aggregate = aggregation.aggregateFrames(&frames); + // OriginatorQosMacDataService assigns the sequence after aggregation, before transmission. + auto sequencedHeader = aggregate->removeAtFront(); + sequenceNumbers.assignSequenceNumber(sequencedHeader); + aggregate->insertAtFront(sequencedHeader); + auto outer = aggregate->peekAtFront(); + ASSERT(outer->getReceiverAddress() == ra); + ASSERT(outer->getTransmitterAddress() == ta); + ASSERT(outer->getToDS() == toDS && outer->getFromDS() == fromDS); + ASSERT(outer->getTid() == 5 && outer->getAMsduPresent()); + ASSERT(outer->getChunkLength() == headerLength); + Packet outerPacket("outer", outer); + ASSERT(outerPacket.peekDataAsBytes()->getChunkLength() == headerLength); + const auto bytes = outerPacket.peekDataAsBytes()->getBytes(); + ASSERT(std::vector(bytes.begin() + 16, bytes.begin() + 22) == expectedAddress3[2 * toDS + fromDS]); + if (toDS && fromDS) + ASSERT(std::vector(bytes.begin() + 24, bytes.begin() + 30) == expectedAddress4); + ASSERT(ackProcedure.buildAck(outer)->getReceiverAddress() == ta); + + auto restored = deaggregation.deaggregateFrame(aggregate); + ASSERT(restored->size() == payloads.size()); + for (size_t i = 0; i < restored->size(); i++) { + auto frame = restored->at(i); + auto header = frame->popAtFront(); + ASSERT(header->getTransmitterAddress() == ta); + ASSERT(header->getReceiverAddress() == ra); + ASSERT(header->getChunkLength() == headerLength); + ASSERT(header->getToDS() == toDS && header->getFromDS() == fromDS); + ASSERT(header->getTid() == 5 && !header->getAMsduPresent()); + ASSERT(header->getAddress3() == address3); + if (toDS && fromDS) + ASSERT(header->getAddress4() == address4); + Packet headerPacket("restored-header", header); + ASSERT(headerPacket.peekDataAsBytes()->getChunkLength() == headerLength); + frame->popAtBack(B(4)); + ASSERT(frame->peekDataAsBytes()->getBytes() == payloads[i]); + delete frame; + } + delete restored; + } +} +EV << "A-MSDU TA, ACK target, header lengths and payloads are preserved.\n"; + +%contains: stdout +A-MSDU TA, ACK target, header lengths and payloads are preserved.