blob: c1fe806a26aae8f44a26c79ce36038085f28d2e6 [file]
/*
* Copyright (c) 2026 Project CHIP Authors
* All rights reserved.
*
* 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.
*/
// FuzzTest harness for WiFiPAFEndPoint::Receive. The injected layer delegate returns send/receive
// errors and withholds resources on a fuzz-controlled schedule (modeling a congested/closing
// transport), so the endpoint's error / close / back-pressure paths are exercised alongside the
// data-phase reassembly. Fragments are seeded with real PAFTP shapes.
#include <cstdint>
#include <cstdlib>
#include <utility>
#include <vector>
#include <pw_fuzzer/fuzztest.h>
#include <pw_unit_test/framework.h>
#include <lib/core/CHIPError.h>
#include <lib/support/CHIPMem.h>
#include <lib/support/CodeUtils.h>
#include <platform/CHIPDeviceLayer.h>
#include <system/SystemLayer.h>
#include <system/SystemPacketBuffer.h>
#include <wifipaf/WiFiPAFEndPoint.h>
#include <wifipaf/WiFiPAFLayer.h>
#include <wifipaf/WiFiPAFLayerDelegate.h>
namespace chip {
namespace WiFiPAF {
// Reuses the friended TestWiFiPAFLayer name to bring up an endpoint without the full handshake.
class TestWiFiPAFLayer : public WiFiPAFLayer, private WiFiPAFLayerDelegate
{
public:
CHIP_ERROR FuzzSetup(uint32_t sendErrMask, uint32_t recvErrMask, bool resourceAvail)
{
mSendErrMask = sendErrMask;
mRecvErrMask = recvErrMask;
mResourceAvail = resourceAvail;
ReturnErrorOnFailure(Init(&DeviceLayer::SystemLayer()));
mWiFiPAFTransport = this;
return CHIP_NO_ERROR;
}
void FuzzTeardown()
{
Shutdown();
mWiFiPAFTransport = nullptr;
}
CHIP_ERROR WiFiPAFMessageReceived(WiFiPAFSession &, System::PacketBufferHandle &&) override { return NextError(mRecvErrMask); }
CHIP_ERROR WiFiPAFMessageSend(WiFiPAFSession &, System::PacketBufferHandle &&) override { return NextError(mSendErrMask); }
CHIP_ERROR WiFiPAFCloseSession(WiFiPAFSession &) override { return CHIP_NO_ERROR; }
bool WiFiPAFResourceAvailable() override { return mResourceAvail; }
private:
// A set bit returns a transport error (as a real backend does when its queue is full).
CHIP_ERROR NextError(uint32_t & mask)
{
const bool fail = (mask & 1u) != 0u;
mask >>= 1;
return fail ? CHIP_ERROR_NOT_CONNECTED : CHIP_NO_ERROR;
}
uint32_t mSendErrMask = 0;
uint32_t mRecvErrMask = 0;
bool mResourceAvail = true;
};
} // namespace WiFiPAF
} // namespace chip
namespace {
using chip::System::PacketBufferHandle;
using chip::WiFiPAF::kWiFiPafRole_Publisher;
using chip::WiFiPAF::kWiFiPafRole_Subscriber;
using chip::WiFiPAF::PafInfoAccess;
using chip::WiFiPAF::State;
using chip::WiFiPAF::TestWiFiPAFLayer;
using chip::WiFiPAF::WiFiPAFEndPoint;
using chip::WiFiPAF::WiFiPafRole;
using chip::WiFiPAF::WiFiPAFSession;
using namespace fuzztest;
void EnsureInitialized()
{
static const bool sInitialized = [] {
VerifyOrDie(chip::Platform::MemoryInit() == CHIP_NO_ERROR);
VerifyOrDie(chip::DeviceLayer::SystemLayer().Init() == CHIP_NO_ERROR);
std::atexit([] { chip::DeviceLayer::SystemLayer().Shutdown(); });
return true;
}();
(void) sInitialized;
}
auto AnyRole()
{
return ElementOf<WiFiPafRole>({ kWiFiPafRole_Publisher, kWiFiPafRole_Subscriber });
}
auto AnyStartState()
{
return ElementOf<uint8_t>({ static_cast<uint8_t>(WiFiPAFEndPoint::kState_Ready),
static_cast<uint8_t>(WiFiPAFEndPoint::kState_Connecting),
static_cast<uint8_t>(WiFiPAFEndPoint::kState_Connected) });
}
std::vector<std::vector<uint8_t>> CapabilitySeeds()
{
return {
{ 0x65, 0x6c, 0x04, 0x00, 0x00, 0x00, 0x5e, 0x01, 0x06 },
{ 0x65, 0x6c, 0x04, 0x5b, 0x01, 0x06 },
};
}
std::vector<std::vector<uint8_t>> PaftpSeeds()
{
return {
{ 0x05, 0x01, 0x01, 0x00, 0x00 },
{ 0x05, 0x01, 0x03, 0x00, 0x00 },
{ 0x01, 0x00, 0x01, 0x00, 0x00 },
{ 0x04, 0x01, 0x02, 0x00 },
};
}
void EndpointReceiveDoesNotCrash(WiFiPafRole role, uint8_t startState, uint32_t sessionId, uint16_t discriminator,
uint32_t sendErrMask, uint32_t recvErrMask, bool resourceAvail, const std::vector<uint8_t> & frag0,
const std::vector<uint8_t> & frag1, const std::vector<uint8_t> & frag2,
const std::vector<uint8_t> & frag3)
{
EnsureInitialized();
TestWiFiPAFLayer harness;
ASSERT_EQ(harness.FuzzSetup(sendErrMask, recvErrMask, resourceAvail), CHIP_NO_ERROR);
WiFiPAFSession session = {};
session.role = role;
// 0 and kUndefinedWiFiPafSessionId are sentinels Shutdown() skips, which would leak the endpoint across iterations.
session.id = (sessionId == 0 || sessionId == chip::WiFiPAF::kUndefinedWiFiPafSessionId) ? 1 : sessionId;
session.peer_id = 1;
session.nodeId = 1;
session.discriminator = discriminator;
WiFiPAFEndPoint * ep = nullptr;
if (harness.NewEndPoint(&ep, session, role) != CHIP_NO_ERROR || ep == nullptr)
{
harness.FuzzTeardown();
return;
}
// Best-effort registration so Shutdown() can find the endpoint; a duplicate/full table is fine here.
RETURN_SAFELY_IGNORED harness.AddPafSession(PafInfoAccess::kAccSessionId, session);
ep->mState = static_cast<decltype(ep->mState)>(startState);
harness.SetWiFiPAFState(startState == WiFiPAFEndPoint::kState_Connected ? State::kConnected : State::kInitialized);
for (const auto * frag : { &frag0, &frag1, &frag2, &frag3 })
{
auto buf = PacketBufferHandle::NewWithData(frag->data(), frag->size());
if (buf.IsNull())
{
continue;
}
// Fuzzer asserts only the no-crash property; Receive's status is irrelevant here.
RETURN_SAFELY_IGNORED ep->Receive(std::move(buf));
if (ep->mState == WiFiPAFEndPoint::kState_Closed || ep->mState == WiFiPAFEndPoint::kState_Aborting)
{
break;
}
}
// FuzzTeardown()->Shutdown() closes the registered endpoint through the production DoClose path
// (cancels timers, frees buffers, returns the static pool slot via ClearAll), so the slot is reusable
// next iteration without poking mWiFiPafLayer directly. The session-id clamp above keeps it findable.
harness.FuzzTeardown();
}
FUZZ_TEST(FuzzWiFiPAFEndPointPW, EndpointReceiveDoesNotCrash)
.WithDomains(AnyRole(), AnyStartState(), Arbitrary<uint32_t>(), Arbitrary<uint16_t>(),
Arbitrary<uint32_t>(), // send-failure schedule
Arbitrary<uint32_t>(), // receive-failure schedule
Arbitrary<bool>(), // resource availability / back-pressure
Arbitrary<std::vector<uint8_t>>().WithSeeds(CapabilitySeeds()),
Arbitrary<std::vector<uint8_t>>().WithSeeds(PaftpSeeds()),
Arbitrary<std::vector<uint8_t>>().WithSeeds(PaftpSeeds()),
Arbitrary<std::vector<uint8_t>>().WithSeeds(PaftpSeeds()));
} // namespace