#include #include #include #include #include #include #include #include #include #include #include #include #include using namespace ::chip; using namespace ::chip::bdx; using namespace ::chip::Protocols; namespace { // Use this as a timestamp if not needing to test BDX timeouts. constexpr System::Clock::Timestamp kNoAdvanceTime = System::Clock::kZero; const TLV::Tag tlvStrTag = TLV::ContextTag(4); const TLV::Tag tlvListTag = TLV::ProfileTag(7777, 8888); } // anonymous namespace // Helper method for generating a complete TLV structure with a list containing a single tag and string CHIP_ERROR WriteTLVString(uint8_t * buf, uint32_t bufLen, const char * data, uint32_t & written) { written = 0; TLV::TLVWriter writer; writer.Init(buf, bufLen); { TLV::TLVWriter listWriter; ReturnErrorOnFailure(writer.OpenContainer(tlvListTag, TLV::kTLVType_List, listWriter)); ReturnErrorOnFailure(listWriter.PutString(tlvStrTag, data)); ReturnErrorOnFailure(writer.CloseContainer(listWriter)); } ReturnErrorOnFailure(writer.Finalize()); written = writer.GetLengthWritten(); return CHIP_NO_ERROR; } // Helper method: read a TLV structure with a single tag and string and verify it matches expected string. CHIP_ERROR ReadAndVerifyTLVString(const uint8_t * dataStart, uint32_t len, const char * expected, size_t expectedLen) { TLV::TLVReader reader; char tmp[64] = { 0 }; size_t readLength = 0; VerifyOrReturnError(sizeof(tmp) > len, CHIP_ERROR_INTERNAL); reader.Init(dataStart, len); CHIP_ERROR err = reader.Next(); VerifyOrReturnError(reader.GetTag() == tlvListTag, CHIP_ERROR_INTERNAL); // Metadata must have a top-level list { TLV::TLVReader listReader; ReturnErrorOnFailure(reader.OpenContainer(listReader)); ReturnErrorOnFailure(listReader.Next()); VerifyOrReturnError(listReader.GetTag() == tlvStrTag, CHIP_ERROR_INTERNAL); readLength = listReader.GetLength(); VerifyOrReturnError(readLength == expectedLen, CHIP_ERROR_INTERNAL); ReturnErrorOnFailure(listReader.GetString(tmp, sizeof(tmp))); VerifyOrReturnError(!memcmp(expected, tmp, readLength), CHIP_ERROR_INTERNAL); ReturnErrorOnFailure(reader.CloseContainer(listReader)); } return err; } CHIP_ERROR AttachHeaderAndSend(TransferSession::MessageTypeData typeData, chip::System::PacketBufferHandle msgBuf, TransferSession & receiver) { chip::PayloadHeader payloadHeader; payloadHeader.SetMessageType(typeData.ProtocolId, typeData.MessageType); ReturnErrorOnFailure(receiver.HandleMessageReceived(payloadHeader, std::move(msgBuf), kNoAdvanceTime)); return CHIP_NO_ERROR; } // Helper method for verifying that a PacketBufferHandle contains a valid BDX header and message type matches expected. void VerifyBdxMessageToSend(const TransferSession::OutputEvent & outEvent, MessageType expected) { static_assert(std::is_same, uint8_t>::value, "Cast is not safe"); EXPECT_EQ(outEvent.EventType, TransferSession::OutputEventType::kMsgToSend); EXPECT_FALSE(outEvent.MsgData.IsNull()); EXPECT_EQ(outEvent.msgTypeData.ProtocolId, Protocols::BDX::Id); EXPECT_EQ(outEvent.msgTypeData.MessageType, static_cast(expected)); } // Helper method for verifying that a PacketBufferHandle contains a valid StatusReport message and contains a specific StatusCode. // The msg argument is expected to begin at the message data start, not at the PayloadHeader. void VerifyStatusReport(const System::PacketBufferHandle & msg, StatusCode expectedCode) { CHIP_ERROR err = CHIP_NO_ERROR; ASSERT_FALSE(msg.IsNull()); System::PacketBufferHandle msgCopy = msg.CloneData(); ASSERT_FALSE(msgCopy.IsNull()); SecureChannel::StatusReport report; err = report.Parse(std::move(msgCopy)); EXPECT_EQ(err, CHIP_NO_ERROR); EXPECT_EQ(report.GetGeneralCode(), SecureChannel::GeneralStatusCode::kFailure); EXPECT_EQ(report.GetProtocolId(), Protocols::BDX::Id); EXPECT_EQ(report.GetProtocolCode(), static_cast(expectedCode)); } void VerifyNoMoreOutput(TransferSession & transferSession) { TransferSession::OutputEvent event; transferSession.PollOutput(event, kNoAdvanceTime); EXPECT_EQ(event.EventType, TransferSession::OutputEventType::kNone); } void VerifyInternalError(TransferSession & transferSession) { TransferSession::OutputEvent event; transferSession.PollOutput(event, kNoAdvanceTime); EXPECT_EQ(event.EventType, TransferSession::OutputEventType::kInternalError); } // Helper method for initializing two TransferSession objects, generating a TransferInit message, and passing it to a responding // TransferSession. void SendAndVerifyTransferInit(TransferSession::OutputEvent & outEvent, System::Clock::Timeout timeout, TransferSession & initiator, TransferRole initiatorRole, TransferSession::TransferInitData initData, TransferSession & responder, BitFlags & responderControlOpts, uint16_t responderMaxBlock) { CHIP_ERROR err = CHIP_NO_ERROR; TransferRole responderRole = (initiatorRole == TransferRole::kSender) ? TransferRole::kReceiver : TransferRole::kSender; MessageType expectedInitMsg = (initiatorRole == TransferRole::kSender) ? MessageType::SendInit : MessageType::ReceiveInit; // Initializer responder to wait for transfer err = responder.WaitForTransfer(responderRole, responderControlOpts, responderMaxBlock, timeout); EXPECT_EQ(err, CHIP_NO_ERROR); VerifyNoMoreOutput(responder); // Verify initiator outputs respective Init message (depending on role) after StartTransfer() err = initiator.StartTransfer(initiatorRole, initData, timeout); EXPECT_EQ(err, CHIP_NO_ERROR); initiator.PollOutput(outEvent, kNoAdvanceTime); EXPECT_EQ(outEvent.EventType, TransferSession::OutputEventType::kMsgToSend); VerifyBdxMessageToSend(outEvent, expectedInitMsg); VerifyNoMoreOutput(initiator); // Verify that all parsed TransferInit fields match what was sent by the initiator err = AttachHeaderAndSend(outEvent.msgTypeData, std::move(outEvent.MsgData), responder); EXPECT_EQ(err, CHIP_NO_ERROR); responder.PollOutput(outEvent, kNoAdvanceTime); VerifyNoMoreOutput(responder); EXPECT_EQ(outEvent.EventType, TransferSession::OutputEventType::kInitReceived); EXPECT_EQ(outEvent.transferInitData.TransferCtlFlags, initData.TransferCtlFlags); EXPECT_EQ(outEvent.transferInitData.MaxBlockSize, initData.MaxBlockSize); EXPECT_EQ(outEvent.transferInitData.StartOffset, initData.StartOffset); EXPECT_EQ(outEvent.transferInitData.Length, initData.Length); EXPECT_NE(outEvent.transferInitData.FileDesignator, nullptr); EXPECT_EQ(outEvent.transferInitData.FileDesLength, initData.FileDesLength); if (outEvent.EventType == TransferSession::OutputEventType::kInitReceived && outEvent.transferInitData.FileDesignator != nullptr) { EXPECT_EQ( 0, memcmp(initData.FileDesignator, outEvent.transferInitData.FileDesignator, outEvent.transferInitData.FileDesLength)); } if (outEvent.transferInitData.Metadata != nullptr) { ASSERT_EQ(outEvent.transferInitData.MetadataLength, initData.MetadataLength); // Even if initData.MetadataLength is 0, it is still technically undefined behaviour to call memcmp with a null bool isNullAndLengthZero = initData.Metadata == nullptr && initData.MetadataLength == 0; if (!isNullAndLengthZero) { // Only check that metadata buffers match. The OutputEvent can still be inspected when this function returns to // parse the metadata and verify that it matches. EXPECT_EQ(0, memcmp(initData.Metadata, outEvent.transferInitData.Metadata, outEvent.transferInitData.MetadataLength)); } } } // Helper method for sending an Accept message and verifying that the received parameters match what was sent. // This function assumes that the acceptData struct contains transfer parameters that are valid responses to the original // TransferInit message (for example, MaxBlockSize should be <= the TransferInit MaxBlockSize). If such parameters are invalid, the // receiver should emit a StatusCode event instead. // // The acceptSender is the node that is sending the Accept message (not necessarily the same node that will send Blocks). void SendAndVerifyAcceptMsg(TransferSession::OutputEvent & outEvent, TransferSession & acceptSender, TransferRole acceptSenderRole, TransferSession::TransferAcceptData acceptData, TransferSession & acceptReceiver, TransferSession::TransferInitData initData) { CHIP_ERROR err = CHIP_NO_ERROR; // If the node sending the Accept message is also the one that will send Blocks, then this should be a ReceiveAccept message. MessageType expectedMsg = (acceptSenderRole == TransferRole::kSender) ? MessageType::ReceiveAccept : MessageType::SendAccept; err = acceptSender.AcceptTransfer(acceptData); EXPECT_EQ(err, CHIP_NO_ERROR); // Verify Sender emits ReceiveAccept message for sending acceptSender.PollOutput(outEvent, kNoAdvanceTime); VerifyNoMoreOutput(acceptSender); EXPECT_EQ(outEvent.EventType, TransferSession::OutputEventType::kMsgToSend); VerifyBdxMessageToSend(outEvent, expectedMsg); // Pass Accept message to acceptReceiver err = AttachHeaderAndSend(outEvent.msgTypeData, std::move(outEvent.MsgData), acceptReceiver); EXPECT_EQ(err, CHIP_NO_ERROR); // Verify received ReceiveAccept. // Client may want to inspect TransferControl, MaxBlockSize, StartOffset, Length, and Metadata, and may choose to reject the // Transfer at this point. acceptReceiver.PollOutput(outEvent, kNoAdvanceTime); VerifyNoMoreOutput(acceptReceiver); EXPECT_EQ(outEvent.EventType, TransferSession::OutputEventType::kAcceptReceived); EXPECT_EQ(outEvent.transferAcceptData.ControlMode, acceptData.ControlMode); EXPECT_EQ(outEvent.transferAcceptData.MaxBlockSize, acceptData.MaxBlockSize); EXPECT_EQ(outEvent.transferAcceptData.StartOffset, acceptData.StartOffset); EXPECT_EQ(outEvent.transferAcceptData.Length, acceptData.Length); if (outEvent.transferAcceptData.Metadata != nullptr) { ASSERT_EQ(outEvent.transferAcceptData.MetadataLength, acceptData.MetadataLength); // Even if acceptData.MetadataLength is 0, it is still technically undefined behaviour to call memcmp with a null bool isNullAndLengthZero = acceptData.Metadata == nullptr && acceptData.MetadataLength == 0; if (!isNullAndLengthZero) { // Only check that metadata buffers match. The OutputEvent can still be inspected when this function returns to // parse the metadata and verify that it matches. EXPECT_EQ( 0, memcmp(acceptData.Metadata, outEvent.transferAcceptData.Metadata, outEvent.transferAcceptData.MetadataLength)); } } // Verify that MaxBlockSize was set appropriately EXPECT_LE(acceptReceiver.GetTransferBlockSize(), initData.MaxBlockSize); } void SendAndVerifyRejectMsg(TransferSession::OutputEvent & outEvent, TransferSession & rejectSender, StatusCode reason, TransferSession & rejectReceiver) { CHIP_ERROR err = rejectSender.RejectTransfer(reason); EXPECT_EQ(err, CHIP_NO_ERROR); // Verify Sender emits status message for sending rejectSender.PollOutput(outEvent, kNoAdvanceTime); VerifyNoMoreOutput(rejectSender); EXPECT_EQ(outEvent.EventType, TransferSession::OutputEventType::kMsgToSend); System::PacketBufferHandle statusReportMsg = outEvent.MsgData.Retain(); VerifyStatusReport(std::move(outEvent.MsgData), reason); // Pass status message to rejectReceiver err = AttachHeaderAndSend(outEvent.msgTypeData, std::move(outEvent.MsgData), rejectReceiver); EXPECT_EQ(err, CHIP_NO_ERROR); // Verify received status message. rejectReceiver.PollOutput(outEvent, kNoAdvanceTime); VerifyInternalError(rejectReceiver); EXPECT_EQ(outEvent.EventType, TransferSession::OutputEventType::kStatusReceived); EXPECT_EQ(outEvent.statusData.statusCode, reason); } // Helper method for preparing a sending a BlockQuery message between two TransferSession objects. void SendAndVerifyQuery(TransferSession & queryReceiver, TransferSession & querySender, TransferSession::OutputEvent & outEvent) { // Verify that querySender emits BlockQuery message CHIP_ERROR err = querySender.PrepareBlockQuery(); EXPECT_EQ(err, CHIP_NO_ERROR); querySender.PollOutput(outEvent, kNoAdvanceTime); EXPECT_EQ(outEvent.EventType, TransferSession::OutputEventType::kMsgToSend); VerifyBdxMessageToSend(outEvent, MessageType::BlockQuery); VerifyNoMoreOutput(querySender); // Pass BlockQuery to queryReceiver and verify queryReceiver emits QueryReceived event err = AttachHeaderAndSend(outEvent.msgTypeData, std::move(outEvent.MsgData), queryReceiver); EXPECT_EQ(err, CHIP_NO_ERROR); queryReceiver.PollOutput(outEvent, kNoAdvanceTime); EXPECT_EQ(outEvent.EventType, TransferSession::OutputEventType::kQueryReceived); VerifyNoMoreOutput(queryReceiver); } // Helper method for preparing a sending a Block message between two TransferSession objects. The sender refers to the node that is // sending Blocks. Uses a static counter incremented with each call. Also verifies that block data received matches what was sent. void SendAndVerifyArbitraryBlock(TransferSession & sender, TransferSession & receiver, TransferSession::OutputEvent & outEvent, bool isEof, uint32_t inBlockCounter) { CHIP_ERROR err = CHIP_NO_ERROR; static uint8_t dataCount = 0; uint16_t maxBlockSize = sender.GetTransferBlockSize(); EXPECT_GT(maxBlockSize, 0); System::PacketBufferHandle fakeDataBuf = System::PacketBufferHandle::New(maxBlockSize); ASSERT_FALSE(fakeDataBuf.IsNull()); uint8_t * fakeBlockData = fakeDataBuf->Start(); fakeBlockData[0] = dataCount++; TransferSession::BlockData blockData; blockData.Data = fakeBlockData; blockData.Length = maxBlockSize; blockData.IsEof = isEof; MessageType expected = isEof ? MessageType::BlockEOF : MessageType::Block; // Provide Block data and verify sender emits Block message err = sender.PrepareBlock(blockData); EXPECT_EQ(err, CHIP_NO_ERROR); sender.PollOutput(outEvent, kNoAdvanceTime); EXPECT_EQ(outEvent.EventType, TransferSession::OutputEventType::kMsgToSend); VerifyBdxMessageToSend(outEvent, expected); VerifyNoMoreOutput(sender); // Pass Block message to receiver and verify matching Block is received err = AttachHeaderAndSend(outEvent.msgTypeData, std::move(outEvent.MsgData), receiver); EXPECT_EQ(err, CHIP_NO_ERROR); receiver.PollOutput(outEvent, kNoAdvanceTime); EXPECT_EQ(outEvent.EventType, TransferSession::OutputEventType::kBlockReceived); EXPECT_NE(outEvent.blockdata.Data, nullptr); if (outEvent.EventType == TransferSession::OutputEventType::kBlockReceived && outEvent.blockdata.Data != nullptr) { EXPECT_EQ(0, memcmp(fakeBlockData, outEvent.blockdata.Data, outEvent.blockdata.Length)); EXPECT_EQ(outEvent.blockdata.BlockCounter, inBlockCounter); } VerifyNoMoreOutput(receiver); } // Helper method for sending a BlockAck or BlockAckEOF, depending on the state of the receiver. void SendAndVerifyBlockAck(TransferSession & ackReceiver, TransferSession & ackSender, TransferSession::OutputEvent & outEvent, bool expectEOF) { TransferSession::OutputEventType expectedEventType = expectEOF ? TransferSession::OutputEventType::kAckEOFReceived : TransferSession::OutputEventType::kAckReceived; MessageType expectedMsgType = expectEOF ? MessageType::BlockAckEOF : MessageType::BlockAck; // Verify PrepareBlockAck() outputs message to send CHIP_ERROR err = ackSender.PrepareBlockAck(); EXPECT_EQ(err, CHIP_NO_ERROR); ackSender.PollOutput(outEvent, kNoAdvanceTime); EXPECT_EQ(outEvent.EventType, TransferSession::OutputEventType::kMsgToSend); VerifyBdxMessageToSend(outEvent, expectedMsgType); VerifyNoMoreOutput(ackSender); // Pass BlockAck to ackReceiver and verify it was received err = AttachHeaderAndSend(outEvent.msgTypeData, std::move(outEvent.MsgData), ackReceiver); EXPECT_EQ(err, CHIP_NO_ERROR); ackReceiver.PollOutput(outEvent, kNoAdvanceTime); EXPECT_EQ(outEvent.EventType, expectedEventType); VerifyNoMoreOutput(ackReceiver); } struct TestBdxTransferSession : public ::testing::Test { static void SetUpTestSuite() { EXPECT_EQ(chip::Platform::MemoryInit(), CHIP_NO_ERROR); } static void TearDownTestSuite() { chip::Platform::MemoryShutdown(); } }; // Test a full transfer using a responding receiver and an initiating sender, receiver drive. TEST_F(TestBdxTransferSession, TestInitiatingReceiverReceiverDrive) { CHIP_ERROR err = CHIP_NO_ERROR; TransferSession::OutputEvent outEvent; TransferSession initiatingReceiver; TransferSession respondingSender; uint32_t numBlocksSent = 0; // Chosen arbitrarily for this test uint32_t numBlockSends = 10; uint16_t proposedBlockSize = 128; uint16_t testSmallerBlockSize = 64; uint64_t proposedOffset = 64; uint64_t proposedLength = 0; System::Clock::Timeout timeout = System::Clock::Seconds16(24); // Chosen specifically for this test TransferControlFlags driveMode = TransferControlFlags::kReceiverDrive; // ReceiveInit parameters TransferSession::TransferInitData initOptions; initOptions.TransferCtlFlags = driveMode; initOptions.MaxBlockSize = proposedBlockSize; char testFileDes[9] = { "test.txt" }; initOptions.FileDesLength = static_cast(strlen(testFileDes)); initOptions.FileDesignator = reinterpret_cast(testFileDes); // Initialize respondingSender and pass ReceiveInit message BitFlags senderOpts; senderOpts.Set(driveMode); SendAndVerifyTransferInit(outEvent, timeout, initiatingReceiver, TransferRole::kReceiver, initOptions, respondingSender, senderOpts, proposedBlockSize); // Test metadata for Accept message uint8_t tlvBuf[64] = { 0 }; char metadataStr[11] = { "hi_dad.txt" }; uint32_t bytesWritten = 0; err = WriteTLVString(tlvBuf, sizeof(tlvBuf), metadataStr, bytesWritten); EXPECT_EQ(err, CHIP_NO_ERROR); uint16_t metadataSize = static_cast(bytesWritten & 0x0000FFFF); // Compose ReceiveAccept parameters struct and give to respondingSender TransferSession::TransferAcceptData acceptData; acceptData.ControlMode = respondingSender.GetControlMode(); acceptData.StartOffset = proposedOffset; acceptData.Length = proposedLength; acceptData.MaxBlockSize = testSmallerBlockSize; acceptData.Metadata = tlvBuf; acceptData.MetadataLength = metadataSize; SendAndVerifyAcceptMsg(outEvent, respondingSender, TransferRole::kSender, acceptData, initiatingReceiver, initOptions); // Verify that MaxBlockSize was chosen correctly EXPECT_EQ(respondingSender.GetTransferBlockSize(), testSmallerBlockSize); EXPECT_EQ(respondingSender.GetTransferBlockSize(), initiatingReceiver.GetTransferBlockSize()); // Verify parsed TLV metadata matches the original err = ReadAndVerifyTLVString(outEvent.transferAcceptData.Metadata, static_cast(outEvent.transferAcceptData.MetadataLength), metadataStr, static_cast(strlen(metadataStr))); EXPECT_EQ(err, CHIP_NO_ERROR); // Test BlockQuery -> Block -> BlockAck SendAndVerifyQuery(respondingSender, initiatingReceiver, outEvent); SendAndVerifyArbitraryBlock(respondingSender, initiatingReceiver, outEvent, false, numBlocksSent); numBlocksSent++; // Test only one block can be prepared at a time, without receiving a response to the first System::PacketBufferHandle fakeBuf = System::PacketBufferHandle::New(testSmallerBlockSize); TransferSession::BlockData prematureBlock; ASSERT_FALSE(fakeBuf.IsNull()); prematureBlock.Data = fakeBuf->Start(); prematureBlock.Length = testSmallerBlockSize; prematureBlock.IsEof = false; err = respondingSender.PrepareBlock(prematureBlock); EXPECT_NE(err, CHIP_NO_ERROR); VerifyNoMoreOutput(respondingSender); // Test Ack -> Query -> Block SendAndVerifyBlockAck(respondingSender, initiatingReceiver, outEvent, false); // Test multiple Blocks sent and received (last Block is BlockEOF) while (numBlocksSent < numBlockSends) { bool isEof = (numBlocksSent == numBlockSends - 1); SendAndVerifyQuery(respondingSender, initiatingReceiver, outEvent); SendAndVerifyArbitraryBlock(respondingSender, initiatingReceiver, outEvent, isEof, numBlocksSent); numBlocksSent++; } // Verify last block was BlockEOF, then verify response BlockAckEOF message EXPECT_TRUE(outEvent.blockdata.IsEof); SendAndVerifyBlockAck(respondingSender, initiatingReceiver, outEvent, true); } // Partial transfer test using Sender Drive to specifically test Block -> BlockAck -> Block sequence TEST_F(TestBdxTransferSession, TestInitiatingSenderSenderDrive) { CHIP_ERROR err = CHIP_NO_ERROR; TransferSession::OutputEvent outEvent; TransferSession initiatingSender; TransferSession respondingReceiver; TransferControlFlags driveMode = TransferControlFlags::kSenderDrive; // Chosen arbitrarily for this test uint16_t transferBlockSize = 10; System::Clock::Timeout timeout = System::Clock::Seconds16(24); // Initialize respondingReceiver BitFlags receiverOpts; receiverOpts.Set(driveMode); // Test metadata for TransferInit message uint8_t tlvBuf[64] = { 0 }; char metadataStr[11] = { "hi_dad.txt" }; uint32_t bytesWritten = 0; err = WriteTLVString(tlvBuf, sizeof(tlvBuf), metadataStr, bytesWritten); EXPECT_EQ(err, CHIP_NO_ERROR); uint16_t metadataSize = static_cast(bytesWritten & 0x0000FFFF); // Initialize struct with TransferInit parameters TransferSession::TransferInitData initOptions; initOptions.TransferCtlFlags = driveMode; initOptions.MaxBlockSize = transferBlockSize; char testFileDes[9] = { "test.txt" }; initOptions.FileDesLength = static_cast(strlen(testFileDes)); initOptions.FileDesignator = reinterpret_cast(testFileDes); initOptions.Metadata = tlvBuf; initOptions.MetadataLength = metadataSize; SendAndVerifyTransferInit(outEvent, timeout, initiatingSender, TransferRole::kSender, initOptions, respondingReceiver, receiverOpts, transferBlockSize); // Verify parsed TLV metadata matches the original err = ReadAndVerifyTLVString(outEvent.transferInitData.Metadata, static_cast(outEvent.transferInitData.MetadataLength), metadataStr, static_cast(strlen(metadataStr))); EXPECT_EQ(err, CHIP_NO_ERROR); // Compose SendAccept parameters struct and give to respondingSender uint16_t proposedBlockSize = transferBlockSize; TransferSession::TransferAcceptData acceptData; acceptData.ControlMode = respondingReceiver.GetControlMode(); acceptData.MaxBlockSize = proposedBlockSize; acceptData.StartOffset = 0; // not used in SendAccept acceptData.Length = 0; // not used in SendAccept acceptData.Metadata = nullptr; acceptData.MetadataLength = 0; SendAndVerifyAcceptMsg(outEvent, respondingReceiver, TransferRole::kReceiver, acceptData, initiatingSender, initOptions); uint32_t numBlocksSent = 0; // Test multiple Block -> BlockAck -> Block for (int i = 0; i < 3; i++) { SendAndVerifyArbitraryBlock(initiatingSender, respondingReceiver, outEvent, false, numBlocksSent); SendAndVerifyBlockAck(initiatingSender, respondingReceiver, outEvent, false); numBlocksSent++; } SendAndVerifyArbitraryBlock(initiatingSender, respondingReceiver, outEvent, true, numBlocksSent); SendAndVerifyBlockAck(initiatingSender, respondingReceiver, outEvent, true); } // Test that calls to AcceptTransfer() with bad parameters result in an error. TEST_F(TestBdxTransferSession, TestBadAcceptMessageFields) { CHIP_ERROR err = CHIP_NO_ERROR; TransferSession::OutputEvent outEvent; TransferSession initiatingReceiver; TransferSession respondingSender; uint16_t maxBlockSize = 64; TransferControlFlags driveMode = TransferControlFlags::kReceiverDrive; uint64_t commonLength = 0; uint64_t commonOffset = 0; System::Clock::Timeout timeout = System::Clock::Seconds16(24); // Initialize struct with TransferInit parameters TransferSession::TransferInitData initOptions; initOptions.TransferCtlFlags = driveMode; initOptions.MaxBlockSize = maxBlockSize; initOptions.StartOffset = commonOffset; initOptions.Length = commonLength; char testFileDes[9] = { "test.txt" }; // arbitrary file designator initOptions.FileDesLength = static_cast(strlen(testFileDes)); initOptions.FileDesignator = reinterpret_cast(testFileDes); initOptions.Metadata = nullptr; initOptions.MetadataLength = 0; // Responder parameters BitFlags responderControl; responderControl.Set(driveMode); SendAndVerifyTransferInit(outEvent, timeout, initiatingReceiver, TransferRole::kReceiver, initOptions, respondingSender, responderControl, maxBlockSize); // Verify AcceptTransfer() returns error for choosing larger max block size TransferSession::TransferAcceptData acceptData; acceptData.ControlMode = driveMode; acceptData.MaxBlockSize = static_cast(maxBlockSize + 1); // invalid if larger than proposed acceptData.StartOffset = commonOffset; acceptData.Length = commonLength; err = respondingSender.AcceptTransfer(acceptData); EXPECT_NE(err, CHIP_NO_ERROR); // Verify AcceptTransfer() returns error for choosing unsupported transfer control mode TransferSession::TransferAcceptData acceptData2; acceptData2.ControlMode = (driveMode == TransferControlFlags::kReceiverDrive) ? TransferControlFlags::kSenderDrive : TransferControlFlags::kReceiverDrive; acceptData2.MaxBlockSize = maxBlockSize; acceptData2.StartOffset = commonOffset; acceptData2.Length = commonLength; err = respondingSender.AcceptTransfer(acceptData2); EXPECT_NE(err, CHIP_NO_ERROR); } // Test that a TransferSession will emit kTransferTimeout if the specified timeout is exceeded while waiting for a response. TEST_F(TestBdxTransferSession, TestTimeout) { CHIP_ERROR err = CHIP_NO_ERROR; TransferSession initiator; TransferSession::OutputEvent outEvent; System::Clock::Timeout timeout = System::Clock::Milliseconds32(24); System::Clock::Timestamp startTime = System::Clock::Milliseconds64(100); System::Clock::Timestamp endTime = System::Clock::Milliseconds64(124); // Initialize struct with arbitrary TransferInit parameters TransferSession::TransferInitData initOptions; initOptions.TransferCtlFlags = TransferControlFlags::kReceiverDrive; initOptions.MaxBlockSize = 64; initOptions.StartOffset = 0; initOptions.Length = 0; char testFileDes[9] = { "test.txt" }; // arbitrary file designator initOptions.FileDesLength = static_cast(strlen(testFileDes)); initOptions.FileDesignator = reinterpret_cast(testFileDes); initOptions.Metadata = nullptr; initOptions.MetadataLength = 0; TransferRole role = TransferRole::kReceiver; // Verify initiator outputs respective Init message (depending on role) after StartTransfer() err = initiator.StartTransfer(role, initOptions, timeout); EXPECT_EQ(err, CHIP_NO_ERROR); // First PollOutput() should output the TransferInit message initiator.PollOutput(outEvent, startTime); EXPECT_EQ(outEvent.EventType, TransferSession::OutputEventType::kMsgToSend); MessageType expectedInitMsg = (role == TransferRole::kSender) ? MessageType::SendInit : MessageType::ReceiveInit; VerifyBdxMessageToSend(outEvent, expectedInitMsg); // Second PollOutput() with no call to HandleMessageReceived() should result in a timeout. initiator.PollOutput(outEvent, endTime); EXPECT_EQ(outEvent.EventType, TransferSession::OutputEventType::kTransferTimeout); } // Test that sending the same block twice (with same block counter) results in a StatusReport message with BadBlockCounter. Also // test that receiving the StatusReport ends the transfer on the other node. TEST_F(TestBdxTransferSession, TestDuplicateBlockError) { CHIP_ERROR err = CHIP_NO_ERROR; TransferSession::OutputEvent outEvent; TransferSession::OutputEvent eventWithBlock; TransferSession initiatingReceiver; TransferSession respondingSender; uint8_t fakeData[64] = { 0 }; uint8_t fakeDataLen = sizeof(fakeData); uint16_t blockSize = sizeof(fakeData); // Chosen arbitrarily for this test uint64_t proposedOffset = 64; uint64_t proposedLength = 0; System::Clock::Timeout timeout = System::Clock::Seconds16(24); // Chosen specifically for this test TransferControlFlags driveMode = TransferControlFlags::kReceiverDrive; // ReceiveInit parameters TransferSession::TransferInitData initOptions; initOptions.TransferCtlFlags = driveMode; initOptions.MaxBlockSize = blockSize; char testFileDes[9] = { "test.txt" }; initOptions.FileDesLength = static_cast(strlen(testFileDes)); initOptions.FileDesignator = reinterpret_cast(testFileDes); // Initialize respondingSender and pass ReceiveInit message BitFlags senderOpts; senderOpts.Set(driveMode); SendAndVerifyTransferInit(outEvent, timeout, initiatingReceiver, TransferRole::kReceiver, initOptions, respondingSender, senderOpts, blockSize); // Compose ReceiveAccept parameters struct and give to respondingSender TransferSession::TransferAcceptData acceptData; acceptData.ControlMode = respondingSender.GetControlMode(); acceptData.StartOffset = proposedOffset; acceptData.Length = proposedLength; acceptData.MaxBlockSize = blockSize; acceptData.Metadata = nullptr; acceptData.MetadataLength = 0; SendAndVerifyAcceptMsg(outEvent, respondingSender, TransferRole::kSender, acceptData, initiatingReceiver, initOptions); SendAndVerifyQuery(respondingSender, initiatingReceiver, outEvent); TransferSession::BlockData blockData; blockData.Data = fakeData; blockData.Length = fakeDataLen; blockData.IsEof = false; // Provide Block data and verify sender emits Block message err = respondingSender.PrepareBlock(blockData); EXPECT_EQ(err, CHIP_NO_ERROR); respondingSender.PollOutput(eventWithBlock, kNoAdvanceTime); EXPECT_EQ(eventWithBlock.EventType, TransferSession::OutputEventType::kMsgToSend); VerifyBdxMessageToSend(eventWithBlock, MessageType::Block); VerifyNoMoreOutput(respondingSender); System::PacketBufferHandle blockCopy = System::PacketBufferHandle::NewWithData(eventWithBlock.MsgData->Start(), eventWithBlock.MsgData->DataLength()); // Pass Block message to receiver and verify matching Block is received err = AttachHeaderAndSend(eventWithBlock.msgTypeData, std::move(eventWithBlock.MsgData), initiatingReceiver); EXPECT_EQ(err, CHIP_NO_ERROR); initiatingReceiver.PollOutput(outEvent, kNoAdvanceTime); EXPECT_EQ(outEvent.EventType, TransferSession::OutputEventType::kBlockReceived); EXPECT_NE(outEvent.blockdata.Data, nullptr); VerifyNoMoreOutput(initiatingReceiver); SendAndVerifyQuery(respondingSender, initiatingReceiver, outEvent); // Verify receiving same Block twice fails and results in StatusReport event, and then InternalError event err = AttachHeaderAndSend(eventWithBlock.msgTypeData, std::move(blockCopy), initiatingReceiver); EXPECT_EQ(err, CHIP_NO_ERROR); initiatingReceiver.PollOutput(outEvent, kNoAdvanceTime); EXPECT_EQ(outEvent.EventType, TransferSession::OutputEventType::kMsgToSend); System::PacketBufferHandle statusReportMsg = outEvent.MsgData.Retain(); TransferSession::MessageTypeData statusReportMsgTypeData = outEvent.msgTypeData; VerifyStatusReport(std::move(outEvent.MsgData), StatusCode::kBadBlockCounter); // All subsequent PollOutput() calls should return kInternalError for (int i = 0; i < 5; ++i) { initiatingReceiver.PollOutput(outEvent, kNoAdvanceTime); EXPECT_EQ(outEvent.EventType, TransferSession::OutputEventType::kInternalError); EXPECT_EQ(outEvent.statusData.statusCode, StatusCode::kBadBlockCounter); } err = AttachHeaderAndSend(statusReportMsgTypeData, std::move(statusReportMsg), respondingSender); EXPECT_EQ(err, CHIP_NO_ERROR); respondingSender.PollOutput(outEvent, kNoAdvanceTime); EXPECT_EQ(outEvent.EventType, TransferSession::OutputEventType::kStatusReceived); EXPECT_EQ(outEvent.statusData.statusCode, StatusCode::kBadBlockCounter); // All subsequent PollOutput() calls should return kInternalError for (int i = 0; i < 5; ++i) { respondingSender.PollOutput(outEvent, kNoAdvanceTime); EXPECT_EQ(outEvent.EventType, TransferSession::OutputEventType::kInternalError); EXPECT_EQ(outEvent.statusData.statusCode, StatusCode::kBadBlockCounter); } } TEST_F(TestBdxTransferSession, TestRejectTransfer) { TransferSession::OutputEvent outEvent; TransferSession initiatingReceiver; TransferSession respondingSender; // Chosen arbitrarily for this test uint16_t proposedBlockSize = 128; System::Clock::Timeout timeout = System::Clock::Seconds16(24); TransferControlFlags driveMode = TransferControlFlags::kReceiverDrive; // ReceiveInit parameters TransferSession::TransferInitData initOptions; initOptions.TransferCtlFlags = driveMode; initOptions.MaxBlockSize = proposedBlockSize; char testFileDes[9] = { "test.txt" }; initOptions.FileDesLength = static_cast(strlen(testFileDes)); initOptions.FileDesignator = reinterpret_cast(testFileDes); // Initialize respondingSender and pass ReceiveInit message BitFlags senderOpts; senderOpts.Set(driveMode); SendAndVerifyTransferInit(outEvent, timeout, initiatingReceiver, TransferRole::kReceiver, initOptions, respondingSender, senderOpts, proposedBlockSize); // Reject the transfer with a status SendAndVerifyRejectMsg(outEvent, respondingSender, StatusCode::kResponderBusy, initiatingReceiver); }