| /* | |
| * | |
| * 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. | |
| */ | |
| /** | |
| * @file | |
| * Stateful FuzzTest harness for the BDX TransferSession state machine. | |
| * | |
| * The existing TestBdxTransferSession unit tests drive two cooperating | |
| * sessions through the happy path (and a handful of hand-crafted | |
| * Accept/Query messages). No fuzzer drives the state machine itself: the | |
| * 6 BdxMessages parsers were fuzzed in isolation, but the ordering / | |
| * value-range space of HandleMessageReceived -> Handle{TransferInit, | |
| * ReceiveAccept,SendAccept,Block,BlockEOF,BlockQuery,BlockQueryWithSkip, | |
| * BlockAck,BlockAckEOF,StatusReport} across every TransferState was not. | |
| * | |
| * This harness stands up ONE TransferSession, bootstrapped as | |
| * either an initiator (StartTransfer) or a responder (WaitForTransfer), | |
| * then feeds it a fuzzer-chosen sequence of steps. Each step either injects | |
| * a wire message (built from the real BdxMessages structs, so it parses and | |
| * reaches the handler bodies) or invokes a local producer API | |
| * (AcceptTransfer / PrepareBlock / ...). Counter-bearing messages can carry | |
| * either the session's currently-expected counter (mirrored from the public | |
| * getters, so the mutator gets past the counter gate into the deep | |
| * block-processing states) or a fuzzer-chosen counter (to exercise the | |
| * reject paths). PollOutput is drained after every step. | |
| * | |
| * Property: no ordering / value of BDX or StatusReport messages may crash | |
| * the session (ASan/UBSan). The harness asserts nothing about the outputs. | |
| */ | |
| #include <cstdint> | |
| #include <tuple> | |
| #include <utility> | |
| #include <vector> | |
| #include <fuzztest/fuzztest_core.h> | |
| #include <gtest/gtest.h> | |
| #include <lib/core/CHIPError.h> | |
| #include <lib/support/BitFlags.h> | |
| #include <lib/support/BufferWriter.h> | |
| #include <lib/support/CHIPMem.h> | |
| #include <lib/support/CodeUtils.h> | |
| #include <protocols/Protocols.h> | |
| #include <protocols/bdx/BdxMessages.h> | |
| #include <protocols/bdx/BdxTransferSession.h> | |
| #include <protocols/bdx/StatusCode.h> | |
| #include <protocols/secure_channel/Constants.h> | |
| #include <system/SystemClock.h> | |
| #include <system/SystemPacketBuffer.h> | |
| #include <transport/raw/MessageHeader.h> | |
| namespace { | |
| using namespace chip; | |
| using namespace chip::bdx; | |
| using namespace fuzztest; | |
| using chip::System::PacketBufferHandle; | |
| constexpr System::Clock::Timestamp kNoAdvanceTime = System::Clock::kZero; | |
| // Upper bound on the number of steps per sequence. | |
| constexpr size_t kMaxSteps = 24; | |
| void EnsureInitialized() | |
| { | |
| static const bool sInitialized = [] { | |
| VerifyOrDie(chip::Platform::MemoryInit() == CHIP_NO_ERROR); | |
| return true; | |
| }(); | |
| (void) sInitialized; | |
| } | |
| // Serialize a BdxMessages struct into a PacketBuffer, matching the injection idiom | |
| // used by TestBdxTransferSession. MessageSize() and WriteToBuffer() share a code | |
| // path, so the allocated buffer is always exactly the needed size. | |
| template <typename MsgType> | |
| PacketBufferHandle EncodeBdxMessage(const MsgType & msg) | |
| { | |
| const size_t msgSize = msg.MessageSize(); | |
| Encoding::LittleEndian::PacketBufferWriter writer(PacketBufferHandle::New(msgSize), msgSize); | |
| if (writer.IsNull()) | |
| { | |
| return PacketBufferHandle(); | |
| } | |
| msg.WriteToBuffer(writer); | |
| return writer.Finalize(); | |
| } | |
| // Wrap a payload in a PayloadHeader and feed it to the session, exactly as the | |
| // exchange layer would once it has stripped the transport header. | |
| void Inject(TransferSession & session, Protocols::Id protocol, uint8_t msgType, PacketBufferHandle buf) | |
| { | |
| if (buf.IsNull()) | |
| { | |
| return; | |
| } | |
| PayloadHeader header; | |
| header.SetMessageType(protocol, msgType); | |
| RETURN_SAFELY_IGNORED session.HandleMessageReceived(header, std::move(buf), kNoAdvanceTime); | |
| } | |
| // Build a PacketBuffer from raw bytes. An empty payload maps to an empty | |
| // buffer via New(0) rather than NewWithData(payload.data(), 0), since an empty | |
| // std::vector's data() may be null. | |
| PacketBufferHandle MakeBuf(const std::vector<uint8_t> & payload) | |
| { | |
| if (payload.empty()) | |
| { | |
| return PacketBufferHandle::New(0); | |
| } | |
| return PacketBufferHandle::NewWithData(payload.data(), payload.size()); | |
| } | |
| void DrainOutput(TransferSession & session) | |
| { | |
| for (int i = 0; i < 8; ++i) | |
| { | |
| TransferSession::OutputEvent event; | |
| session.PollOutput(event, kNoAdvanceTime); | |
| if (event.EventType == TransferSession::OutputEventType::kNone) | |
| { | |
| break; | |
| } | |
| } | |
| } | |
| // Steps. The low bits of `opcode` select the action; the high bit selects whether | |
| // counter-bearing messages carry the mirrored (expected) counter or the fuzzed one. | |
| enum class Op : uint8_t | |
| { | |
| kInjectInit = 0, | |
| kInjectReceiveAccept, | |
| kInjectSendAccept, | |
| kInjectBlockQuery, | |
| kInjectBlockQueryWithSkip, | |
| kInjectBlock, | |
| kInjectBlockEOF, | |
| kInjectBlockAck, | |
| kInjectBlockAckEOF, | |
| kInjectRawStatusReport, | |
| kInjectRawBdx, | |
| kLocalAccept, | |
| kLocalReject, | |
| kLocalPrepareBlockQuery, | |
| kLocalPrepareBlockQueryWithSkip, | |
| kLocalPrepareBlock, | |
| kLocalPrepareBlockAck, | |
| kLocalAbort, | |
| kDrain, | |
| kOpCount, | |
| }; | |
| // Per-step fuzzed fields: (opcode, u16, u32, u64, payload). | |
| using Step = std::tuple<uint8_t, uint16_t, uint32_t, uint64_t, std::vector<uint8_t>>; | |
| const TransferControlFlags kDriveModes[] = { TransferControlFlags::kSenderDrive, TransferControlFlags::kReceiverDrive, | |
| TransferControlFlags::kAsync }; | |
| StatusCode PickStatusCode(uint32_t v) | |
| { | |
| const StatusCode codes[] = { StatusCode::kLengthTooLarge, | |
| StatusCode::kLengthMismatch, | |
| StatusCode::kBadMessageContents, | |
| StatusCode::kBadBlockCounter, | |
| StatusCode::kUnexpectedMessage, | |
| StatusCode::kTransferFailedUnknownError, | |
| StatusCode::kTransferMethodNotSupported, | |
| StatusCode::kFileDesignatorUnknown, | |
| StatusCode::kStartOffsetNotSupported, | |
| StatusCode::kVersionNotSupported, | |
| StatusCode::kUnknown }; | |
| return codes[v % (sizeof(codes) / sizeof(codes[0]))]; | |
| } | |
| void HandleStep(TransferSession & session, const Step & step) | |
| { | |
| const uint8_t rawOp = std::get<0>(step); | |
| const uint16_t u16 = std::get<1>(step); | |
| const uint32_t u32 = std::get<2>(step); | |
| const uint64_t u64 = std::get<3>(step); | |
| const std::vector<uint8_t> & payload = std::get<4>(step); | |
| const Op op = static_cast<Op>(rawOp % static_cast<uint8_t>(Op::kOpCount)); | |
| // Derive the mirror flag from a value bit (NOT from rawOp: `rawOp % kOpCount` | |
| // consumes the whole byte, so a high bit there would perturb the opcode). | |
| const bool useMirror = (u32 & 0x1u) != 0u; | |
| switch (op) | |
| { | |
| case Op::kInjectInit: { | |
| TransferInit msg; | |
| msg.TransferCtlOptions.Set(kDriveModes[u16 % 3]); | |
| msg.Version = 0; | |
| msg.MaxBlockSize = static_cast<uint16_t>(u32); | |
| msg.StartOffset = u64; | |
| msg.MaxLength = (static_cast<uint64_t>(u32) << 8) ^ u64; | |
| msg.FileDesLength = static_cast<uint16_t>(payload.size() > kMaxFileDesignatorLen ? kMaxFileDesignatorLen : payload.size()); | |
| msg.FileDesignator = payload.empty() ? nullptr : payload.data(); | |
| const MessageType type = (u32 & 0x1u) ? MessageType::SendInit : MessageType::ReceiveInit; | |
| Inject(session, Protocols::BDX::Id, to_underlying(type), EncodeBdxMessage(msg)); | |
| break; | |
| } | |
| case Op::kInjectReceiveAccept: { | |
| ReceiveAccept msg; | |
| msg.TransferCtlFlags.Set(kDriveModes[u16 % 3]); | |
| msg.Version = 0; | |
| msg.MaxBlockSize = static_cast<uint16_t>(u16); | |
| msg.StartOffset = static_cast<uint64_t>(u32); | |
| msg.Length = u64; | |
| Inject(session, Protocols::BDX::Id, to_underlying(MessageType::ReceiveAccept), EncodeBdxMessage(msg)); | |
| break; | |
| } | |
| case Op::kInjectSendAccept: { | |
| SendAccept msg; | |
| msg.TransferCtlFlags.Set(kDriveModes[u16 % 3]); | |
| msg.Version = 0; | |
| msg.MaxBlockSize = static_cast<uint16_t>(u16); | |
| Inject(session, Protocols::BDX::Id, to_underlying(MessageType::SendAccept), EncodeBdxMessage(msg)); | |
| break; | |
| } | |
| case Op::kInjectBlockQuery: { | |
| BlockQuery msg; | |
| msg.BlockCounter = useMirror ? session.GetNextBlockNum() : u32; | |
| Inject(session, Protocols::BDX::Id, to_underlying(MessageType::BlockQuery), EncodeBdxMessage(msg)); | |
| break; | |
| } | |
| case Op::kInjectBlockQueryWithSkip: { | |
| BlockQueryWithSkip msg; | |
| msg.BlockCounter = useMirror ? session.GetNextBlockNum() : u32; | |
| msg.BytesToSkip = u64; | |
| Inject(session, Protocols::BDX::Id, to_underlying(MessageType::BlockQueryWithSkip), EncodeBdxMessage(msg)); | |
| break; | |
| } | |
| case Op::kInjectBlock: { | |
| Block msg; | |
| // HandleBlock checks the incoming counter against mLastQueryNum (the query the receiver last sent). | |
| msg.BlockCounter = useMirror ? session.GetLastQueryNum() : u32; | |
| msg.Data = payload.empty() ? nullptr : payload.data(); | |
| msg.DataLength = payload.size(); | |
| Inject(session, Protocols::BDX::Id, to_underlying(MessageType::Block), EncodeBdxMessage(msg)); | |
| break; | |
| } | |
| case Op::kInjectBlockEOF: { | |
| BlockEOF msg; | |
| msg.BlockCounter = useMirror ? session.GetLastQueryNum() : u32; | |
| msg.Data = payload.empty() ? nullptr : payload.data(); | |
| msg.DataLength = payload.size(); | |
| Inject(session, Protocols::BDX::Id, to_underlying(MessageType::BlockEOF), EncodeBdxMessage(msg)); | |
| break; | |
| } | |
| case Op::kInjectBlockAck: { | |
| BlockAck msg; | |
| // HandleBlockAck checks the incoming counter against mLastBlockNum (the block the sender last sent). | |
| msg.BlockCounter = useMirror ? session.GetLastBlockNum() : u32; | |
| Inject(session, Protocols::BDX::Id, to_underlying(MessageType::BlockAck), EncodeBdxMessage(msg)); | |
| break; | |
| } | |
| case Op::kInjectBlockAckEOF: { | |
| BlockAckEOF msg; | |
| msg.BlockCounter = useMirror ? session.GetLastBlockNum() : u32; | |
| Inject(session, Protocols::BDX::Id, to_underlying(MessageType::BlockAckEOF), EncodeBdxMessage(msg)); | |
| break; | |
| } | |
| case Op::kInjectRawStatusReport: { | |
| // Raw SecureChannel StatusReport bytes -> HandleStatusReportMessage / StatusReport::Parse. | |
| Inject(session, Protocols::SecureChannel::Id, to_underlying(Protocols::SecureChannel::MsgType::StatusReport), | |
| MakeBuf(payload)); | |
| break; | |
| } | |
| case Op::kInjectRawBdx: { | |
| // Arbitrary bytes under a fuzzer-chosen BDX message type -> the Handle*/Parse dispatch | |
| // with garbage, in whatever state the session currently holds. | |
| Inject(session, Protocols::BDX::Id, static_cast<uint8_t>(u16), MakeBuf(payload)); | |
| break; | |
| } | |
| case Op::kLocalAccept: { | |
| TransferSession::TransferAcceptData acceptData; | |
| acceptData.ControlMode = kDriveModes[u16 % 3]; | |
| acceptData.MaxBlockSize = static_cast<uint16_t>(u16); | |
| acceptData.StartOffset = static_cast<uint64_t>(u32); | |
| acceptData.Length = u64; | |
| RETURN_SAFELY_IGNORED session.AcceptTransfer(acceptData); | |
| break; | |
| } | |
| case Op::kLocalReject: { | |
| RETURN_SAFELY_IGNORED session.RejectTransfer(PickStatusCode(u32)); | |
| break; | |
| } | |
| case Op::kLocalPrepareBlockQuery: { | |
| RETURN_SAFELY_IGNORED session.PrepareBlockQuery(); | |
| break; | |
| } | |
| case Op::kLocalPrepareBlockQueryWithSkip: { | |
| RETURN_SAFELY_IGNORED session.PrepareBlockQueryWithSkip(u64); | |
| break; | |
| } | |
| case Op::kLocalPrepareBlock: { | |
| TransferSession::BlockData blockData; | |
| blockData.Data = payload.empty() ? nullptr : payload.data(); | |
| blockData.Length = payload.size(); | |
| blockData.IsEof = (u32 & 0x1u) != 0u; | |
| RETURN_SAFELY_IGNORED session.PrepareBlock(blockData); | |
| break; | |
| } | |
| case Op::kLocalPrepareBlockAck: { | |
| RETURN_SAFELY_IGNORED session.PrepareBlockAck(); | |
| break; | |
| } | |
| case Op::kLocalAbort: { | |
| RETURN_SAFELY_IGNORED session.AbortTransfer(PickStatusCode(u32)); | |
| break; | |
| } | |
| case Op::kDrain: | |
| case Op::kOpCount: | |
| break; | |
| } | |
| DrainOutput(session); | |
| } | |
| Step Mk(uint8_t op, uint16_t a, uint32_t b, uint64_t c, std::vector<uint8_t> payload = {}) | |
| { | |
| return std::make_tuple(op, a, b, c, std::move(payload)); | |
| } | |
| // A complete FUZZ_TEST input: the six setup scalars plus the step vector. The seeds below | |
| // are whole-input seeds so each step sequence is pinned to the setup role / drive-mode that | |
| // lets it reach the deep in-progress handlers. (Seeding only the step vector -- as before -- | |
| // left weInitiate/role/drive-mode random, so a deep sequence fired only when the random setup | |
| // happened to match, and the block-prepare / ack handlers stayed near-uncovered.) | |
| using SessionInput = std::tuple<bool, uint8_t, uint8_t, uint16_t, uint64_t, uint64_t, std::vector<Step>>; | |
| SessionInput MkInput(bool weInitiate, uint8_t roleByte, uint8_t initFlags, uint16_t initMaxBlock, uint64_t initLength, | |
| uint64_t initOffset, std::vector<Step> steps) | |
| { | |
| return std::make_tuple(weInitiate, roleByte, initFlags, initMaxBlock, initLength, initOffset, std::move(steps)); | |
| } | |
| // Seeds that walk ONE session through the accept -> query -> block(/EOF) -> ack flow for each | |
| // role x drive-mode, so the mutator starts inside kTransferInProgress instead of rediscovering | |
| // the exact (role, drive-mode, ordered messages, matching counters) tuple from random bytes. | |
| // roleByte: even = receiver, odd = sender. initFlags % 3: 0 = SenderDrive, 1 = ReceiverDrive. | |
| // For counter-bearing steps, u32 bit0 = 1 requests the session's currently-expected (mirrored) | |
| // counter. A Block/Ack whose expected counter is odd cannot use the mirror (bit0 doubles as the | |
| // mirror flag), and the mirror only matches for the first block of a transfer, so these seeds | |
| // stay single-cycle and use non-mirror even literals for acks (mLastBlockNum == 0 after one block). | |
| std::vector<SessionInput> BdxWholeInputSeeds() | |
| { | |
| const std::vector<uint8_t> blk(64, 0xAB); | |
| const std::vector<uint8_t> blk32(32, 0xCD); | |
| const std::vector<uint8_t> fileDes = { 't', 'e', 's', 't', '.', 'b', 'i', 'n' }; | |
| return { | |
| // Initiating receiver, ReceiverDrive: ReceiveAccept -> BlockQuery -> Block -> PrepareBlockAck. | |
| MkInput(true, 0, 1, 64, 200, 0, { Mk(1, 64, 0, 200, {}), Mk(13, 0, 0, 0), Mk(5, 0, 1, 0, blk), Mk(16, 0, 0, 0) }), | |
| // Initiating receiver, ReceiverDrive, EOF path: ReceiveAccept -> BlockQuery -> BlockEOF -> PrepareBlockAck(EOF). | |
| MkInput(true, 0, 1, 64, 200, 0, { Mk(1, 64, 0, 200, {}), Mk(13, 0, 0, 0), Mk(6, 0, 1, 0, blk), Mk(16, 0, 0, 0) }), | |
| // Initiating receiver, ReceiverDrive, skip: ReceiveAccept -> PrepareBlockQueryWithSkip. | |
| MkInput(true, 0, 1, 64, 0, 0, { Mk(1, 64, 0, 0, {}), Mk(14, 0, 0, 128) }), | |
| // Initiating receiver, SenderDrive: ReceiveAccept -> Block -> PrepareBlockAck. | |
| MkInput(true, 0, 0, 64, 200, 0, { Mk(1, 63, 0, 200, {}), Mk(5, 0, 1, 0, blk32), Mk(16, 0, 0, 0) }), | |
| // Initiating sender, SenderDrive: SendAccept -> PrepareBlock immediately (no query needed). | |
| MkInput(true, 1, 0, 64, 0, 0, { Mk(2, 63, 0, 0, {}), Mk(15, 0, 0, 0, blk32) }), | |
| // Initiating sender, SenderDrive, EOF: SendAccept -> PrepareBlock(EOF) -> BlockAckEOF(counter 0). | |
| MkInput(true, 1, 0, 64, 0, 0, { Mk(2, 63, 0, 0, {}), Mk(15, 0, 1, 0, blk32), Mk(8, 0, 0, 0) }), | |
| // Initiating sender, ReceiverDrive: SendAccept -> BlockQuery(mirror) -> PrepareBlock -> BlockAck(counter 0). | |
| MkInput(true, 1, 1, 64, 0, 0, { Mk(2, 64, 0, 0, {}), Mk(3, 0, 1, 0), Mk(15, 0, 0, 0, blk), Mk(7, 0, 0, 0) }), | |
| // Initiating sender, ReceiverDrive: SendAccept -> BlockQueryWithSkip(mirror) -> PrepareBlock (covers | |
| // HandleBlockQueryWithSkip). | |
| MkInput(true, 1, 1, 64, 0, 0, { Mk(2, 64, 0, 0, {}), Mk(4, 0, 1, 0), Mk(15, 0, 0, 0, blk) }), | |
| // Responder as sender, ReceiverDrive: ReceiveInit -> local Accept -> BlockQuery(mirror) -> PrepareBlock. | |
| // op0 u32 carries the ReceiveInit MaxBlockSize (64); the local Accept's block size must not exceed it. | |
| MkInput(false, 1, 1, 64, 0, 0, { Mk(0, 64, 64, 0, fileDes), Mk(11, 64, 0, 0), Mk(3, 0, 1, 0), Mk(15, 0, 0, 0, blk) }), | |
| // Responder as receiver, ReceiverDrive: SendInit -> local Accept (SendAccept branch) -> PrepareBlockQuery. | |
| MkInput(false, 0, 1, 64, 0, 0, { Mk(0, 64, 65, 0, fileDes), Mk(11, 64, 0, 0), Mk(13, 0, 0, 0) }), | |
| // Responder reject: ReceiveInit -> local Reject. | |
| MkInput(false, 1, 0, 64, 0, 0, { Mk(0, 64, 0, 0, fileDes), Mk(12, 0, 0, 0) }), | |
| // Initiating receiver, ReceiverDrive, MULTI-BLOCK: Accept(len 1024) then 8x (Query, Block) then Query, BlockEOF. | |
| // Each Block/BlockEOF uses the mirrored counter; this only progresses past block 1 if the mirror is correct. | |
| MkInput(true, 0, 1, 64, 1024, 0, | |
| { Mk(1, 64, 0, 1024, {}), Mk(13, 0, 0, 0), Mk(5, 0, 1, 0, blk), Mk(13, 0, 0, 0), Mk(5, 0, 1, 0, blk), | |
| Mk(13, 0, 0, 0), Mk(5, 0, 1, 0, blk), Mk(13, 0, 0, 0), Mk(5, 0, 1, 0, blk), Mk(13, 0, 0, 0), Mk(5, 0, 1, 0, blk), | |
| Mk(13, 0, 0, 0), Mk(5, 0, 1, 0, blk), Mk(13, 0, 0, 0), Mk(5, 0, 1, 0, blk), Mk(13, 0, 0, 0), Mk(5, 0, 1, 0, blk), | |
| Mk(13, 0, 0, 0), Mk(6, 0, 1, 0, blk) }), | |
| }; | |
| } | |
| void BdxSessionSequenceDoesNotCrash(bool weInitiate, uint8_t roleByte, uint8_t initFlags, uint16_t initMaxBlock, | |
| uint64_t initLength, uint64_t initOffset, const std::vector<Step> & steps) | |
| { | |
| EnsureInitialized(); | |
| TransferSession session; | |
| const System::Clock::Timeout timeout = System::Clock::Seconds16(24); | |
| const TransferRole role = (roleByte & 0x1u) ? TransferRole::kSender : TransferRole::kReceiver; | |
| const uint16_t setupBlock = (initMaxBlock == 0) ? 64 : initMaxBlock; | |
| if (weInitiate) | |
| { | |
| TransferSession::TransferInitData initData; | |
| initData.TransferCtlFlags = kDriveModes[initFlags % 3]; | |
| initData.MaxBlockSize = setupBlock; | |
| initData.StartOffset = initOffset; | |
| initData.Length = initLength; | |
| static const uint8_t kFileDes[] = { 't', 'e', 's', 't', '.', 'b', 'i', 'n' }; | |
| initData.FileDesignator = kFileDes; | |
| initData.FileDesLength = static_cast<uint16_t>(sizeof(kFileDes)); | |
| if (session.StartTransfer(role, initData, timeout) != CHIP_NO_ERROR) | |
| { | |
| return; | |
| } | |
| } | |
| else | |
| { | |
| BitFlags<TransferControlFlags> opts; | |
| opts.Set(TransferControlFlags::kSenderDrive).Set(TransferControlFlags::kReceiverDrive); | |
| if (session.WaitForTransfer(role, opts, setupBlock, timeout) != CHIP_NO_ERROR) | |
| { | |
| return; | |
| } | |
| } | |
| DrainOutput(session); | |
| for (const Step & step : steps) | |
| { | |
| HandleStep(session, step); | |
| } | |
| } | |
| // The seven parameter domains are wrapped in ONE TupleOf so the whole-input seeds attach at DOMAIN | |
| // level (WithDomains unpacks a tuple-aggregate domain onto the parameter list). Seeds chained on | |
| // the FUZZ_TEST registration itself are only loaded through the runtime's seed enumeration, which | |
| // the libFuzzer-compat mode used by OSS-Fuzz never runs -- domain-level seeds ride the domain's | |
| // Init() and survive there. | |
| FUZZ_TEST(FuzzBdxTransferSessionPW, BdxSessionSequenceDoesNotCrash) | |
| .WithDomains(TupleOf(Arbitrary<bool>(), // weInitiate: StartTransfer vs WaitForTransfer | |
| Arbitrary<uint8_t>(), // role selector | |
| Arbitrary<uint8_t>(), // drive-mode selector for setup | |
| Arbitrary<uint16_t>(), // proposed MaxBlockSize | |
| Arbitrary<uint64_t>(), // proposed transfer length | |
| Arbitrary<uint64_t>(), // proposed start offset | |
| VectorOf(TupleOf(Arbitrary<uint8_t>(), Arbitrary<uint16_t>(), Arbitrary<uint32_t>(), Arbitrary<uint64_t>(), | |
| Arbitrary<std::vector<uint8_t>>())) | |
| .WithMaxSize(kMaxSteps)) | |
| .WithSeeds(BdxWholeInputSeeds())); | |
| } // namespace |