| /* |
| * |
| * Copyright (c) 2025 Project CHIP Authors |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include <cstdint> |
| #include <cstring> |
| #include <string> |
| #include <type_traits> |
| #include <utility> |
| |
| #include <pw_unit_test/framework.h> |
| |
| #include <lib/core/CHIPError.h> |
| #include <lib/core/StringBuilderAdapters.h> |
| #include <lib/support/CHIPMem.h> |
| #include <lib/support/Span.h> |
| #include <lib/support/TypeTraits.h> |
| #include <lib/support/logging/CHIPLogging.h> |
| #include <platform/CHIPDeviceLayer.h> |
| #include <system/RAIIMockClock.h> |
| #include <system/SystemLayer.h> |
| #include <system/SystemPacketBuffer.h> |
| #include <system/SystemStats.h> |
| |
| #include <wifipaf/WiFiPAFError.h> |
| #include <wifipaf/WiFiPAFLayer.h> |
| #include <wifipaf/WiFiPAFLayerDelegate.h> |
| |
| namespace chip { |
| namespace WiFiPAF { |
| |
| class TestWiFiPAFLayer : public WiFiPAFLayer, private WiFiPAFLayerDelegate, public ::testing::Test |
| { |
| public: |
| static void SetUpTestSuite() |
| { |
| ASSERT_EQ(chip::Platform::MemoryInit(), CHIP_NO_ERROR); |
| ASSERT_EQ(DeviceLayer::SystemLayer().Init(), CHIP_NO_ERROR); |
| } |
| |
| static void TearDownTestSuite() |
| { |
| DeviceLayer::SystemLayer().Shutdown(); |
| chip::Platform::MemoryShutdown(); |
| } |
| |
| void SetUp() override |
| { |
| ASSERT_EQ(Init(&DeviceLayer::SystemLayer()), CHIP_NO_ERROR); |
| mWiFiPAFTransport = this; |
| InitialPafInfo(); |
| } |
| |
| void TearDown() override |
| { |
| Shutdown(); |
| mWiFiPAFTransport = nullptr; |
| } |
| |
| CHIP_ERROR WiFiPAFMessageReceived(WiFiPAFSession & RxInfo, System::PacketBufferHandle && msg) override { return CHIP_NO_ERROR; } |
| CHIP_ERROR WiFiPAFMessageSend(WiFiPAFSession & TxInfo, System::PacketBufferHandle && msg) override { return CHIP_NO_ERROR; } |
| CHIP_ERROR WiFiPAFCloseSession(WiFiPAFSession & SessionInfo) override { return CHIP_NO_ERROR; } |
| bool WiFiPAFResourceAvailable() override { return mResourceAvailable; } |
| static constexpr size_t kTestPacketLength = 100; |
| static constexpr size_t kTestPacketLengthLong = 500; |
| |
| void SetEndPoint(WiFiPAFEndPoint * pEndPoint) { mEndPoint = pEndPoint; } |
| void EpDoClose(uint8_t flags, CHIP_ERROR err) { return mEndPoint->DoClose(flags, err); } |
| CHIP_ERROR EpDriveStandAloneAck() { return mEndPoint->DriveStandAloneAck(); } |
| CHIP_ERROR EpDoSendStandAloneAck() { return mEndPoint->DoSendStandAloneAck(); } |
| void EpSetRxNextSeqNum(SequenceNumber_t seq) { mEndPoint->mPafTP.mRxNextSeqNum = seq; } |
| WiFiPAFTP::State_t EpGetTxState() { return mEndPoint->mPafTP.mTxState; } |
| bool mResourceAvailable = true; |
| bool isSendQueueNull() { return mEndPoint->mSendQueue.IsNull(); } |
| uint8_t GetResourceWaitCount() { return mEndPoint->mResourceWaitCount; } |
| CHIP_ERROR EpStartReceiveConnectionTimer() { return mEndPoint->StartReceiveConnectionTimer(); } |
| bool EpHasReceiveConnectionTimer() |
| { |
| return mEndPoint->mTimerStateFlags.Has(WiFiPAFEndPoint::TimerStateFlag::kReceiveConnectionTimerRunning); |
| } |
| WiFiPAFLayer * EpGetWiFiPafLayer() { return mEndPoint->mWiFiPafLayer; } |
| void EpParkAckToSend(System::PacketBufferHandle && b) { mEndPoint->mAckToSend = std::move(b); } |
| void EpClearAll() { mEndPoint->ClearAll(); } |
| |
| private: |
| WiFiPAFEndPoint * mEndPoint; |
| }; |
| |
| // Regression: a closed WiFiPAFEndPoint must not be revivable. ClearAll() (called at the end of |
| // FinalizeClose) nulls mWiFiPafLayer; if it also left mState at a non-closed value, the DoClose() |
| // guard would re-open and a second close trigger -- e.g. a retransmit/ack timer that still |
| // references this endpoint and fires after it closed -- would re-enter and dereference the null |
| // mWiFiPafLayer. Two DoClose() calls reproduce it through the production path. |
| TEST_F(TestWiFiPAFLayer, FinalizeCloseIsIdempotentAcrossDoubleClose) |
| { |
| WiFiPAFSession sessionInfo = { |
| .role = kWiFiPafRole_Subscriber, |
| .id = 1, |
| .peer_id = 1, |
| .peer_addr = { 0xd0, 0x17, 0x69, 0xee, 0x7f, 0x3c }, |
| .nodeId = 1, |
| .discriminator = 0xF00, |
| }; |
| WiFiPAFEndPoint * ep = nullptr; |
| ASSERT_EQ(NewEndPoint(&ep, sessionInfo, sessionInfo.role), CHIP_NO_ERROR); |
| ASSERT_NE(ep, nullptr); |
| SetEndPoint(ep); |
| EXPECT_EQ(AddPafSession(PafInfoAccess::kAccSessionId, sessionInfo), CHIP_NO_ERROR); |
| ep->mState = WiFiPAFEndPoint::kState_Connected; |
| |
| EpDoClose(kWiFiPAFCloseFlag_AbortTransmission, CHIP_ERROR_INTERNAL); |
| EpDoClose(kWiFiPAFCloseFlag_AbortTransmission, CHIP_ERROR_INTERNAL); |
| SUCCEED(); |
| } |
| |
| TEST_F(TestWiFiPAFLayer, CheckWiFiPAFTransportCapabilitiesRequestMessage) |
| { |
| auto buf = System::PacketBufferHandle::New(100); |
| ASSERT_FALSE(buf.IsNull()); |
| |
| PAFTransportCapabilitiesRequestMessage msg{}; |
| msg.SetSupportedProtocolVersion(0, CHIP_PAF_TRANSPORT_PROTOCOL_MIN_SUPPORTED_VERSION); |
| msg.mMtu = CHIP_PAF_DEFAULT_MTU; |
| msg.mWindowSize = PAF_MAX_RECEIVE_WINDOW_SIZE; |
| |
| ASSERT_EQ(msg.Encode(buf), CHIP_NO_ERROR); |
| ChipLogByteSpan(Test, ByteSpan(buf->Start(), buf->DataLength())); |
| |
| PAFTransportCapabilitiesRequestMessage msgVerify; |
| ASSERT_EQ(PAFTransportCapabilitiesRequestMessage::Decode(buf, msgVerify), CHIP_NO_ERROR); |
| EXPECT_EQ(memcmp(msg.mSupportedProtocolVersions, msgVerify.mSupportedProtocolVersions, sizeof(msg.mSupportedProtocolVersions)), |
| 0); |
| EXPECT_EQ(msg.mMtu, msgVerify.mMtu); |
| EXPECT_EQ(msg.mWindowSize, msgVerify.mWindowSize); |
| } |
| |
| TEST_F(TestWiFiPAFLayer, CheckWiFiPAFTransportCapabilitiesResponseMessage) |
| { |
| auto buf = System::PacketBufferHandle::New(100); |
| ASSERT_FALSE(buf.IsNull()); |
| |
| PAFTransportCapabilitiesResponseMessage msg{}; |
| msg.mSelectedProtocolVersion = CHIP_PAF_TRANSPORT_PROTOCOL_MAX_SUPPORTED_VERSION; |
| msg.mFragmentSize = CHIP_PAF_DEFAULT_MTU; |
| msg.mWindowSize = PAF_MAX_RECEIVE_WINDOW_SIZE; |
| |
| EXPECT_EQ(msg.Encode(buf), CHIP_NO_ERROR); |
| ChipLogByteSpan(Test, ByteSpan(buf->Start(), buf->DataLength())); |
| |
| PAFTransportCapabilitiesResponseMessage msgVerify; |
| ASSERT_EQ(PAFTransportCapabilitiesResponseMessage::Decode(buf, msgVerify), CHIP_NO_ERROR); |
| EXPECT_EQ(msg.mSelectedProtocolVersion, msgVerify.mSelectedProtocolVersion); |
| EXPECT_EQ(msg.mFragmentSize, msgVerify.mFragmentSize); |
| EXPECT_EQ(msg.mWindowSize, msgVerify.mWindowSize); |
| } |
| |
| TEST_F(TestWiFiPAFLayer, CheckPafSession) |
| { |
| // Add the 1st session by giving node_id, discriminator |
| WiFiPAF::WiFiPAFSession sessionInfo = { .role = kWiFiPafRole_Subscriber, .nodeId = 0x1, .discriminator = 0xF01 }; |
| EXPECT_EQ(AddPafSession(PafInfoAccess::kAccNodeInfo, sessionInfo), CHIP_NO_ERROR); |
| |
| // Add the 2nd session |
| sessionInfo = { .role = WiFiPAF::WiFiPafRole::kWiFiPafRole_Subscriber, .nodeId = 0x2, .discriminator = 0xF02 }; |
| EXPECT_EQ(AddPafSession(PafInfoAccess::kAccNodeInfo, sessionInfo), CHIP_NO_ERROR); |
| |
| // Add the 3rd session => expect: no space |
| sessionInfo.id = 0x3; |
| EXPECT_EQ(AddPafSession(PafInfoAccess::kAccSessionId, sessionInfo), CHIP_ERROR_PROVIDER_LIST_EXHAUSTED); |
| |
| // Get the session info by giving node_id |
| sessionInfo.nodeId = 0x1; |
| auto pPafSessionInfo_nodeid = GetPAFInfo(PafInfoAccess::kAccNodeId, sessionInfo); |
| pPafSessionInfo_nodeid->id = 0x1; |
| EXPECT_EQ(pPafSessionInfo_nodeid->nodeId, 0x1u); |
| EXPECT_EQ(pPafSessionInfo_nodeid->discriminator, 0xF01); |
| |
| // Get the session info by giving the discriminator |
| sessionInfo.discriminator = 0xF01; |
| auto pPafSessionInfo_disc = GetPAFInfo(PafInfoAccess::kAccDisc, sessionInfo); |
| EXPECT_NE(pPafSessionInfo_disc, nullptr); |
| EXPECT_EQ(pPafSessionInfo_disc->nodeId, 0x1u); |
| pPafSessionInfo_disc->id = 1; |
| |
| sessionInfo.discriminator = 0xF02; |
| pPafSessionInfo_disc = GetPAFInfo(PafInfoAccess::kAccDisc, sessionInfo); |
| EXPECT_NE(pPafSessionInfo_disc, nullptr); |
| EXPECT_EQ(pPafSessionInfo_disc->nodeId, 0x2u); |
| EXPECT_EQ(pPafSessionInfo_disc->discriminator, 0xF02); |
| pPafSessionInfo_disc->id = 2; |
| |
| // Get the session info by giving the session id |
| sessionInfo.id = 0x1; |
| auto pPafSessionInfo_id = GetPAFInfo(PafInfoAccess::kAccSessionId, sessionInfo); |
| EXPECT_NE(pPafSessionInfo_id, nullptr); |
| EXPECT_EQ(pPafSessionInfo_id->nodeId, 0x1u); |
| EXPECT_EQ(pPafSessionInfo_id->discriminator, 0xF01); |
| |
| // Remove the session |
| sessionInfo.id = 0x1; |
| EXPECT_EQ(RmPafSession(PafInfoAccess::kAccSessionId, sessionInfo), CHIP_NO_ERROR); |
| sessionInfo.id = 0x2; |
| EXPECT_EQ(RmPafSession(PafInfoAccess::kAccSessionId, sessionInfo), CHIP_NO_ERROR); |
| |
| EXPECT_EQ(RmPafSession(PafInfoAccess::kAccNodeInfo, sessionInfo), CHIP_ERROR_NOT_IMPLEMENTED); |
| EXPECT_EQ(RmPafSession(PafInfoAccess::kAccSessionId, sessionInfo), CHIP_ERROR_NOT_FOUND); |
| } |
| |
| // Run under ASan to catch regressions of the heap-buffer-overflow read. |
| TEST_F(TestWiFiPAFLayer, GetPktSnRejectsShortFragment) |
| { |
| WiFiPAFSession sessionInfo = { |
| .role = kWiFiPafRole_Publisher, |
| .id = 1, |
| .peer_id = 1, |
| .peer_addr = { 0xd0, 0x17, 0x69, 0xee, 0x7f, 0x3c }, |
| .nodeId = 1, |
| .discriminator = 0xF00, |
| }; |
| |
| WiFiPAFEndPoint * newEndPoint = nullptr; |
| ASSERT_EQ(NewEndPoint(&newEndPoint, sessionInfo, sessionInfo.role), CHIP_NO_ERROR); |
| ASSERT_NE(newEndPoint, nullptr); |
| SetEndPoint(newEndPoint); |
| EXPECT_EQ(AddPafSession(PafInfoAccess::kAccSessionId, sessionInfo), CHIP_NO_ERROR); |
| newEndPoint->mState = WiFiPAFEndPoint::kState_Ready; |
| |
| // Flag byte 0x2B sets kFragmentAck and kManagementOpcode, pushing SnOffset |
| // to 3 (header + mgmt-op + ack). With a 3-byte buffer, dereferencing |
| // pHead + 3 is a heap-buffer-overflow read. |
| constexpr uint8_t kShortFragmentWithMaxFlags[] = { 0x2B, 0x8F, 0x2B }; |
| auto packet = System::PacketBufferHandle::NewWithData(kShortFragmentWithMaxFlags, sizeof(kShortFragmentWithMaxFlags)); |
| ASSERT_FALSE(packet.IsNull()); |
| |
| EXPECT_EQ(newEndPoint->Receive(std::move(packet)), CHIP_ERROR_MESSAGE_INCOMPLETE); |
| |
| EXPECT_EQ(RmPafSession(PafInfoAccess::kAccSessionId, sessionInfo), CHIP_NO_ERROR); |
| } |
| |
| TEST_F(TestWiFiPAFLayer, CheckRunAsCommissioner) |
| { |
| WiFiPAFSession sessionInfo = { |
| .role = kWiFiPafRole_Subscriber, |
| .id = 1, |
| .peer_id = 1, |
| .peer_addr = { 0xd0, 0x17, 0x69, 0xee, 0x7f, 0x3c }, |
| .nodeId = 1, |
| .discriminator = 0xF00, |
| }; |
| |
| WiFiPAFEndPoint * newEndPoint = nullptr; |
| EXPECT_EQ(NewEndPoint(&newEndPoint, sessionInfo, sessionInfo.role), CHIP_NO_ERROR); |
| EXPECT_NE(newEndPoint, nullptr); |
| SetEndPoint(newEndPoint); |
| newEndPoint->mState = WiFiPAFEndPoint::kState_Ready; |
| SetWiFiPAFState(State::kInitialized); |
| EXPECT_EQ(GetWiFiPAFState(), State::kInitialized); |
| |
| EXPECT_EQ(newEndPoint->StartConnect(), CHIP_NO_ERROR); |
| EXPECT_EQ(AddPafSession(PafInfoAccess::kAccSessionId, sessionInfo), CHIP_NO_ERROR); |
| newEndPoint->mState = WiFiPAFEndPoint::kState_Connected; |
| |
| // Send the capability request packet |
| constexpr uint8_t bufCapReq[] = { 0x65, 0x6c, 0x04, 0x00, 0x00, 0x00, 0x5e, 0x01, 0x06 }; |
| auto packetCapReq = System::PacketBufferHandle::NewWithData(bufCapReq, sizeof(bufCapReq)); |
| EXPECT_EQ(SendMessage(sessionInfo, std::move(packetCapReq)), CHIP_NO_ERROR); |
| EXPECT_EQ(HandleWriteConfirmed(sessionInfo, true), CHIP_NO_ERROR); |
| |
| // Receive the capability response packet |
| constexpr uint8_t bufCapResp[] = { 0x65, 0x6c, 0x04, 0x5b, 0x01, 0x06 }; |
| auto packetCapResp = System::PacketBufferHandle::NewWithData(bufCapResp, sizeof(bufCapResp)); |
| newEndPoint->mState = WiFiPAFEndPoint::kState_Connecting; |
| EXPECT_EQ(OnWiFiPAFMessageReceived(sessionInfo, std::move(packetCapResp)), true); |
| |
| // Send a packet |
| auto buf = System::PacketBufferHandle::New(kTestPacketLength); |
| buf->SetDataLength(kTestPacketLength); |
| memset(buf->Start(), 0, buf->DataLength()); |
| EXPECT_EQ(SendMessage(sessionInfo, std::move(buf)), CHIP_NO_ERROR); |
| EXPECT_EQ(HandleWriteConfirmed(sessionInfo, true), CHIP_NO_ERROR); |
| |
| constexpr uint8_t buf_rx[] = { |
| to_underlying(WiFiPAFTP::HeaderFlags::kStartMessage) | to_underlying(WiFiPAFTP::HeaderFlags::kEndMessage) | |
| to_underlying(WiFiPAFTP::HeaderFlags::kFragmentAck), |
| 0x01, |
| 0x01, |
| 0x00, |
| 0x00, // payload |
| }; |
| |
| // Receive a pcaket |
| auto packet_rx = System::PacketBufferHandle::NewWithData(buf_rx, sizeof(buf_rx)); |
| EXPECT_EQ(packet_rx->DataLength(), static_cast<size_t>(5)); |
| EXPECT_EQ(newEndPoint->Receive(std::move(packet_rx)), CHIP_NO_ERROR); |
| |
| EXPECT_EQ(EpDriveStandAloneAck(), CHIP_NO_ERROR); |
| EXPECT_EQ(EpDoSendStandAloneAck(), CHIP_NO_ERROR); |
| |
| // Close the session |
| EXPECT_EQ(RmPafSession(PafInfoAccess::kAccSessionId, sessionInfo), CHIP_NO_ERROR); |
| EpDoClose(kWiFiPAFCloseFlag_AbortTransmission, WIFIPAF_ERROR_APP_CLOSED_CONNECTION); |
| } |
| |
| TEST_F(TestWiFiPAFLayer, CheckRunAsCommissionee) |
| { |
| WiFiPAFSession sessionInfo = { |
| .role = kWiFiPafRole_Publisher, |
| .id = 1, |
| .peer_id = 1, |
| .peer_addr = { 0xd0, 0x17, 0x69, 0xee, 0x7f, 0x3c }, |
| .nodeId = 1, |
| .discriminator = 0xF00, |
| }; |
| |
| WiFiPAFEndPoint * newEndPoint = nullptr; |
| EXPECT_EQ(NewEndPoint(&newEndPoint, sessionInfo, sessionInfo.role), CHIP_NO_ERROR); |
| EXPECT_NE(newEndPoint, nullptr); |
| SetEndPoint(newEndPoint); |
| EXPECT_EQ(AddPafSession(PafInfoAccess::kAccSessionId, sessionInfo), CHIP_NO_ERROR); |
| newEndPoint->mState = WiFiPAFEndPoint::kState_Ready; |
| |
| // Receive the Capability_Request packet |
| constexpr uint8_t bufCapReq[] = { 0x65, 0x6c, 0x04, 0x00, 0x00, 0x00, 0x5e, 0x01, 0x06 }; |
| auto packetCapReq = System::PacketBufferHandle::NewWithData(bufCapReq, sizeof(bufCapReq)); |
| EXPECT_EQ(OnWiFiPAFMessageReceived(sessionInfo, std::move(packetCapReq)), true); |
| |
| // Reply the Capability Response packet |
| constexpr uint8_t bufCapResp[] = { 0x65, 0x6c, 0x04, 0x5b, 0x01, 0x06 }; |
| auto packetCapResp = System::PacketBufferHandle::NewWithData(bufCapResp, sizeof(bufCapResp)); |
| EXPECT_EQ(HandleWriteConfirmed(sessionInfo, true), CHIP_NO_ERROR); |
| EXPECT_EQ(SendMessage(sessionInfo, std::move(packetCapResp)), CHIP_NO_ERROR); |
| EXPECT_EQ(HandleWriteConfirmed(sessionInfo, true), CHIP_NO_ERROR); |
| |
| // Send a long packet |
| auto buf = System::PacketBufferHandle::New(kTestPacketLengthLong); |
| buf->SetDataLength(kTestPacketLengthLong); |
| memset(buf->Start(), 0, buf->DataLength()); |
| EXPECT_EQ(SendMessage(sessionInfo, std::move(buf)), CHIP_NO_ERROR); |
| EXPECT_EQ(EpGetTxState(), WiFiPAFTP::kState_InProgress); |
| EXPECT_EQ(HandleWriteConfirmed(sessionInfo, true), CHIP_NO_ERROR); |
| EXPECT_EQ(EpGetTxState(), WiFiPAFTP::kState_Complete); |
| EXPECT_EQ(HandleWriteConfirmed(sessionInfo, true), CHIP_NO_ERROR); |
| |
| // Send a normal packet |
| buf = System::PacketBufferHandle::New(kTestPacketLength); |
| buf->SetDataLength(kTestPacketLength); |
| memset(buf->Start(), 0, buf->DataLength()); |
| EXPECT_EQ(SendMessage(sessionInfo, std::move(buf)), CHIP_NO_ERROR); |
| EXPECT_EQ(EpGetTxState(), WiFiPAFTP::kState_Complete); |
| EXPECT_EQ(HandleWriteConfirmed(sessionInfo, true), CHIP_NO_ERROR); |
| |
| // Receive a packet, sn#1 |
| constexpr uint8_t buf_rx[] = { |
| to_underlying(WiFiPAFTP::HeaderFlags::kStartMessage) | to_underlying(WiFiPAFTP::HeaderFlags::kEndMessage) | |
| to_underlying(WiFiPAFTP::HeaderFlags::kFragmentAck), |
| 0x01, |
| 0x01, // sn |
| 0x00, |
| 0x00, // payload |
| }; |
| auto packet_rx = System::PacketBufferHandle::NewWithData(buf_rx, sizeof(buf_rx)); |
| EXPECT_EQ(packet_rx->DataLength(), static_cast<size_t>(5)); |
| EpSetRxNextSeqNum(1); |
| EXPECT_EQ(newEndPoint->Receive(std::move(packet_rx)), CHIP_NO_ERROR); |
| |
| // Receive the duplicate packet |
| packet_rx = System::PacketBufferHandle::NewWithData(buf_rx, sizeof(buf_rx)); |
| EXPECT_EQ(packet_rx->DataLength(), static_cast<size_t>(5)); |
| EXPECT_EQ(newEndPoint->Receive(std::move(packet_rx)), CHIP_NO_ERROR); |
| |
| // Test Reordering |
| // Receive pkt sn#3 |
| constexpr uint8_t buf_rx_sn3[] = { |
| to_underlying(WiFiPAFTP::HeaderFlags::kStartMessage) | to_underlying(WiFiPAFTP::HeaderFlags::kEndMessage) | |
| to_underlying(WiFiPAFTP::HeaderFlags::kFragmentAck), |
| 0x1, |
| 0x03, // sn |
| 0x00, |
| 0x00, // payload |
| }; |
| packet_rx = System::PacketBufferHandle::NewWithData(buf_rx_sn3, sizeof(buf_rx_sn3)); |
| EXPECT_EQ(packet_rx->DataLength(), static_cast<size_t>(5)); |
| EXPECT_EQ(newEndPoint->Receive(std::move(packet_rx)), CHIP_NO_ERROR); |
| |
| // Receive pkt sn#2 |
| constexpr uint8_t buf_rx_sn2[] = { |
| to_underlying(WiFiPAFTP::HeaderFlags::kStartMessage) | to_underlying(WiFiPAFTP::HeaderFlags::kEndMessage) | |
| to_underlying(WiFiPAFTP::HeaderFlags::kFragmentAck), |
| 0x01, |
| 0x02, // sn |
| 0x00, |
| 0x00, // payload |
| }; |
| packet_rx = System::PacketBufferHandle::NewWithData(buf_rx_sn2, sizeof(buf_rx_sn2)); |
| EXPECT_EQ(packet_rx->DataLength(), static_cast<size_t>(5)); |
| EXPECT_EQ(newEndPoint->Receive(std::move(packet_rx)), CHIP_NO_ERROR); |
| |
| // Test, send chained packet |
| constexpr uint8_t buf_chain[] = { |
| to_underlying(WiFiPAFTP::HeaderFlags::kStartMessage) | to_underlying(WiFiPAFTP::HeaderFlags::kEndMessage), |
| 0x01, |
| 0x01, |
| 0x00, |
| 0x00, // payload |
| }; |
| auto packet_c1 = System::PacketBufferHandle::NewWithData(buf_chain, sizeof(buf_chain)); |
| auto packet_c2 = System::PacketBufferHandle::NewWithData(buf_chain, sizeof(buf_chain)); |
| packet_c1->AddToEnd(std::move(packet_c2)); |
| EXPECT_EQ(packet_c1->HasChainedBuffer(), true); |
| EXPECT_EQ(newEndPoint->Send(std::move(packet_c1)), CHIP_NO_ERROR); |
| EXPECT_EQ(HandleWriteConfirmed(sessionInfo, true), CHIP_NO_ERROR); |
| |
| // Test, Send the packet while resource is unavaialbe -> available |
| mResourceAvailable = false; |
| auto packet_resource = System::PacketBufferHandle::NewWithData(buf_chain, sizeof(buf_chain)); |
| EXPECT_EQ(newEndPoint->Send(std::move(packet_resource)), CHIP_NO_ERROR); |
| // break because resource is unavailable |
| EXPECT_EQ(isSendQueueNull(), false); |
| EXPECT_GT(GetResourceWaitCount(), 0); |
| // Resource is available now |
| mResourceAvailable = true; |
| // PAF packets should be sent within a second |
| |
| System::Clock::Internal::RAIIMockClock clock; |
| |
| constexpr System::Clock::Seconds64 pauseSec = System::Clock::Seconds64(2); |
| clock.SetMonotonic(pauseSec); |
| EXPECT_EQ(HandleWriteConfirmed(sessionInfo, true), CHIP_NO_ERROR); |
| // PAF packet has been sent |
| EXPECT_EQ(isSendQueueNull(), true); |
| EXPECT_EQ(GetResourceWaitCount(), 0); |
| |
| // Close the session |
| EXPECT_EQ(RmPafSession(PafInfoAccess::kAccSessionId, sessionInfo), CHIP_NO_ERROR); |
| EpDoClose(kWiFiPAFCloseFlag_AbortTransmission, WIFIPAF_ERROR_APP_CLOSED_CONNECTION); |
| } |
| // TODO: Currently, chip::System::Layer lacks a unified virtual test abstraction |
| // (such as `ServiceScheduledTimers()` or `StepEventLoop()`) allowing standalone unit |
| // test runners to synchronously dispatch expired timer callbacks across diverse platform |
| // implementations (POSIX Sockets, LwIP, FreeRTOS). |
| // Consequently, this test directly downcasts to `System::LayerSelectLoop` internals to manually |
| // extract and run pending timer entries, restricting execution exclusively to Sockets builds. |
| #if CHIP_SYSTEM_CONFIG_USE_SOCKETS |
| TEST_F(TestWiFiPAFLayer, ReceiveConnectionTimerTimeout) |
| { |
| System::Clock::Internal::RAIIMockClock clock; |
| |
| WiFiPAFSession sessionInfo = { |
| .role = kWiFiPafRole_Publisher, |
| .id = 1, |
| .peer_id = 1, |
| .peer_addr = { 0xd0, 0x17, 0x69, 0xee, 0x7f, 0x3c }, |
| .nodeId = 1, |
| .discriminator = 0xF00, |
| }; |
| |
| WiFiPAFEndPoint * newEndPoint = nullptr; |
| EXPECT_EQ(NewEndPoint(&newEndPoint, sessionInfo, sessionInfo.role), CHIP_NO_ERROR); |
| ASSERT_NE(newEndPoint, nullptr); |
| SetEndPoint(newEndPoint); |
| |
| EXPECT_EQ(EpStartReceiveConnectionTimer(), CHIP_NO_ERROR); |
| EXPECT_TRUE(EpHasReceiveConnectionTimer()); |
| |
| // Advance mock clock past connection timeout deadline. |
| clock.AdvanceMonotonic(System::Clock::Milliseconds64(PAFTP_CONN_RSP_TIMEOUT_MS + 100)); |
| |
| // Directly drive the underlying POSIX select loop to dispatch the expired timer. |
| static_cast<System::LayerSelectLoop &>(DeviceLayer::SystemLayer()).PrepareEvents(); |
| static_cast<System::LayerSelectLoop &>(DeviceLayer::SystemLayer()).HandleEvents(); |
| |
| // Verify timer expired, flag cleared, and endpoint closed successfully. |
| EXPECT_FALSE(EpHasReceiveConnectionTimer()); |
| |
| EXPECT_EQ(EpGetWiFiPafLayer(), nullptr); |
| } |
| #endif // CHIP_SYSTEM_CONFIG_USE_SOCKETS |
| |
| TEST_F(TestWiFiPAFLayer, ShutdownClosesStalledEndpoints) |
| { |
| WiFiPAFSession sessionInfo = { |
| .role = kWiFiPafRole_Publisher, |
| .id = 1, |
| .peer_id = 1, |
| .peer_addr = { 0xd0, 0x17, 0x69, 0xee, 0x7f, 0x3c }, |
| .nodeId = 1, |
| .discriminator = 0xF00, |
| }; |
| |
| WiFiPAFEndPoint * newEndPoint = nullptr; |
| EXPECT_EQ(NewEndPoint(&newEndPoint, sessionInfo, sessionInfo.role), CHIP_NO_ERROR); |
| ASSERT_NE(newEndPoint, nullptr); |
| SetEndPoint(newEndPoint); |
| |
| Shutdown(); |
| |
| // Verify the endpoint is closed and dissociated from the layer |
| EXPECT_EQ(EpGetWiFiPafLayer(), nullptr); |
| } |
| |
| // kSystemLayer_NumPacketBufs only exists as a single counter when packet buffers are not the |
| // custom-pool LWIP configuration (which splits the counter per pool via lwippools.h), so guard |
| // these counter-based checks the same way SystemStats.h declares the entry. |
| #if CHIP_SYSTEM_CONFIG_PROVIDE_STATISTICS && !(CHIP_SYSTEM_CONFIG_USE_LWIP && CHIP_SYSTEM_CONFIG_LWIP_PBUF_FROM_CUSTOM_POOL) |
| // ClearAll() must release any PacketBufferHandle the endpoint still owns. Park one in a handle |
| // member, then check ClearAll() returns it to the pool. |
| TEST_F(TestWiFiPAFLayer, ClearAllReleasesOwnedBuffer) |
| { |
| WiFiPAFSession sessionInfo = { |
| .role = kWiFiPafRole_Subscriber, |
| .id = 1, |
| .peer_id = 1, |
| .peer_addr = { 0xd0, 0x17, 0x69, 0xee, 0x7f, 0x3c }, |
| .nodeId = 1, |
| .discriminator = 0xF00, |
| }; |
| |
| WiFiPAFEndPoint * newEndPoint = nullptr; |
| ASSERT_EQ(NewEndPoint(&newEndPoint, sessionInfo, sessionInfo.role), CHIP_NO_ERROR); |
| ASSERT_NE(newEndPoint, nullptr); |
| SetEndPoint(newEndPoint); |
| |
| auto * inUse = chip::System::Stats::GetResourcesInUse(); |
| const int baselinePacketBufs = inUse[chip::System::Stats::kSystemLayer_NumPacketBufs]; |
| |
| // Park a live packet buffer in a handle member. |
| auto buf = System::PacketBufferHandle::New(kTestPacketLength); |
| ASSERT_FALSE(buf.IsNull()); |
| EXPECT_GT(static_cast<int>(inUse[chip::System::Stats::kSystemLayer_NumPacketBufs]), baselinePacketBufs); |
| EpParkAckToSend(std::move(buf)); |
| |
| EpClearAll(); |
| |
| // The buffer must be back in the pool. |
| EXPECT_EQ(static_cast<int>(inUse[chip::System::Stats::kSystemLayer_NumPacketBufs]), baselinePacketBufs); |
| } |
| |
| // A Subscriber closed with a non-fatal error reaches ClearAll() via FinalizeClose without going |
| // through Free() (the endpoint is kept to signal close). A pending ack owned at that point must |
| // still be released. |
| TEST_F(TestWiFiPAFLayer, SubscriberNonFatalCloseReleasesPendingAck) |
| { |
| WiFiPAFSession sessionInfo = { |
| .role = kWiFiPafRole_Subscriber, |
| .id = 1, |
| .peer_id = 1, |
| .peer_addr = { 0xd0, 0x17, 0x69, 0xee, 0x7f, 0x3c }, |
| .nodeId = 1, |
| .discriminator = 0xF00, |
| }; |
| |
| WiFiPAFEndPoint * newEndPoint = nullptr; |
| ASSERT_EQ(NewEndPoint(&newEndPoint, sessionInfo, sessionInfo.role), CHIP_NO_ERROR); |
| ASSERT_NE(newEndPoint, nullptr); |
| SetEndPoint(newEndPoint); |
| EXPECT_EQ(AddPafSession(PafInfoAccess::kAccSessionId, sessionInfo), CHIP_NO_ERROR); |
| newEndPoint->mState = WiFiPAFEndPoint::kState_Connected; |
| |
| auto * inUse = chip::System::Stats::GetResourcesInUse(); |
| const int baselinePacketBufs = inUse[chip::System::Stats::kSystemLayer_NumPacketBufs]; |
| |
| // A pending stand-alone ack, as if queued when the close arrives. |
| auto buf = System::PacketBufferHandle::New(kTestPacketLength); |
| ASSERT_FALSE(buf.IsNull()); |
| EXPECT_GT(static_cast<int>(inUse[chip::System::Stats::kSystemLayer_NumPacketBufs]), baselinePacketBufs); |
| EpParkAckToSend(std::move(buf)); |
| |
| // Subscriber + non-fatal error: FinalizeClose skips Free(), then calls ClearAll(). |
| EpDoClose(kWiFiPAFCloseFlag_AbortTransmission, CHIP_ERROR_INTERNAL); |
| |
| // The pending ack must be back in the pool. |
| EXPECT_EQ(static_cast<int>(inUse[chip::System::Stats::kSystemLayer_NumPacketBufs]), baselinePacketBufs); |
| |
| EXPECT_EQ(RmPafSession(PafInfoAccess::kAccSessionId, sessionInfo), CHIP_NO_ERROR); |
| } |
| #endif // CHIP_SYSTEM_CONFIG_PROVIDE_STATISTICS && !(custom-pool LWIP) |
| |
| }; // namespace WiFiPAF |
| }; // namespace chip |