1 #include <protocols/Protocols.h>
2 #include <protocols/bdx/BdxMessages.h>
3 #include <protocols/bdx/BdxTransferSession.h>
7 #include <nlunit-test.h>
9 #include <core/CHIPTLV.h>
10 #include <protocols/secure_channel/Constants.h>
11 #include <protocols/secure_channel/StatusReport.h>
12 #include <support/BufferReader.h>
13 #include <support/CHIPMem.h>
14 #include <support/CodeUtils.h>
15 #include <support/UnitTestRegistration.h>
16 #include <system/SystemPacketBuffer.h>
18 using namespace ::chip;
19 using namespace ::chip::bdx;
20 using namespace ::chip::Protocols;
23 // Use this as a timestamp if not needing to test BDX timeouts.
24 constexpr uint64_t kNoAdvanceTime = 0;
26 const uint64_t tlvStrTag = TLV::ContextTag(4);
27 const uint64_t tlvListTag = TLV::ProfileTag(7777, 8888);
28 } // anonymous namespace
30 // Helper method for generating a complete TLV structure with a list containing a single tag and string
31 CHIP_ERROR WriteChipTLVString(uint8_t * buf, uint32_t bufLen, const char * data, uint32_t & written)
33 CHIP_ERROR err = CHIP_NO_ERROR;
35 TLV::TLVWriter writer;
36 writer.Init(buf, bufLen);
39 TLV::TLVWriter listWriter;
40 err = writer.OpenContainer(tlvListTag, TLV::kTLVType_List, listWriter);
42 err = listWriter.PutString(tlvStrTag, data);
44 err = writer.CloseContainer(listWriter);
48 err = writer.Finalize();
50 written = writer.GetLengthWritten();
56 // Helper method: read a TLV structure with a single tag and string and verify it matches expected string.
57 CHIP_ERROR ReadAndVerifyTLVString(nlTestSuite * inSuite, void * inContext, const uint8_t * dataStart, uint32_t len,
58 const char * expected, uint16_t expectedLen)
60 CHIP_ERROR err = CHIP_NO_ERROR;
61 TLV::TLVReader reader;
63 uint32_t readLength = 0;
64 VerifyOrExit(sizeof(tmp) > len, err = CHIP_ERROR_INTERNAL);
66 reader.Init(dataStart, len);
69 VerifyOrExit(reader.GetTag() == tlvListTag, err = CHIP_ERROR_INTERNAL);
71 // Metadata must have a top-level list
73 TLV::TLVReader listReader;
74 err = reader.OpenContainer(listReader);
77 err = listReader.Next();
80 VerifyOrExit(listReader.GetTag() == tlvStrTag, err = CHIP_ERROR_INTERNAL);
81 readLength = listReader.GetLength();
82 VerifyOrExit(readLength == expectedLen, err = CHIP_ERROR_INTERNAL);
83 err = listReader.GetString(tmp, sizeof(tmp));
85 VerifyOrExit(!memcmp(expected, tmp, readLength), err = CHIP_ERROR_INTERNAL);
87 err = reader.CloseContainer(listReader);
95 // Helper method for verifying that a PacketBufferHandle contains a valid BDX header and message type matches expected.
96 void VerifyBdxMessageType(nlTestSuite * inSuite, void * inContext, const System::PacketBufferHandle & msg, MessageType expected)
98 CHIP_ERROR err = CHIP_NO_ERROR;
99 uint16_t headerSize = 0;
100 PayloadHeader payloadHeader;
104 NL_TEST_ASSERT(inSuite, false);
108 err = payloadHeader.Decode(msg->Start(), msg->DataLength(), &headerSize);
109 NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
110 NL_TEST_ASSERT(inSuite, payloadHeader.HasMessageType(expected));
113 // Helper method for verifying that a PacketBufferHandle contains a valid StatusReport message and contains a specific StatusCode.
114 void VerifyStatusReport(nlTestSuite * inSuite, void * inContext, const System::PacketBufferHandle & msg, StatusCode code)
116 CHIP_ERROR err = CHIP_NO_ERROR;
117 PayloadHeader payloadHeader;
121 NL_TEST_ASSERT(inSuite, false);
125 System::PacketBufferHandle msgCopy = msg.CloneData();
126 if (msgCopy.IsNull())
128 NL_TEST_ASSERT(inSuite, false);
132 err = payloadHeader.DecodeAndConsume(msgCopy);
133 NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
134 NL_TEST_ASSERT(inSuite, payloadHeader.HasMessageType(SecureChannel::MsgType::StatusReport));
136 SecureChannel::StatusReport report;
137 err = report.Parse(std::move(msgCopy));
138 NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
139 NL_TEST_ASSERT(inSuite, report.GetGeneralCode() == SecureChannel::GeneralStatusCode::kFailure);
140 NL_TEST_ASSERT(inSuite, report.GetProtocolId() == Protocols::BDX::Id.ToFullyQualifiedSpecForm());
141 NL_TEST_ASSERT(inSuite, report.GetProtocolCode() == static_cast<uint16_t>(code));
144 void VerifyNoMoreOutput(nlTestSuite * inSuite, void * inContext, TransferSession & transferSession)
146 TransferSession::OutputEvent event;
147 transferSession.PollOutput(event, kNoAdvanceTime);
148 NL_TEST_ASSERT(inSuite, event.EventType == TransferSession::OutputEventType::kNone);
151 // Helper method for initializing two TransferSession objects, generating a TransferInit message, and passing it to a responding
153 void SendAndVerifyTransferInit(nlTestSuite * inSuite, void * inContext, TransferSession::OutputEvent & outEvent, uint32_t timeoutMs,
154 TransferSession & initiator, TransferRole initiatorRole, TransferSession::TransferInitData initData,
155 TransferSession & responder, BitFlags<TransferControlFlags> & responderControlOpts,
156 uint16_t responderMaxBlock)
158 CHIP_ERROR err = CHIP_NO_ERROR;
159 TransferRole responderRole = (initiatorRole == TransferRole::kSender) ? TransferRole::kReceiver : TransferRole::kSender;
160 MessageType expectedInitMsg = (initiatorRole == TransferRole::kSender) ? MessageType::SendInit : MessageType::ReceiveInit;
162 // Initializer responder to wait for transfer
163 err = responder.WaitForTransfer(responderRole, responderControlOpts, responderMaxBlock, timeoutMs);
164 NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
165 VerifyNoMoreOutput(inSuite, inContext, responder);
167 // Verify initiator outputs respective Init message (depending on role) after StartTransfer()
168 err = initiator.StartTransfer(initiatorRole, initData, timeoutMs);
169 NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
170 initiator.PollOutput(outEvent, kNoAdvanceTime);
171 NL_TEST_ASSERT(inSuite, outEvent.EventType == TransferSession::OutputEventType::kMsgToSend);
172 VerifyBdxMessageType(inSuite, inContext, outEvent.MsgData, expectedInitMsg);
173 VerifyNoMoreOutput(inSuite, inContext, initiator);
175 // Verify that all parsed TransferInit fields match what was sent by the initiator
176 err = responder.HandleMessageReceived(std::move(outEvent.MsgData), kNoAdvanceTime);
177 NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
178 responder.PollOutput(outEvent, kNoAdvanceTime);
179 VerifyNoMoreOutput(inSuite, inContext, responder);
180 NL_TEST_ASSERT(inSuite, outEvent.EventType == TransferSession::OutputEventType::kInitReceived);
181 NL_TEST_ASSERT(inSuite, outEvent.transferInitData.TransferCtlFlags == initData.TransferCtlFlags);
182 NL_TEST_ASSERT(inSuite, outEvent.transferInitData.MaxBlockSize == initData.MaxBlockSize);
183 NL_TEST_ASSERT(inSuite, outEvent.transferInitData.StartOffset == initData.StartOffset);
184 NL_TEST_ASSERT(inSuite, outEvent.transferInitData.Length == initData.Length);
185 NL_TEST_ASSERT(inSuite, outEvent.transferInitData.FileDesignator != nullptr);
186 NL_TEST_ASSERT(inSuite, outEvent.transferInitData.FileDesLength == initData.FileDesLength);
187 if (outEvent.EventType == TransferSession::OutputEventType::kInitReceived &&
188 outEvent.transferInitData.FileDesignator != nullptr)
192 !memcmp(initData.FileDesignator, outEvent.transferInitData.FileDesignator, outEvent.transferInitData.FileDesLength));
194 if (outEvent.transferInitData.Metadata != nullptr)
196 NL_TEST_ASSERT(inSuite, outEvent.transferInitData.MetadataLength == initData.MetadataLength);
197 if (outEvent.transferInitData.MetadataLength == initData.MetadataLength)
199 // Only check that metadata buffers match. The OutputEvent can still be inspected when this function returns to parse
200 // the metadata and verify that it matches.
202 inSuite, !memcmp(initData.Metadata, outEvent.transferInitData.Metadata, outEvent.transferInitData.MetadataLength));
206 NL_TEST_ASSERT(inSuite, false); // Metadata length mismatch
211 // Helper method for sending an Accept message and verifying that the received parameters match what was sent.
212 // This function assumes that the acceptData struct contains transfer parameters that are valid responses to the original
213 // TransferInit message (for example, MaxBlockSize should be <= the TransferInit MaxBlockSize). If such parameters are invalid, the
214 // receiver should emit a StatusCode event instead.
216 // The acceptSender is the node that is sending the Accept message (not necessarily the same node that will send Blocks).
217 void SendAndVerifyAcceptMsg(nlTestSuite * inSuite, void * inContext, TransferSession::OutputEvent & outEvent,
218 TransferSession & acceptSender, TransferRole acceptSenderRole,
219 TransferSession::TransferAcceptData acceptData, TransferSession & acceptReceiver,
220 TransferSession::TransferInitData initData)
222 CHIP_ERROR err = CHIP_NO_ERROR;
224 // If the node sending the Accept message is also the one that will send Blocks, then this should be a ReceiveAccept message.
225 MessageType expectedMsg = (acceptSenderRole == TransferRole::kSender) ? MessageType::ReceiveAccept : MessageType::SendAccept;
227 err = acceptSender.AcceptTransfer(acceptData);
228 NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
230 // Verify Sender emits ReceiveAccept message for sending
231 acceptSender.PollOutput(outEvent, kNoAdvanceTime);
232 VerifyNoMoreOutput(inSuite, inContext, acceptSender);
233 NL_TEST_ASSERT(inSuite, outEvent.EventType == TransferSession::OutputEventType::kMsgToSend);
234 VerifyBdxMessageType(inSuite, inContext, outEvent.MsgData, expectedMsg);
236 // Pass Accept message to acceptReceiver
237 err = acceptReceiver.HandleMessageReceived(std::move(outEvent.MsgData), kNoAdvanceTime);
238 NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
240 // Verify received ReceiveAccept.
241 // Client may want to inspect TransferControl, MaxBlockSize, StartOffset, Length, and Metadata, and may choose to reject the
242 // Transfer at this point.
243 acceptReceiver.PollOutput(outEvent, kNoAdvanceTime);
244 VerifyNoMoreOutput(inSuite, inContext, acceptReceiver);
245 NL_TEST_ASSERT(inSuite, outEvent.EventType == TransferSession::OutputEventType::kAcceptReceived);
246 NL_TEST_ASSERT(inSuite, outEvent.transferAcceptData.ControlMode == acceptData.ControlMode);
247 NL_TEST_ASSERT(inSuite, outEvent.transferAcceptData.MaxBlockSize == acceptData.MaxBlockSize);
248 NL_TEST_ASSERT(inSuite, outEvent.transferAcceptData.StartOffset == acceptData.StartOffset);
249 NL_TEST_ASSERT(inSuite, outEvent.transferAcceptData.Length == acceptData.Length);
250 if (outEvent.transferAcceptData.Metadata != nullptr)
252 NL_TEST_ASSERT(inSuite, outEvent.transferAcceptData.MetadataLength == acceptData.MetadataLength);
253 if (outEvent.transferAcceptData.MetadataLength == acceptData.MetadataLength)
255 // Only check that metadata buffers match. The OutputEvent can still be inspected when this function returns to parse
256 // the metadata and verify that it matches.
259 !memcmp(acceptData.Metadata, outEvent.transferAcceptData.Metadata, outEvent.transferAcceptData.MetadataLength));
263 NL_TEST_ASSERT(inSuite, false); // Metadata length mismatch
267 // Verify that MaxBlockSize was set appropriately
268 NL_TEST_ASSERT(inSuite, acceptReceiver.GetTransferBlockSize() <= initData.MaxBlockSize);
271 // Helper method for preparing a sending a BlockQuery message between two TransferSession objects.
272 void SendAndVerifyQuery(nlTestSuite * inSuite, void * inContext, TransferSession & queryReceiver, TransferSession & querySender,
273 TransferSession::OutputEvent & outEvent)
275 // Verify that querySender emits BlockQuery message
276 CHIP_ERROR err = querySender.PrepareBlockQuery();
277 NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
278 querySender.PollOutput(outEvent, kNoAdvanceTime);
279 NL_TEST_ASSERT(inSuite, outEvent.EventType == TransferSession::OutputEventType::kMsgToSend);
280 VerifyBdxMessageType(inSuite, inContext, outEvent.MsgData, MessageType::BlockQuery);
281 VerifyNoMoreOutput(inSuite, inContext, querySender);
283 // Pass BlockQuery to queryReceiver and verify queryReceiver emits QueryReceived event
284 err = queryReceiver.HandleMessageReceived(std::move(outEvent.MsgData), kNoAdvanceTime);
285 NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
286 queryReceiver.PollOutput(outEvent, kNoAdvanceTime);
287 NL_TEST_ASSERT(inSuite, outEvent.EventType == TransferSession::OutputEventType::kQueryReceived);
288 VerifyNoMoreOutput(inSuite, inContext, queryReceiver);
291 // Helper method for preparing a sending a Block message between two TransferSession objects. The sender refers to the node that is
292 // sending Blocks. Uses a static counter incremented with each call. Also verifies that block data received matches what was sent.
293 void SendAndVerifyArbitraryBlock(nlTestSuite * inSuite, void * inContext, TransferSession & sender, TransferSession & receiver,
294 TransferSession::OutputEvent & outEvent, bool isEof)
296 CHIP_ERROR err = CHIP_NO_ERROR;
297 static uint8_t dataCount = 0;
298 uint16_t maxBlockSize = sender.GetTransferBlockSize();
300 NL_TEST_ASSERT(inSuite, maxBlockSize > 0);
301 System::PacketBufferHandle fakeDataBuf = System::PacketBufferHandle::New(maxBlockSize);
302 if (fakeDataBuf.IsNull())
304 NL_TEST_ASSERT(inSuite, false);
308 uint8_t * fakeBlockData = fakeDataBuf->Start();
309 fakeBlockData[0] = dataCount++;
311 TransferSession::BlockData blockData;
312 blockData.Data = fakeBlockData;
313 blockData.Length = maxBlockSize;
314 blockData.IsEof = isEof;
316 MessageType expected = isEof ? MessageType::BlockEOF : MessageType::Block;
318 // Provide Block data and verify sender emits Block message
319 err = sender.PrepareBlock(blockData);
320 NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
321 sender.PollOutput(outEvent, kNoAdvanceTime);
322 NL_TEST_ASSERT(inSuite, outEvent.EventType == TransferSession::OutputEventType::kMsgToSend);
323 VerifyBdxMessageType(inSuite, inContext, outEvent.MsgData, expected);
324 VerifyNoMoreOutput(inSuite, inContext, sender);
326 // Pass Block message to receiver and verify matching Block is received
327 err = receiver.HandleMessageReceived(std::move(outEvent.MsgData), kNoAdvanceTime);
328 NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
329 receiver.PollOutput(outEvent, kNoAdvanceTime);
330 NL_TEST_ASSERT(inSuite, outEvent.EventType == TransferSession::OutputEventType::kBlockReceived);
331 NL_TEST_ASSERT(inSuite, outEvent.blockdata.Data != nullptr);
332 if (outEvent.EventType == TransferSession::OutputEventType::kBlockReceived && outEvent.blockdata.Data != nullptr)
334 NL_TEST_ASSERT(inSuite, !memcmp(fakeBlockData, outEvent.blockdata.Data, outEvent.blockdata.Length));
336 VerifyNoMoreOutput(inSuite, inContext, receiver);
339 // Helper method for sending a BlockAck or BlockAckEOF, depending on the state of the receiver.
340 void SendAndVerifyBlockAck(nlTestSuite * inSuite, void * inContext, TransferSession & ackReceiver, TransferSession & ackSender,
341 TransferSession::OutputEvent & outEvent, bool expectEOF)
343 TransferSession::OutputEventType expectedEventType =
344 expectEOF ? TransferSession::OutputEventType::kAckEOFReceived : TransferSession::OutputEventType::kAckReceived;
345 MessageType expectedMsgType = expectEOF ? MessageType::BlockAckEOF : MessageType::BlockAck;
347 // Verify PrepareBlockAck() outputs message to send
348 CHIP_ERROR err = ackSender.PrepareBlockAck();
349 NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
350 ackSender.PollOutput(outEvent, kNoAdvanceTime);
351 NL_TEST_ASSERT(inSuite, outEvent.EventType == TransferSession::OutputEventType::kMsgToSend);
352 VerifyBdxMessageType(inSuite, inContext, outEvent.MsgData, expectedMsgType);
353 VerifyNoMoreOutput(inSuite, inContext, ackSender);
355 // Pass BlockAck to ackReceiver and verify it was received
356 err = ackReceiver.HandleMessageReceived(std::move(outEvent.MsgData), kNoAdvanceTime);
357 NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
358 ackReceiver.PollOutput(outEvent, kNoAdvanceTime);
359 NL_TEST_ASSERT(inSuite, outEvent.EventType == expectedEventType);
360 VerifyNoMoreOutput(inSuite, inContext, ackReceiver);
363 // Test a full transfer using a responding receiver and an initiating sender, receiver drive.
364 void TestInitiatingReceiverReceiverDrive(nlTestSuite * inSuite, void * inContext)
366 CHIP_ERROR err = CHIP_NO_ERROR;
367 TransferSession::OutputEvent outEvent;
368 TransferSession initiatingReceiver;
369 TransferSession respondingSender;
370 uint32_t numBlocksSent = 0;
372 // Chosen arbitrarily for this test
373 uint32_t numBlockSends = 10;
374 uint16_t proposedBlockSize = 128;
375 uint16_t testSmallerBlockSize = 64;
376 uint64_t proposedOffset = 64;
377 uint64_t proposedLength = 0;
378 uint32_t timeoutMs = 1000 * 24;
380 // Chosen specifically for this test
381 TransferControlFlags driveMode = TransferControlFlags::kReceiverDrive;
383 // ReceiveInit parameters
384 TransferSession::TransferInitData initOptions;
385 initOptions.TransferCtlFlags = driveMode;
386 initOptions.MaxBlockSize = proposedBlockSize;
387 char testFileDes[9] = { "test.txt" };
388 initOptions.FileDesLength = static_cast<uint16_t>(strlen(testFileDes));
389 initOptions.FileDesignator = reinterpret_cast<uint8_t *>(testFileDes);
391 // Initialize respondingSender and pass ReceiveInit message
392 BitFlags<TransferControlFlags> senderOpts;
393 senderOpts.Set(driveMode);
395 SendAndVerifyTransferInit(inSuite, inContext, outEvent, timeoutMs, initiatingReceiver, TransferRole::kReceiver, initOptions,
396 respondingSender, senderOpts, proposedBlockSize);
398 // Test metadata for Accept message
399 uint8_t tlvBuf[64] = { 0 };
400 char metadataStr[11] = { "hi_dad.txt" };
401 uint32_t bytesWritten = 0;
402 err = WriteChipTLVString(tlvBuf, sizeof(tlvBuf), metadataStr, bytesWritten);
403 NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
404 uint16_t metadataSize = static_cast<uint16_t>(bytesWritten & 0x0000FFFF);
406 // Compose ReceiveAccept parameters struct and give to respondingSender
407 TransferSession::TransferAcceptData acceptData;
408 acceptData.ControlMode = respondingSender.GetControlMode();
409 acceptData.StartOffset = proposedOffset;
410 acceptData.Length = proposedLength;
411 acceptData.MaxBlockSize = testSmallerBlockSize;
412 acceptData.Metadata = tlvBuf;
413 acceptData.MetadataLength = metadataSize;
415 SendAndVerifyAcceptMsg(inSuite, inContext, outEvent, respondingSender, TransferRole::kSender, acceptData, initiatingReceiver,
418 // Verify that MaxBlockSize was chosen correctly
419 NL_TEST_ASSERT(inSuite, respondingSender.GetTransferBlockSize() == testSmallerBlockSize);
420 NL_TEST_ASSERT(inSuite, respondingSender.GetTransferBlockSize() == initiatingReceiver.GetTransferBlockSize());
422 // Verify parsed TLV metadata matches the original
424 ReadAndVerifyTLVString(inSuite, inContext, outEvent.transferAcceptData.Metadata, outEvent.transferAcceptData.MetadataLength,
425 metadataStr, static_cast<uint16_t>(strlen(metadataStr)));
426 NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
428 // Test BlockQuery -> Block -> BlockAck
429 SendAndVerifyQuery(inSuite, inContext, respondingSender, initiatingReceiver, outEvent);
430 SendAndVerifyArbitraryBlock(inSuite, inContext, respondingSender, initiatingReceiver, outEvent, false);
433 // Test only one block can be prepared at a time, without receiving a response to the first
434 System::PacketBufferHandle fakeBuf = System::PacketBufferHandle::New(testSmallerBlockSize);
435 TransferSession::BlockData prematureBlock;
436 if (fakeBuf.IsNull())
438 NL_TEST_ASSERT(inSuite, false);
441 prematureBlock.Data = fakeBuf->Start();
442 prematureBlock.Length = testSmallerBlockSize;
443 prematureBlock.IsEof = false;
444 err = respondingSender.PrepareBlock(prematureBlock);
445 NL_TEST_ASSERT(inSuite, err != CHIP_NO_ERROR);
446 VerifyNoMoreOutput(inSuite, inContext, respondingSender);
448 // Test Ack -> Query -> Block
449 SendAndVerifyBlockAck(inSuite, inContext, respondingSender, initiatingReceiver, outEvent, false);
451 // Test multiple Blocks sent and received (last Block is BlockEOF)
452 while (numBlocksSent < numBlockSends)
454 bool isEof = (numBlocksSent == numBlockSends - 1);
456 SendAndVerifyQuery(inSuite, inContext, respondingSender, initiatingReceiver, outEvent);
457 SendAndVerifyArbitraryBlock(inSuite, inContext, respondingSender, initiatingReceiver, outEvent, isEof);
462 // Verify last block was BlockEOF, then verify response BlockAckEOF message
463 NL_TEST_ASSERT(inSuite, outEvent.blockdata.IsEof == true);
464 SendAndVerifyBlockAck(inSuite, inContext, respondingSender, initiatingReceiver, outEvent, true);
467 // Partial transfer test using Sender Drive to specifically test Block -> BlockAck -> Block sequence
468 void TestInitiatingSenderSenderDrive(nlTestSuite * inSuite, void * inContext)
470 CHIP_ERROR err = CHIP_NO_ERROR;
471 TransferSession::OutputEvent outEvent;
472 TransferSession initiatingSender;
473 TransferSession respondingReceiver;
475 TransferControlFlags driveMode = TransferControlFlags::kSenderDrive;
477 // Chosen arbitrarily for this test
478 uint16_t transferBlockSize = 10;
479 uint32_t timeoutMs = 1000 * 24;
481 // Initialize respondingReceiver
482 BitFlags<TransferControlFlags> receiverOpts;
483 receiverOpts.Set(driveMode);
485 // Test metadata for TransferInit message
486 uint8_t tlvBuf[64] = { 0 };
487 char metadataStr[11] = { "hi_dad.txt" };
488 uint32_t bytesWritten = 0;
489 err = WriteChipTLVString(tlvBuf, sizeof(tlvBuf), metadataStr, bytesWritten);
490 NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
491 uint16_t metadataSize = static_cast<uint16_t>(bytesWritten & 0x0000FFFF);
493 // Initialize struct with TransferInit parameters
494 TransferSession::TransferInitData initOptions;
495 initOptions.TransferCtlFlags = driveMode;
496 initOptions.MaxBlockSize = transferBlockSize;
497 char testFileDes[9] = { "test.txt" };
498 initOptions.FileDesLength = static_cast<uint16_t>(strlen(testFileDes));
499 initOptions.FileDesignator = reinterpret_cast<uint8_t *>(testFileDes);
500 initOptions.Metadata = tlvBuf;
501 initOptions.MetadataLength = metadataSize;
503 SendAndVerifyTransferInit(inSuite, inContext, outEvent, timeoutMs, initiatingSender, TransferRole::kSender, initOptions,
504 respondingReceiver, receiverOpts, transferBlockSize);
506 // Verify parsed TLV metadata matches the original
507 err = ReadAndVerifyTLVString(inSuite, inContext, outEvent.transferInitData.Metadata, outEvent.transferInitData.MetadataLength,
508 metadataStr, static_cast<uint16_t>(strlen(metadataStr)));
509 NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
511 // Compose SendAccept parameters struct and give to respondingSender
512 uint16_t proposedBlockSize = transferBlockSize;
513 TransferSession::TransferAcceptData acceptData;
514 acceptData.ControlMode = respondingReceiver.GetControlMode();
515 acceptData.MaxBlockSize = proposedBlockSize;
516 acceptData.StartOffset = 0; // not used in SendAccept
517 acceptData.Length = 0; // not used in SendAccept
518 acceptData.Metadata = nullptr;
519 acceptData.MetadataLength = 0;
521 SendAndVerifyAcceptMsg(inSuite, inContext, outEvent, respondingReceiver, TransferRole::kReceiver, acceptData, initiatingSender,
524 // Test multiple Block -> BlockAck -> Block
525 for (int i = 0; i < 3; i++)
527 SendAndVerifyArbitraryBlock(inSuite, inContext, initiatingSender, respondingReceiver, outEvent, false);
528 SendAndVerifyBlockAck(inSuite, inContext, initiatingSender, respondingReceiver, outEvent, false);
531 SendAndVerifyArbitraryBlock(inSuite, inContext, initiatingSender, respondingReceiver, outEvent, true);
532 SendAndVerifyBlockAck(inSuite, inContext, initiatingSender, respondingReceiver, outEvent, true);
535 // Test that calls to AcceptTransfer() with bad parameters result in an error.
536 void TestBadAcceptMessageFields(nlTestSuite * inSuite, void * inContext)
538 CHIP_ERROR err = CHIP_NO_ERROR;
539 TransferSession::OutputEvent outEvent;
540 TransferSession initiatingReceiver;
541 TransferSession respondingSender;
543 uint16_t maxBlockSize = 64;
544 TransferControlFlags driveMode = TransferControlFlags::kReceiverDrive;
545 uint64_t commonLength = 0;
546 uint64_t commonOffset = 0;
547 uint32_t timeoutMs = 1000 * 24;
549 // Initialize struct with TransferInit parameters
550 TransferSession::TransferInitData initOptions;
551 initOptions.TransferCtlFlags = driveMode;
552 initOptions.MaxBlockSize = maxBlockSize;
553 initOptions.StartOffset = commonOffset;
554 initOptions.Length = commonLength;
555 char testFileDes[9] = { "test.txt" }; // arbitrary file designator
556 initOptions.FileDesLength = static_cast<uint16_t>(strlen(testFileDes));
557 initOptions.FileDesignator = reinterpret_cast<uint8_t *>(testFileDes);
558 initOptions.Metadata = nullptr;
559 initOptions.MetadataLength = 0;
561 // Responder parameters
562 BitFlags<TransferControlFlags> responderControl;
563 responderControl.Set(driveMode);
565 SendAndVerifyTransferInit(inSuite, inContext, outEvent, timeoutMs, initiatingReceiver, TransferRole::kReceiver, initOptions,
566 respondingSender, responderControl, maxBlockSize);
568 // Verify AcceptTransfer() returns error for choosing larger max block size
569 TransferSession::TransferAcceptData acceptData;
570 acceptData.ControlMode = driveMode;
571 acceptData.MaxBlockSize = static_cast<uint16_t>(maxBlockSize + 1); // invalid if larger than proposed
572 acceptData.StartOffset = commonOffset;
573 acceptData.Length = commonLength;
574 err = respondingSender.AcceptTransfer(acceptData);
575 NL_TEST_ASSERT(inSuite, err != CHIP_NO_ERROR);
577 // Verify AcceptTransfer() returns error for choosing unsupported transfer control mode
578 TransferSession::TransferAcceptData acceptData2;
579 acceptData2.ControlMode = (driveMode == TransferControlFlags::kReceiverDrive) ? TransferControlFlags::kSenderDrive
580 : TransferControlFlags::kReceiverDrive;
581 acceptData2.MaxBlockSize = maxBlockSize;
582 acceptData2.StartOffset = commonOffset;
583 acceptData2.Length = commonLength;
584 err = respondingSender.AcceptTransfer(acceptData2);
585 NL_TEST_ASSERT(inSuite, err != CHIP_NO_ERROR);
588 // Test that a TransferSession will emit kTransferTimeout if the specified timeout is exceeded while waiting for a response.
589 void TestTimeout(nlTestSuite * inSuite, void * inContext)
591 CHIP_ERROR err = CHIP_NO_ERROR;
592 TransferSession initiator;
593 TransferSession::OutputEvent outEvent;
595 uint32_t timeoutMs = 24;
596 uint64_t startTimeMs = 100;
597 uint64_t endTimeMs = 124;
599 // Initialize struct with arbitrary TransferInit parameters
600 TransferSession::TransferInitData initOptions;
601 initOptions.TransferCtlFlags = TransferControlFlags::kReceiverDrive;
602 initOptions.MaxBlockSize = 64;
603 initOptions.StartOffset = 0;
604 initOptions.Length = 0;
605 char testFileDes[9] = { "test.txt" }; // arbitrary file designator
606 initOptions.FileDesLength = static_cast<uint16_t>(strlen(testFileDes));
607 initOptions.FileDesignator = reinterpret_cast<uint8_t *>(testFileDes);
608 initOptions.Metadata = nullptr;
609 initOptions.MetadataLength = 0;
611 TransferRole role = TransferRole::kReceiver;
613 // Verify initiator outputs respective Init message (depending on role) after StartTransfer()
614 err = initiator.StartTransfer(role, initOptions, timeoutMs);
615 NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
617 // First PollOutput() should output the TransferInit message
618 initiator.PollOutput(outEvent, startTimeMs);
619 NL_TEST_ASSERT(inSuite, outEvent.EventType == TransferSession::OutputEventType::kMsgToSend);
620 MessageType expectedInitMsg = (role == TransferRole::kSender) ? MessageType::SendInit : MessageType::ReceiveInit;
621 VerifyBdxMessageType(inSuite, inContext, outEvent.MsgData, expectedInitMsg);
623 // Second PollOutput() with no call to HandleMessageReceived() should result in a timeout.
624 initiator.PollOutput(outEvent, endTimeMs);
625 NL_TEST_ASSERT(inSuite, outEvent.EventType == TransferSession::OutputEventType::kTransferTimeout);
628 // Test that sending the same block twice (with same block counter) results in a StatusReport message with BadBlockCounter. Also
629 // test that receiving the StatusReport ends the transfer on the other node.
630 void TestDuplicateBlockError(nlTestSuite * inSuite, void * inContext)
632 CHIP_ERROR err = CHIP_NO_ERROR;
633 TransferSession::OutputEvent outEvent;
634 TransferSession::OutputEvent eventWithBlock;
635 TransferSession initiatingReceiver;
636 TransferSession respondingSender;
638 uint8_t fakeData[64] = { 0 };
639 uint8_t fakeDataLen = sizeof(fakeData);
640 uint16_t blockSize = sizeof(fakeData);
642 // Chosen arbitrarily for this test
643 uint64_t proposedOffset = 64;
644 uint64_t proposedLength = 0;
645 uint32_t timeoutMs = 1000 * 24;
647 // Chosen specifically for this test
648 TransferControlFlags driveMode = TransferControlFlags::kReceiverDrive;
650 // ReceiveInit parameters
651 TransferSession::TransferInitData initOptions;
652 initOptions.TransferCtlFlags = driveMode;
653 initOptions.MaxBlockSize = blockSize;
654 char testFileDes[9] = { "test.txt" };
655 initOptions.FileDesLength = static_cast<uint16_t>(strlen(testFileDes));
656 initOptions.FileDesignator = reinterpret_cast<uint8_t *>(testFileDes);
658 // Initialize respondingSender and pass ReceiveInit message
659 BitFlags<TransferControlFlags> senderOpts;
660 senderOpts.Set(driveMode);
662 SendAndVerifyTransferInit(inSuite, inContext, outEvent, timeoutMs, initiatingReceiver, TransferRole::kReceiver, initOptions,
663 respondingSender, senderOpts, blockSize);
665 // Compose ReceiveAccept parameters struct and give to respondingSender
666 TransferSession::TransferAcceptData acceptData;
667 acceptData.ControlMode = respondingSender.GetControlMode();
668 acceptData.StartOffset = proposedOffset;
669 acceptData.Length = proposedLength;
670 acceptData.MaxBlockSize = blockSize;
671 acceptData.Metadata = nullptr;
672 acceptData.MetadataLength = 0;
674 SendAndVerifyAcceptMsg(inSuite, inContext, outEvent, respondingSender, TransferRole::kSender, acceptData, initiatingReceiver,
677 SendAndVerifyQuery(inSuite, inContext, respondingSender, initiatingReceiver, outEvent);
679 TransferSession::BlockData blockData;
680 blockData.Data = fakeData;
681 blockData.Length = fakeDataLen;
682 blockData.IsEof = false;
684 // Provide Block data and verify sender emits Block message
685 err = respondingSender.PrepareBlock(blockData);
686 NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
687 respondingSender.PollOutput(eventWithBlock, kNoAdvanceTime);
688 NL_TEST_ASSERT(inSuite, eventWithBlock.EventType == TransferSession::OutputEventType::kMsgToSend);
689 VerifyBdxMessageType(inSuite, inContext, eventWithBlock.MsgData, MessageType::Block);
690 VerifyNoMoreOutput(inSuite, inContext, respondingSender);
691 System::PacketBufferHandle blockCopy =
692 System::PacketBufferHandle::NewWithData(eventWithBlock.MsgData->Start(), eventWithBlock.MsgData->DataLength());
694 // Pass Block message to receiver and verify matching Block is received
695 err = initiatingReceiver.HandleMessageReceived(std::move(eventWithBlock.MsgData), kNoAdvanceTime);
696 NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
697 initiatingReceiver.PollOutput(outEvent, kNoAdvanceTime);
698 NL_TEST_ASSERT(inSuite, outEvent.EventType == TransferSession::OutputEventType::kBlockReceived);
699 NL_TEST_ASSERT(inSuite, outEvent.blockdata.Data != nullptr);
700 VerifyNoMoreOutput(inSuite, inContext, initiatingReceiver);
702 SendAndVerifyQuery(inSuite, inContext, respondingSender, initiatingReceiver, outEvent);
704 // Verify receiving same Block twice fails and results in StatusReport event, and then InternalError event
705 err = initiatingReceiver.HandleMessageReceived(std::move(blockCopy), kNoAdvanceTime);
706 NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
707 initiatingReceiver.PollOutput(outEvent, kNoAdvanceTime);
708 NL_TEST_ASSERT(inSuite, outEvent.EventType == TransferSession::OutputEventType::kMsgToSend);
709 System::PacketBufferHandle statusReportMsg = outEvent.MsgData.Retain();
710 VerifyStatusReport(inSuite, inContext, std::move(outEvent.MsgData), StatusCode::kBadBlockCounter);
712 // All subsequent PollOutput() calls should return kInternalError
713 for (int i = 0; i < 5; ++i)
715 initiatingReceiver.PollOutput(outEvent, kNoAdvanceTime);
716 NL_TEST_ASSERT(inSuite, outEvent.EventType == TransferSession::OutputEventType::kInternalError);
717 NL_TEST_ASSERT(inSuite, outEvent.statusData.statusCode == StatusCode::kBadBlockCounter);
720 err = respondingSender.HandleMessageReceived(std::move(statusReportMsg), kNoAdvanceTime);
721 NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
722 respondingSender.PollOutput(outEvent, kNoAdvanceTime);
723 NL_TEST_ASSERT(inSuite, outEvent.EventType == TransferSession::OutputEventType::kStatusReceived);
724 NL_TEST_ASSERT(inSuite, outEvent.statusData.statusCode == StatusCode::kBadBlockCounter);
726 // All subsequent PollOutput() calls should return kInternalError
727 for (int i = 0; i < 5; ++i)
729 respondingSender.PollOutput(outEvent, kNoAdvanceTime);
730 NL_TEST_ASSERT(inSuite, outEvent.EventType == TransferSession::OutputEventType::kInternalError);
731 NL_TEST_ASSERT(inSuite, outEvent.statusData.statusCode == StatusCode::kBadBlockCounter);
738 * Test Suite that lists all the test functions.
741 static const nlTest sTests[] =
743 NL_TEST_DEF("TestInitiatingReceiverReceiverDrive", TestInitiatingReceiverReceiverDrive),
744 NL_TEST_DEF("TestInitiatingSenderSenderDrive", TestInitiatingSenderSenderDrive),
745 NL_TEST_DEF("TestBadAcceptMessageFields", TestBadAcceptMessageFields),
746 NL_TEST_DEF("TestTimeout", TestTimeout),
747 NL_TEST_DEF("TestDuplicateBlockError", TestDuplicateBlockError),
752 int TestBdxTransferSession_Setup(void * inContext)
754 CHIP_ERROR error = chip::Platform::MemoryInit();
755 if (error != CHIP_NO_ERROR)
760 int TestBdxTransferSession_Teardown(void * inContext)
762 chip::Platform::MemoryShutdown();
767 static nlTestSuite sSuite =
769 "Test-CHIP-TransferSession",
771 TestBdxTransferSession_Setup,
772 TestBdxTransferSession_Teardown
779 int TestBdxTransferSession()
781 // Run test suit against one context
782 nlTestRunner(&sSuite, nullptr);
784 return (nlTestRunnerStats(&sSuite));
787 CHIP_REGISTER_TEST_SUITE(TestBdxTransferSession)