BDX TransferSession spec implementation (#4473)
* First commit for BDX message utilities
- establish directory for BDX
- add BDXMessageUtils files
- add unit tests for BDXMessageUtils
- edit BUILD.gn files to build BDX directory and tests
* First commit for BDX message utilities
- establish directory for BDX
- add BDXMessageUtils files
- add unit tests for BDXMessageUtils
- edit BUILD.gn files to build BDX directory and tests
* move bdx directory to src/protocols
* fix BufferReader usage, style mistakes
* remove transport/bdx and fix build files
* remove unused header
* adding State files to BDX directory
* First commit for BDX message utilities
- establish directory for BDX
- add BDXMessageUtils files
- add unit tests for BDXMessageUtils
- edit BUILD.gn files to build BDX directory and tests
* move bdx directory to src/protocols
* fix BufferReader usage, style mistakes
* First commit for BDX message utilities
- establish directory for BDX
- add BDXMessageUtils files
- add unit tests for BDXMessageUtils
- edit BUILD.gn files to build BDX directory and tests
* remove transport/bdx and fix build files
* remove unused header
* adding State files to BDX directory
* WIP: integrating BdxMessages back into this branch
* Redesign BdxTransferSession API around StartTransfer, WaitForTransfer
* Delete old files BDXState + BDXMessageUtils
* Implementation for StartTransfer and unit test
* write full exchange test up to first BlockQuery
- add test logic for exchange up to first BlockQuery message (failing
here)
- fix virtual destructor errors
- add helper method for writing BDX messages to packetbuffer
* add methods for verifying transfer control options
also add delegate methods for file descriptor and metadata
* Full overhaul of TransferSession using EmitOutput()
also includes two tests for receiver drive and sender drive
* rename MessageType enum values
* remove reference to kProtocol_StatusReport and add TODO
* remove accidental change to src/BUILD.gn
* fix accidental third party repo changes
* restyling
* fix struct initialization compiler errors
* QEMU compilation fix
* initialize anonymous union to fix -Werror=uninitialized
* replace std::move() calls with Retain(), fix OutputEvent initialization
* remove brace-enclosed initializer lists
* replace Retain() with std::move() when possible
* Address comments from Tennessee:
- lowercase bdx namespace
- remove PrepareBlockAckEOF and combine in PrepareBlockAck
- add test helper for verifying output message type
- change test metadata to TLV format
* add checks for invalid AcceptTransfer data and add tests
* fix build after merge
* add check for transfer timeout, add timestamps to API, add timeout test
* fix build: add SystemPacketBuffer.h include
* change NewWithAvailableSize() -> New()
* add helper test function VerifyNoMoreOutput()
* fix block counter tracking, add test conditions for counters
- also add test helper SendAndVerifyTransferInit
* change OutputEventFlags (bitfield) to OutputEventType (flat enum)
* Update src/protocols/bdx/BdxTransferSession.h
Co-authored-by: Tennessee Carmel-Veilleux <tennessee.carmelveilleux@gmail.com>
* use strlen instead of sizeof
* use and enforce metadata with top-level list (path)
* add BdxMessage struct interface for common method calls
* change kTLVType_Path to _List
* addressing many of Tennessee's comments:
- rename remove Output from kOutput_ enums
- add AvailableDataLength() check when writing message to buffer
- fix timeout check
- fix payloadHeader.Encode() call, use headerSize
* remove "State_" prefix from TransferStates enum
- also rename from Idle to Unititalized
* rename TransferStates -> TransferState
* restyling: whitespace
* Use PacketBufBound instead of PacketBufferHandle::New()
* Implement StatusReport handling and sending with tests
* implement AbortTransfer() and update comments related to StatusReport
* combine vendorID and profileID in StatusReport handling
Co-authored-by: Justin Wood <woody@apple.com>
Co-authored-by: Tennessee Carmel-Veilleux <tennessee.carmelveilleux@gmail.com>
diff --git a/src/protocols/bdx/BUILD.gn b/src/protocols/bdx/BUILD.gn
index ddc63fb..e8ca3f1 100644
--- a/src/protocols/bdx/BUILD.gn
+++ b/src/protocols/bdx/BUILD.gn
@@ -20,6 +20,8 @@
sources = [
"BdxMessages.cpp",
"BdxMessages.h",
+ "BdxTransferSession.cpp",
+ "BdxTransferSession.h",
]
cflags = [ "-Wconversion" ]
@@ -28,5 +30,6 @@
"${chip_root}/src/lib/core",
"${chip_root}/src/lib/support",
"${chip_root}/src/system",
+ "${chip_root}/src/transport/raw",
]
}
diff --git a/src/protocols/bdx/BdxMessages.cpp b/src/protocols/bdx/BdxMessages.cpp
index 7f6b7d6..8d00abb 100644
--- a/src/protocols/bdx/BdxMessages.cpp
+++ b/src/protocols/bdx/BdxMessages.cpp
@@ -35,12 +35,12 @@
} // namespace
using namespace chip;
-using namespace chip::BDX;
+using namespace chip::bdx;
using namespace chip::Encoding::LittleEndian;
// WARNING: this function should never return early, since MessageSize() relies on it to calculate
// the size of the message (even if the message is incomplete or filled out incorrectly).
-BufBound & TransferInit::WriteToBuffer(BufBound & aBuffer) const
+BufBound & TransferInit::DerivedWriteToBuffer(BufBound & aBuffer) const
{
uint8_t proposedTransferCtl = 0;
bool widerange = (StartOffset > std::numeric_limits<uint32_t>::max()) || (MaxLength > std::numeric_limits<uint32_t>::max());
@@ -49,9 +49,9 @@
proposedTransferCtl = proposedTransferCtl | TransferCtlOptions.Raw();
BitFlags<uint8_t, RangeControlFlags> rangeCtlFlags;
- rangeCtlFlags.Set(kDefLen, MaxLength > 0);
- rangeCtlFlags.Set(kStartOffset, StartOffset > 0);
- rangeCtlFlags.Set(kWiderange, widerange);
+ rangeCtlFlags.Set(kRange_DefLen, MaxLength > 0);
+ rangeCtlFlags.Set(kRange_StartOffset, StartOffset > 0);
+ rangeCtlFlags.Set(kRange_Widerange, widerange);
aBuffer.Put(proposedTransferCtl);
aBuffer.Put(rangeCtlFlags.Raw());
@@ -94,7 +94,7 @@
return aBuffer;
}
-CHIP_ERROR TransferInit::Parse(System::PacketBufferHandle aBuffer)
+CHIP_ERROR TransferInit::DerivedParse(System::PacketBufferHandle aBuffer)
{
CHIP_ERROR err = CHIP_NO_ERROR;
uint8_t proposedTransferCtl;
@@ -111,9 +111,9 @@
rangeCtlFlags.SetRaw(rangeCtl);
StartOffset = 0;
- if (rangeCtlFlags.Has(kStartOffset))
+ if (rangeCtlFlags.Has(kRange_StartOffset))
{
- if (rangeCtlFlags.Has(kWiderange))
+ if (rangeCtlFlags.Has(kRange_Widerange))
{
SuccessOrExit(bufReader.Read64(&StartOffset).StatusCode());
}
@@ -125,9 +125,9 @@
}
MaxLength = 0;
- if (rangeCtlFlags.Has(kDefLen))
+ if (rangeCtlFlags.Has(kRange_DefLen))
{
- if (rangeCtlFlags.Has(kWiderange))
+ if (rangeCtlFlags.Has(kRange_Widerange))
{
SuccessOrExit(bufReader.Read64(&MaxLength).StatusCode());
}
@@ -164,7 +164,7 @@
return err;
}
-size_t TransferInit::MessageSize() const
+size_t TransferInit::DerivedMessageSize() const
{
BufBound emptyBuf(nullptr, 0);
return WriteToBuffer(emptyBuf).Needed();
@@ -196,7 +196,7 @@
// WARNING: this function should never return early, since MessageSize() relies on it to calculate
// the size of the message (even if the message is incomplete or filled out incorrectly).
-BufBound & SendAccept::WriteToBuffer(BufBound & aBuffer) const
+BufBound & SendAccept::DerivedWriteToBuffer(BufBound & aBuffer) const
{
uint8_t transferCtl = 0;
@@ -213,7 +213,7 @@
return aBuffer;
}
-CHIP_ERROR SendAccept::Parse(System::PacketBufferHandle aBuffer)
+CHIP_ERROR SendAccept::DerivedParse(System::PacketBufferHandle aBuffer)
{
CHIP_ERROR err = CHIP_NO_ERROR;
uint8_t transferCtl = 0;
@@ -247,7 +247,7 @@
return err;
}
-size_t SendAccept::MessageSize() const
+size_t SendAccept::DerivedMessageSize() const
{
BufBound emptyBuf(nullptr, 0);
return WriteToBuffer(emptyBuf).Needed();
@@ -272,7 +272,7 @@
// WARNING: this function should never return early, since MessageSize() relies on it to calculate
// the size of the message (even if the message is incomplete or filled out incorrectly).
-BufBound & ReceiveAccept::WriteToBuffer(BufBound & aBuffer) const
+BufBound & ReceiveAccept::DerivedWriteToBuffer(BufBound & aBuffer) const
{
uint8_t transferCtl = 0;
bool widerange = (StartOffset > std::numeric_limits<uint32_t>::max()) || (Length > std::numeric_limits<uint32_t>::max());
@@ -281,9 +281,9 @@
transferCtl = transferCtl | TransferCtlFlags.Raw();
BitFlags<uint8_t, RangeControlFlags> rangeCtlFlags;
- rangeCtlFlags.Set(kDefLen, Length > 0);
- rangeCtlFlags.Set(kStartOffset, StartOffset > 0);
- rangeCtlFlags.Set(kWiderange, widerange);
+ rangeCtlFlags.Set(kRange_DefLen, Length > 0);
+ rangeCtlFlags.Set(kRange_StartOffset, StartOffset > 0);
+ rangeCtlFlags.Set(kRange_Widerange, widerange);
aBuffer.Put(transferCtl);
aBuffer.Put(rangeCtlFlags.Raw());
@@ -320,7 +320,7 @@
return aBuffer;
}
-CHIP_ERROR ReceiveAccept::Parse(System::PacketBufferHandle aBuffer)
+CHIP_ERROR ReceiveAccept::DerivedParse(System::PacketBufferHandle aBuffer)
{
CHIP_ERROR err = CHIP_NO_ERROR;
uint8_t transferCtl = 0;
@@ -340,9 +340,9 @@
rangeCtlFlags.SetRaw(rangeCtl);
StartOffset = 0;
- if (rangeCtlFlags.Has(kStartOffset))
+ if (rangeCtlFlags.Has(kRange_StartOffset))
{
- if (rangeCtlFlags.Has(kWiderange))
+ if (rangeCtlFlags.Has(kRange_Widerange))
{
SuccessOrExit(bufReader.Read64(&StartOffset).StatusCode());
}
@@ -354,9 +354,9 @@
}
Length = 0;
- if (rangeCtlFlags.Has(kDefLen))
+ if (rangeCtlFlags.Has(kRange_DefLen))
{
- if (rangeCtlFlags.Has(kWiderange))
+ if (rangeCtlFlags.Has(kRange_Widerange))
{
SuccessOrExit(bufReader.Read64(&Length).StatusCode());
}
@@ -387,7 +387,7 @@
return err;
}
-size_t ReceiveAccept::MessageSize() const
+size_t ReceiveAccept::DerivedMessageSize() const
{
BufBound emptyBuf(nullptr, 0);
return WriteToBuffer(emptyBuf).Needed();
@@ -413,19 +413,19 @@
// WARNING: this function should never return early, since MessageSize() relies on it to calculate
// the size of the message (even if the message is incomplete or filled out incorrectly).
-BufBound & CounterMessage::WriteToBuffer(BufBound & aBuffer) const
+BufBound & CounterMessage::DerivedWriteToBuffer(BufBound & aBuffer) const
{
return aBuffer.Put32(BlockCounter);
}
-CHIP_ERROR CounterMessage::Parse(System::PacketBufferHandle aBuffer)
+CHIP_ERROR CounterMessage::DerivedParse(System::PacketBufferHandle aBuffer)
{
uint8_t * bufStart = aBuffer->Start();
Reader bufReader(bufStart, aBuffer->DataLength());
return bufReader.Read32(&BlockCounter).StatusCode();
}
-size_t CounterMessage::MessageSize() const
+size_t CounterMessage::DerivedMessageSize() const
{
BufBound emptyBuf(nullptr, 0);
return WriteToBuffer(emptyBuf).Needed();
@@ -438,7 +438,7 @@
// WARNING: this function should never return early, since MessageSize() relies on it to calculate
// the size of the message (even if the message is incomplete or filled out incorrectly).
-BufBound & DataBlock::WriteToBuffer(BufBound & aBuffer) const
+BufBound & DataBlock::DerivedWriteToBuffer(BufBound & aBuffer) const
{
aBuffer.Put32(BlockCounter);
if (Data != nullptr)
@@ -448,7 +448,7 @@
return aBuffer;
}
-CHIP_ERROR DataBlock::Parse(System::PacketBufferHandle aBuffer)
+CHIP_ERROR DataBlock::DerivedParse(System::PacketBufferHandle aBuffer)
{
CHIP_ERROR err = CHIP_NO_ERROR;
uint8_t * bufStart = aBuffer->Start();
@@ -476,7 +476,7 @@
return err;
}
-size_t DataBlock::MessageSize() const
+size_t DataBlock::DerivedMessageSize() const
{
BufBound emptyBuf(nullptr, 0);
return WriteToBuffer(emptyBuf).Needed();
diff --git a/src/protocols/bdx/BdxMessages.h b/src/protocols/bdx/BdxMessages.h
index 061888b..79920e7 100644
--- a/src/protocols/bdx/BdxMessages.h
+++ b/src/protocols/bdx/BdxMessages.h
@@ -28,65 +28,111 @@
#include <support/BufBound.h>
#include <support/CodeUtils.h>
#include <system/SystemPacketBuffer.h>
-
namespace chip {
-namespace BDX {
+namespace bdx {
+
+enum MessageType : uint8_t
+{
+ kBdxMsg_SendInit = 0x01,
+ kBdxMsg_SendAccept = 0x02,
+ kBdxMsg_ReceiveInit = 0x04,
+ kBdxMsg_ReceiveAccept = 0x05,
+ kBdxMsg_BlockQuery = 0x10,
+ kBdxMsg_Block = 0x11,
+ kBdxMsg_BlockEOF = 0x12,
+ kBdxMsg_BlockAck = 0x13,
+ kBdxMsg_BlockAckEOF = 0x14,
+};
+
+enum StatusCode : uint16_t
+{
+ kStatus_None = 0x0000,
+ kStatus_Overflow = 0x0011,
+ kStatus_LengthTooLarge = 0x0012,
+ kStatus_LengthTooShort = 0x0013,
+ kStatus_LengthMismatch = 0x0014,
+ kStatus_LengthRequired = 0x0015,
+ kStatus_BadMessageContents = 0x0016,
+ kStatus_BadBlockCounter = 0x0017,
+ kStatus_TransferFailedUnknownError = 0x001F,
+ kStatus_ServerBadState = 0x0020,
+ kStatus_FailureToSend = 0x0021,
+ kStatus_TransferMethodNotSupported = 0x0050,
+ kStatus_FileDesignatorUnknown = 0x0051,
+ kStatus_StartOffsetNotSupported = 0x0052,
+ kStatus_VersionNotSupported = 0x0053,
+ kStatus_Unknown = 0x005F,
+};
enum TransferControlFlags : uint8_t
{
// first 4 bits reserved for version
- kSenderDrive = (1U << 4),
- kReceiverDrive = (1U << 5),
- kAsync = (1U << 6),
+ kControl_SenderDrive = (1U << 4),
+ kControl_ReceiverDrive = (1U << 5),
+ kControl_Async = (1U << 6),
};
enum RangeControlFlags : uint8_t
{
- kDefLen = (1U),
- kStartOffset = (1U << 1),
- kWiderange = (1U << 4),
+ kRange_DefLen = (1U),
+ kRange_StartOffset = (1U << 1),
+ kRange_Widerange = (1U << 4),
};
-/*
- * A structure for representing a SendInit or ReceiveInit message (both contain
- * identical parameters).
+/**
+ * @brief
+ * Interface for defining methods that apply to all BDX messages.
*/
-struct TransferInit
+struct BdxMessage
{
/**
* @brief
+ * Parse data from an PacketBuffer into a BdxMessage struct.
+ *
+ * Note that this may store pointers that point into the passed PacketBuffer,
+ * so it is essential that the underlying PacketBuffer is not modified until after this
+ * struct is no longer needed.
+ *
+ * @param[in] aBuffer A PacketBufferHandle with a refernce to the PacketBuffer containing the data.
+ *
+ * @return CHIP_ERROR Return an error if the message format is invalid and/or can't be parsed
+ */
+ CHECK_RETURN_VALUE
+ CHIP_ERROR Parse(System::PacketBufferHandle aBuffer) { return DerivedParse(std::move(aBuffer)); }
+
+ /**
+ * @brief
* Write the message fields to a buffer using the provided BufBound.
*
* It is up to the caller to use BufBound::Fit() to verify that the write was
* successful. This method will also not check for correctness or completeness for
* any of the fields - it is the caller's responsibility to ensure that the fields
- * have been filled out adequately.
+ * align with BDX specifications.
*
* @param aBuffer A BufBound object that will be used to write the message
*/
- BufBound & WriteToBuffer(BufBound & aBuffer) const;
+ BufBound & WriteToBuffer(BufBound & aBuffer) const { return DerivedWriteToBuffer(aBuffer); }
/**
* @brief
- * Parse data from an PacketBuffer into a struct instance.
- *
- * Note that this struct will store pointers that point into the passed PacketBuffer,
- * so it is essential that the PacketBuffer is not modified until after this
- * struct is no longer needed.
- *
- * @param[in] aBuffer Pointer to a PacketBuffer containing the data.
- *
- * @return CHIP_ERROR Any error that occurs when trying to read the message
+ * Returns the size of buffer needed to write the message.
*/
- CHECK_RETURN_VALUE
- CHIP_ERROR Parse(System::PacketBufferHandle aBuffer);
+ virtual size_t MessageSize() const { return DerivedMessageSize(); }
- /**
- * @brief
- * Returns the size of buffer needed to write this message.
- */
- size_t MessageSize() const;
+ virtual ~BdxMessage() = default;
+private:
+ virtual CHIP_ERROR DerivedParse(System::PacketBufferHandle aBuffer) = 0;
+ virtual BufBound & DerivedWriteToBuffer(BufBound & aBuffer) const = 0;
+ virtual size_t DerivedMessageSize() const = 0;
+};
+
+/*
+ * A structure for representing a SendInit or ReceiveInit message (both contain
+ * identical parameters).
+ */
+struct TransferInit : public BdxMessage
+{
/**
* @brief
* Equality check method.
@@ -102,16 +148,21 @@
uint64_t StartOffset = 0; ///< Proposed start offset of data. 0 for no offset
uint64_t MaxLength = 0; ///< Proposed max length of data in transfer, 0 for indefinite
- // File designator (required)
+ // File designator (required) and additional metadata (optional, TLV format)
+ // WARNING: there is no guarantee at any point that these pointers will point to valid memory. The Buffer field should be used
+ // to hold a reference to the PacketBuffer containing the data in order to ensure the data is not freed.
const uint8_t * FileDesignator = nullptr;
uint16_t FileDesLength = 0; ///< Length of file designator string (not including null-terminator)
-
- // Additional metadata (optional, TLV format)
- const uint8_t * Metadata = nullptr;
- uint16_t MetadataLength = 0;
+ const uint8_t * Metadata = nullptr;
+ uint16_t MetadataLength = 0;
// Retain ownership of the packet buffer so that the FileDesignator and Metadata pointers remain valid.
System::PacketBufferHandle Buffer;
+
+private:
+ CHIP_ERROR DerivedParse(System::PacketBufferHandle aBuffer) override;
+ BufBound & DerivedWriteToBuffer(BufBound & aBuffer) const override;
+ size_t DerivedMessageSize() const override;
};
using SendInit = TransferInit;
@@ -120,44 +171,10 @@
/*
* A structure for representing a SendAccept message.
*/
-struct SendAccept
+struct SendAccept : public BdxMessage
{
/**
* @brief
- * Write the message fields to a buffer using the provided BufBound.
- *
- * It is up to the caller to use BufBound::Fit() to verify that the write was
- * successful. This method will also not check for correctness or completeness for
- * any of the fields - it is the caller's responsibility to ensure that the fields
- * have been filled out adequately.
- *
- * @param aBuffer A BufBound object that will be used to write the message
- */
- BufBound & WriteToBuffer(BufBound & aBuffer) const;
-
- /**
- * @brief
- * Parse data from an PacketBuffer into a struct instance
- *
- * Note that this struct will store pointers that point into the passed PacketBuffer,
- * so it is essential that the PacketBuffer is not modified until after this
- * struct is no longer needed.
- *
- * @param[in] aBuffer Pointer to a PacketBuffer containing the data
- *
- * @return CHIP_ERROR Any error that occurs when trying to read the message
- */
- CHECK_RETURN_VALUE
- CHIP_ERROR Parse(System::PacketBufferHandle aBuffer);
-
- /**
- * @brief
- * Returns the size of buffer needed to write this message.
- */
- size_t MessageSize() const;
-
- /**
- * @brief
* Equality check method.
*/
bool operator==(const SendAccept &) const;
@@ -169,56 +186,27 @@
uint16_t MaxBlockSize = 0; ///< Chosen max block size to use in transfer (required)
// Additional metadata (optional, TLV format)
- // WARNING: there is no guarantee at any point that this pointer will point to valid memory.
- // It is up to the caller to ensure that the memory pointed to here has not been freed.
- uint8_t * Metadata = nullptr;
- uint16_t MetadataLength = 0;
+ // WARNING: there is no guarantee at any point that this pointer will point to valid memory. The Buffer field should be used to
+ // hold a reference to the PacketBuffer containing the data in order to ensure the data is not freed.
+ const uint8_t * Metadata = nullptr;
+ uint16_t MetadataLength = 0;
// Retain ownership of the packet buffer so that the FileDesignator and Metadata pointers remain valid.
System::PacketBufferHandle Buffer;
+
+private:
+ CHIP_ERROR DerivedParse(System::PacketBufferHandle aBuffer) override;
+ BufBound & DerivedWriteToBuffer(BufBound & aBuffer) const override;
+ size_t DerivedMessageSize() const override;
};
/**
* A structure for representing ReceiveAccept messages.
*/
-struct ReceiveAccept
+struct ReceiveAccept : public BdxMessage
{
/**
* @brief
- * Write the message fields to a buffer using the provided BufBound.
- *
- * It is up to the caller to use BufBound::Fit() to verify that the write was
- * successful. This method will also not check for correctness or completeness for
- * any of the fields - it is the caller's responsibility to ensure that the fields
- * have been filled out adequately.
- *
- * @param aBuffer A BufBound object that will be used to write the message
- */
- BufBound & WriteToBuffer(BufBound & aBuffer) const;
-
- /**
- * @brief
- * Parse data from an PacketBuffer into a struct instance
- *
- * Note that this struct will store pointers that point into the passed PacketBuffer,
- * so it is essential that the PacketBuffer is not modified until after this
- * struct is no longer needed.
- *
- * @param[in] aBuffer Pointer to a PacketBuffer containing the data
- *
- * @return CHIP_ERROR Any error that occurs when trying to read the message
- */
- CHECK_RETURN_VALUE
- CHIP_ERROR Parse(System::PacketBufferHandle aBuffer);
-
- /**
- * @brief
- * Returns the size of buffer needed to write this message.
- */
- size_t MessageSize() const;
-
- /**
- * @brief
* Equality check method.
*/
bool operator==(const ReceiveAccept &) const;
@@ -233,61 +221,38 @@
uint64_t Length = 0; ///< Length of transfer. 0 if length is indefinite.
// Additional metadata (optional, TLV format)
- // WARNING: there is no guarantee at any point that this pointer will point to valid memory.
- // It is up to the caller to ensure that the memory pointed to here has not been freed.
- uint8_t * Metadata = nullptr;
- uint16_t MetadataLength = 0;
+ // WARNING: there is no guarantee at any point that this pointer will point to valid memory. The Buffer field should be used to
+ // hold a reference to the PacketBuffer containing the data in order to ensure the data is not freed.
+ const uint8_t * Metadata = nullptr;
+ uint16_t MetadataLength = 0;
// Retain ownership of the packet buffer so that the FileDesignator and Metadata pointers remain valid.
System::PacketBufferHandle Buffer;
+
+private:
+ CHIP_ERROR DerivedParse(System::PacketBufferHandle aBuffer) override;
+ BufBound & DerivedWriteToBuffer(BufBound & aBuffer) const override;
+ size_t DerivedMessageSize() const override;
};
/**
* A struct for representing messages contiaining just a counter field. Can be used to
* represent BlockQuery, BlockAck, and BlockAckEOF.
*/
-struct CounterMessage
+struct CounterMessage : public BdxMessage
{
/**
* @brief
- * Write the message fields to a buffer using the provided BufBound.
- *
- * It is up to the caller to use BufBound::Fit() to verify that the write was
- * successful. This method will also not check for correctness or completeness for
- * any of the fields - it is the caller's responsibility to ensure that the fields
- * have been filled out adequately.
- *
- * @param aBuffer A BufBound object that will be used to write the message
- */
- BufBound & WriteToBuffer(BufBound & aBuffer) const;
-
- /**
- * @brief
- * Parse data from an PacketBuffer into a struct instance
- *
- * @param[in] aBuffer Pointer to a PacketBuffer containing the data
- *
- * @return CHIP_ERROR Any error that occurs when trying to read the message
- */
- CHECK_RETURN_VALUE
- CHIP_ERROR Parse(System::PacketBufferHandle aBuffer);
-
- /**
- * @brief
- * Returns the size of buffer needed to write this message.
- */
- size_t MessageSize() const;
-
- /**
- * @brief
* Equality check method.
*/
bool operator==(const CounterMessage &) const;
uint32_t BlockCounter = 0;
- // Retain ownership of the packet buffer so that the FileDesignator and Metadata pointers remain valid.
- System::PacketBufferHandle Buffer;
+private:
+ CHIP_ERROR DerivedParse(System::PacketBufferHandle aBuffer) override;
+ BufBound & DerivedWriteToBuffer(BufBound & aBuffer) const override;
+ size_t DerivedMessageSize() const override;
};
using BlockQuery = CounterMessage;
@@ -297,61 +262,32 @@
/**
* A struct that represents a message containing actual data (Block, BlockEOF).
*/
-struct DataBlock
+struct DataBlock : public BdxMessage
{
/**
* @brief
- * Write the message fields to a buffer using the provided BufBound.
- *
- * It is up to the caller to use BufBound::Fit() to verify that the write was
- * successful. This method will also not check for correctness or completeness for
- * any of the fields - it is the caller's responsibility to ensure that the fields
- * have been filled out adequately.
- *
- * @param aBuffer A BufBound object that will be used to write the message
- */
- BufBound & WriteToBuffer(BufBound & aBuffer) const;
-
- /**
- * @brief
- * Parse data from an PacketBuffer into a struct instance
- *
- * Note that this struct will store pointers that point into the passed PacketBuffer,
- * so it is essential that the PacketBuffer is not modified until after this
- * struct is no longer needed.
- *
- * @param[in] aBuffer Pointer to a PacketBuffer containing the data
- *
- * @return CHIP_ERROR Any error that occurs when trying to read the message
- */
- CHECK_RETURN_VALUE
- CHIP_ERROR Parse(System::PacketBufferHandle aBuffer);
-
- /**
- * @brief
- * Returns the size of buffer needed to write this message.
- */
- size_t MessageSize() const;
-
- /**
- * @brief
* Equality check method.
*/
bool operator==(const DataBlock &) const;
uint32_t BlockCounter = 0;
- // WARNING: there is no guarantee at any point that this pointer will point to valid memory.
- // It is up to the caller to ensure that the memory pointed to here has not been freed.
- uint8_t * Data = nullptr;
- uint16_t DataLength = 0;
+ // WARNING: there is no guarantee at any point that this pointer will point to valid memory. The Buffer field should be used to
+ // hold a reference to the PacketBuffer containing the data in order to ensure the data is not freed.
+ const uint8_t * Data = nullptr;
+ uint16_t DataLength = 0;
// Retain ownership of the packet buffer so that the FileDesignator and Metadata pointers remain valid.
System::PacketBufferHandle Buffer;
+
+private:
+ CHIP_ERROR DerivedParse(System::PacketBufferHandle aBuffer) override;
+ BufBound & DerivedWriteToBuffer(BufBound & aBuffer) const override;
+ size_t DerivedMessageSize() const override;
};
using Block = DataBlock;
using BlockEOF = DataBlock;
-} // namespace BDX
+} // namespace bdx
} // namespace chip
diff --git a/src/protocols/bdx/BdxTransferSession.cpp b/src/protocols/bdx/BdxTransferSession.cpp
new file mode 100644
index 0000000..3427dbd
--- /dev/null
+++ b/src/protocols/bdx/BdxTransferSession.cpp
@@ -0,0 +1,934 @@
+/**
+ * @file
+ * Implementation for the TransferSession class.
+ * // TODO: Support Asynchronous mode. Currently, only Synchronous mode is supported.
+ */
+
+#include <protocols/bdx/BdxTransferSession.h>
+
+#include <protocols/Protocols.h>
+#include <protocols/bdx/BdxMessages.h>
+#include <protocols/common/Constants.h>
+#include <support/BufferReader.h>
+#include <support/CodeUtils.h>
+#include <support/ReturnMacros.h>
+#include <system/SystemPacketBuffer.h>
+
+namespace {
+constexpr uint8_t kBdxVersion = 0; ///< The version of this implementation of the BDX spec
+constexpr size_t kStatusReportMinSize = 2 + 4 + 2; ///< 16 bits for GeneralCode, 32 bits for ProtocolId, 16 bits for ProtocolCode
+
+/**
+ * @brief
+ * Allocate a new PacketBuffer and write data from a BDX message struct.
+ */
+CHIP_ERROR WriteToPacketBuffer(const ::chip::bdx::BdxMessage & msgStruct, ::chip::System::PacketBufferHandle & msgBuf)
+{
+ size_t msgDataSize = msgStruct.MessageSize();
+ ::chip::System::PacketBufBound bbuf(msgDataSize);
+ if (bbuf.IsNull())
+ {
+ return CHIP_ERROR_NO_MEMORY;
+ }
+ msgStruct.WriteToBuffer(bbuf);
+ msgBuf = bbuf.Finalize();
+ if (msgBuf.IsNull())
+ {
+ return CHIP_ERROR_NO_MEMORY;
+ }
+
+ return CHIP_NO_ERROR;
+}
+
+CHIP_ERROR AttachHeader(uint16_t protocolId, uint8_t msgType, ::chip::System::PacketBufferHandle & msgBuf)
+{
+ CHIP_ERROR err = CHIP_NO_ERROR;
+ ::chip::PayloadHeader payloadHeader;
+
+ payloadHeader.SetMessageType(protocolId, msgType);
+
+ uint16_t headerSize = payloadHeader.EncodeSizeBytes();
+ uint16_t actualEncodedHeaderSize = 0;
+
+ VerifyOrExit(msgBuf->EnsureReservedSize(headerSize), err = CHIP_ERROR_NO_MEMORY);
+
+ msgBuf->SetStart(msgBuf->Start() - headerSize);
+ err = payloadHeader.Encode(msgBuf->Start(), headerSize, &actualEncodedHeaderSize);
+ SuccessOrExit(err);
+ VerifyOrExit(headerSize == actualEncodedHeaderSize, err = CHIP_ERROR_INTERNAL);
+
+exit:
+ return err;
+}
+} // anonymous namespace
+
+namespace chip {
+namespace bdx {
+
+TransferSession::TransferSession()
+{
+ mSuppportedXferOpts.SetRaw(0);
+}
+
+void TransferSession::PollOutput(OutputEvent & event, uint64_t curTimeMs)
+{
+ event = OutputEvent(kNone);
+
+ if (mShouldInitTimeoutStart)
+ {
+ mTimeoutStartTimeMs = curTimeMs;
+ mShouldInitTimeoutStart = false;
+ }
+
+ if (mAwaitingResponse && ((curTimeMs - mTimeoutStartTimeMs) >= mTimeoutMs))
+ {
+ event = OutputEvent(kTransferTimeout);
+ mState = kErrorState;
+ mAwaitingResponse = false;
+ return;
+ }
+
+ switch (mPendingOutput)
+ {
+ case kNone:
+ event = OutputEvent(kNone);
+ break;
+ case kInternalError:
+ event = OutputEvent::StatusReportEvent(kInternalError, mStatusReportData);
+ break;
+ case kStatusReceived:
+ event = OutputEvent::StatusReportEvent(kStatusReceived, mStatusReportData);
+ break;
+ case kMsgToSend:
+ event = OutputEvent(kMsgToSend);
+ event.MsgData = std::move(mPendingMsgHandle);
+ mTimeoutStartTimeMs = curTimeMs;
+ break;
+ case kInitReceived:
+ event = OutputEvent::TransferInitEvent(mTransferRequestData, std::move(mPendingMsgHandle));
+ break;
+ case kAcceptReceived:
+ event = OutputEvent::TransferAcceptEvent(mTransferAcceptData, std::move(mPendingMsgHandle));
+ break;
+ case kQueryReceived:
+ event = OutputEvent(kQueryReceived);
+ break;
+ case kBlockReceived:
+ event = OutputEvent::BlockDataEvent(mBlockEventData, std::move(mPendingMsgHandle));
+ break;
+ case kAckReceived:
+ event = OutputEvent(kAckReceived);
+ break;
+ case kAckEOFReceived:
+ event = OutputEvent(kAckEOFReceived);
+ break;
+ default:
+ event = OutputEvent(kNone);
+ break;
+ }
+
+ // If there's no other pending output but an error occured or was received, then continue to output the error.
+ // This ensures that when the TransferSession encounters an error and needs to send a StatusReport, both a kMsgToSend and a
+ // kInternalError output event will be emitted.
+ if (event.EventType == kNone && mState == kErrorState)
+ {
+ event = OutputEvent::StatusReportEvent(kInternalError, mStatusReportData);
+ }
+
+ mPendingOutput = kNone;
+}
+
+CHIP_ERROR TransferSession::StartTransfer(TransferRole role, const TransferInitData & initData, uint32_t timeoutMs)
+{
+ CHIP_ERROR err = CHIP_NO_ERROR;
+ MessageType msgType;
+ TransferInit initMsg;
+
+ VerifyOrExit(mState == kUnitialized, err = CHIP_ERROR_INCORRECT_STATE);
+
+ mRole = role;
+ mTimeoutMs = timeoutMs;
+
+ // Set transfer parameters. They may be overridden later by an Accept message
+ mSuppportedXferOpts.SetRaw(initData.TransferCtlFlagsRaw);
+ mMaxSupportedBlockSize = initData.MaxBlockSize;
+ mStartOffset = initData.StartOffset;
+ mTransferLength = initData.Length;
+
+ // Prepare TransferInit message
+ initMsg.TransferCtlOptions.SetRaw(initData.TransferCtlFlagsRaw);
+ initMsg.Version = kBdxVersion;
+ initMsg.MaxBlockSize = mMaxSupportedBlockSize;
+ initMsg.StartOffset = mStartOffset;
+ initMsg.MaxLength = mTransferLength;
+ initMsg.FileDesignator = initData.FileDesignator;
+ initMsg.FileDesLength = initData.FileDesLength;
+ initMsg.Metadata = initData.Metadata;
+ initMsg.MetadataLength = initData.MetadataLength;
+
+ err = WriteToPacketBuffer(initMsg, mPendingMsgHandle);
+ SuccessOrExit(err);
+
+ msgType = (mRole == kRole_Sender) ? kBdxMsg_SendInit : kBdxMsg_ReceiveInit;
+ err = AttachHeader(Protocols::kProtocol_BDX, msgType, mPendingMsgHandle);
+ SuccessOrExit(err);
+
+ mState = kAwaitingAccept;
+ mAwaitingResponse = true;
+
+ mPendingOutput = kMsgToSend;
+
+exit:
+ return err;
+}
+
+CHIP_ERROR TransferSession::WaitForTransfer(TransferRole role, BitFlags<uint8_t, TransferControlFlags> xferControlOpts,
+ uint16_t maxBlockSize, uint32_t timeoutMs)
+{
+ CHIP_ERROR err = CHIP_NO_ERROR;
+
+ VerifyOrExit(mState == kUnitialized, err = CHIP_ERROR_INCORRECT_STATE);
+
+ // Used to determine compatibility with any future TransferInit parameters
+ mRole = role;
+ mTimeoutMs = timeoutMs;
+ mSuppportedXferOpts = xferControlOpts;
+ mMaxSupportedBlockSize = maxBlockSize;
+
+ mState = kAwaitingInitMsg;
+
+exit:
+ return err;
+}
+
+CHIP_ERROR TransferSession::AcceptTransfer(const TransferAcceptData & acceptData)
+{
+ CHIP_ERROR err = CHIP_NO_ERROR;
+ System::PacketBufferHandle outMsgBuf;
+ BitFlags<uint8_t, TransferControlFlags> proposedControlOpts;
+
+ VerifyOrExit(mState == kNegotiateTransferParams, err = CHIP_ERROR_INCORRECT_STATE);
+ VerifyOrExit(mPendingOutput == kNone, err = CHIP_ERROR_INCORRECT_STATE);
+
+ // Don't allow a Control method that wasn't supported by the initiator
+ // MaxBlockSize can't be larger than the proposed value
+ proposedControlOpts.SetRaw(mTransferRequestData.TransferCtlFlagsRaw);
+ VerifyOrExit(proposedControlOpts.Has(acceptData.ControlMode), err = CHIP_ERROR_INVALID_ARGUMENT);
+ VerifyOrExit(acceptData.MaxBlockSize <= mTransferRequestData.MaxBlockSize, err = CHIP_ERROR_INVALID_ARGUMENT);
+
+ mTransferMaxBlockSize = acceptData.MaxBlockSize;
+
+ if (mRole == kRole_Sender)
+ {
+ mStartOffset = acceptData.StartOffset;
+ mTransferLength = acceptData.Length;
+
+ ReceiveAccept acceptMsg;
+ acceptMsg.TransferCtlFlags.Set(acceptData.ControlMode);
+ acceptMsg.Version = mTransferVersion;
+ acceptMsg.MaxBlockSize = acceptData.MaxBlockSize;
+ acceptMsg.StartOffset = acceptData.StartOffset;
+ acceptMsg.Length = acceptData.Length;
+ acceptMsg.Metadata = acceptData.Metadata;
+ acceptMsg.MetadataLength = acceptData.MetadataLength;
+
+ err = WriteToPacketBuffer(acceptMsg, mPendingMsgHandle);
+ SuccessOrExit(err);
+
+ err = AttachHeader(Protocols::kProtocol_BDX, kBdxMsg_ReceiveAccept, mPendingMsgHandle);
+ SuccessOrExit(err);
+ }
+ else
+ {
+ SendAccept acceptMsg;
+ acceptMsg.TransferCtlFlags.Set(acceptData.ControlMode);
+ acceptMsg.Version = mTransferVersion;
+ acceptMsg.MaxBlockSize = acceptData.MaxBlockSize;
+ acceptMsg.Metadata = acceptData.Metadata;
+ acceptMsg.MetadataLength = acceptData.MetadataLength;
+
+ err = WriteToPacketBuffer(acceptMsg, mPendingMsgHandle);
+ SuccessOrExit(err);
+
+ err = AttachHeader(Protocols::kProtocol_BDX, kBdxMsg_SendAccept, mPendingMsgHandle);
+ SuccessOrExit(err);
+ }
+
+ mPendingOutput = kMsgToSend;
+
+ mState = kTransferInProgress;
+
+ if ((mRole == kRole_Receiver && mControlMode == kControl_SenderDrive) ||
+ (mRole == kRole_Sender && mControlMode == kControl_ReceiverDrive))
+ {
+ mAwaitingResponse = true;
+ }
+
+exit:
+ return err;
+}
+
+CHIP_ERROR TransferSession::PrepareBlockQuery()
+{
+ CHIP_ERROR err = CHIP_NO_ERROR;
+ BlockQuery queryMsg;
+
+ VerifyOrExit(mState == kTransferInProgress, err = CHIP_ERROR_INCORRECT_STATE);
+ VerifyOrExit(mRole == kRole_Receiver, err = CHIP_ERROR_INCORRECT_STATE);
+ VerifyOrExit(mPendingOutput == kNone, err = CHIP_ERROR_INCORRECT_STATE);
+ VerifyOrExit(!mAwaitingResponse, err = CHIP_ERROR_INCORRECT_STATE);
+
+ queryMsg.BlockCounter = mNextQueryNum;
+
+ err = WriteToPacketBuffer(queryMsg, mPendingMsgHandle);
+ SuccessOrExit(err);
+
+ err = AttachHeader(Protocols::kProtocol_BDX, kBdxMsg_BlockQuery, mPendingMsgHandle);
+ SuccessOrExit(err);
+
+ mPendingOutput = kMsgToSend;
+
+ mAwaitingResponse = true;
+ mLastQueryNum = mNextQueryNum++;
+
+exit:
+ return err;
+}
+
+CHIP_ERROR TransferSession::PrepareBlock(const BlockData & inData)
+{
+ CHIP_ERROR err = CHIP_NO_ERROR;
+ DataBlock blockMsg;
+ MessageType msgType;
+
+ VerifyOrExit(mState == kTransferInProgress, err = CHIP_ERROR_INCORRECT_STATE);
+ VerifyOrExit(mRole == kRole_Sender, err = CHIP_ERROR_INCORRECT_STATE);
+ VerifyOrExit(mPendingOutput == kNone, err = CHIP_ERROR_INCORRECT_STATE);
+ VerifyOrExit(!mAwaitingResponse, err = CHIP_ERROR_INCORRECT_STATE);
+
+ // Verify non-zero data is provided and is no longer than MaxBlockSize (BlockEOF may contain 0 length data)
+ VerifyOrExit((inData.Data != nullptr) && (inData.Length <= mTransferMaxBlockSize), err = CHIP_ERROR_INVALID_ARGUMENT);
+
+ blockMsg.BlockCounter = mNextBlockNum;
+ blockMsg.Data = inData.Data;
+ blockMsg.DataLength = inData.Length;
+
+ err = WriteToPacketBuffer(blockMsg, mPendingMsgHandle);
+ SuccessOrExit(err);
+
+ msgType = inData.IsEof ? kBdxMsg_BlockEOF : kBdxMsg_Block;
+ err = AttachHeader(Protocols::kProtocol_BDX, msgType, mPendingMsgHandle);
+ SuccessOrExit(err);
+
+ mPendingOutput = kMsgToSend;
+
+ if (msgType == kBdxMsg_BlockEOF)
+ {
+ mState = kAwaitingEOFAck;
+ }
+
+ mAwaitingResponse = true;
+ mLastBlockNum = mNextBlockNum++;
+
+exit:
+ return err;
+}
+
+CHIP_ERROR TransferSession::PrepareBlockAck()
+{
+ CHIP_ERROR err = CHIP_NO_ERROR;
+ CounterMessage ackMsg;
+ MessageType msgType;
+
+ VerifyOrExit(mRole == kRole_Receiver, err = CHIP_ERROR_INCORRECT_STATE);
+ VerifyOrExit((mState == kTransferInProgress) || (mState == kReceivedEOF), err = CHIP_ERROR_INCORRECT_STATE);
+ VerifyOrExit(mPendingOutput == kNone, err = CHIP_ERROR_INCORRECT_STATE);
+
+ ackMsg.BlockCounter = mLastBlockNum;
+ msgType = (mState == kReceivedEOF) ? kBdxMsg_BlockAckEOF : kBdxMsg_BlockAck;
+
+ err = WriteToPacketBuffer(ackMsg, mPendingMsgHandle);
+ SuccessOrExit(err);
+
+ err = AttachHeader(Protocols::kProtocol_BDX, msgType, mPendingMsgHandle);
+ SuccessOrExit(err);
+
+ if (mState == kTransferInProgress)
+ {
+ if (mControlMode == kControl_SenderDrive)
+ {
+ // In Sender Drive, a BlockAck is implied to also be a query for the next Block, so expect to receive a Block
+ // message.
+ mLastQueryNum = ackMsg.BlockCounter + 1;
+ mAwaitingResponse = true;
+ }
+ }
+ else if (mState == kReceivedEOF)
+ {
+ mState = kTransferDone;
+ mAwaitingResponse = false;
+ }
+
+ mPendingOutput = kMsgToSend;
+
+exit:
+ return err;
+}
+
+CHIP_ERROR TransferSession::AbortTransfer(StatusCode reason)
+{
+ CHIP_ERROR err = CHIP_NO_ERROR;
+
+ VerifyOrExit((mState != kUnitialized) && (mState != kTransferDone) && (mState != kErrorState),
+ err = CHIP_ERROR_INCORRECT_STATE);
+
+ PrepareStatusReport(reason);
+
+exit:
+ return err;
+}
+
+void TransferSession::Reset()
+{
+ mPendingOutput = kNone;
+ mState = kUnitialized;
+ mSuppportedXferOpts.SetRaw(0);
+ mTransferVersion = 0;
+ mMaxSupportedBlockSize = 0;
+ mStartOffset = 0;
+ mTransferLength = 0;
+ mTransferMaxBlockSize = 0;
+
+ mPendingMsgHandle = nullptr;
+
+ mNumBytesProcessed = 0;
+ mLastBlockNum = 0;
+ mNextBlockNum = 0;
+ mLastQueryNum = 0;
+ mNextQueryNum = 0;
+
+ mTimeoutMs = 0;
+ mTimeoutStartTimeMs = 0;
+ mShouldInitTimeoutStart = true;
+ mAwaitingResponse = false;
+}
+
+CHIP_ERROR TransferSession::HandleMessageReceived(System::PacketBufferHandle msg, uint64_t curTimeMs)
+{
+ CHIP_ERROR err = CHIP_NO_ERROR;
+ uint16_t headerSize = 0;
+ PayloadHeader payloadHeader;
+
+ VerifyOrExit(!msg.IsNull(), err = CHIP_ERROR_INVALID_ARGUMENT);
+
+ err = payloadHeader.Decode(msg->Start(), msg->DataLength(), &headerSize);
+ SuccessOrExit(err);
+
+ msg->ConsumeHead(headerSize);
+
+ if (payloadHeader.GetProtocolID() == Protocols::kProtocol_BDX)
+ {
+ err = HandleBdxMessage(payloadHeader, std::move(msg));
+ SuccessOrExit(err);
+
+ mTimeoutStartTimeMs = curTimeMs;
+ }
+ else if (payloadHeader.GetProtocolID() == Protocols::kProtocol_Protocol_Common &&
+ payloadHeader.GetMessageType() == static_cast<uint8_t>(Protocols::Common::MsgType::StatusReport))
+ {
+ err = HandleStatusReportMessage(payloadHeader, std::move(msg));
+ SuccessOrExit(err);
+ }
+ else
+ {
+ err = CHIP_ERROR_INVALID_MESSAGE_TYPE;
+ }
+
+exit:
+ return err;
+}
+
+// Return CHIP_ERROR only if there was a problem decoding the message. Otherwise, call PrepareStatusReport().
+CHIP_ERROR TransferSession::HandleBdxMessage(PayloadHeader & header, System::PacketBufferHandle msg)
+{
+ CHIP_ERROR err = CHIP_NO_ERROR;
+ MessageType msgType = static_cast<MessageType>(header.GetMessageType());
+
+ VerifyOrExit(!msg.IsNull(), err = CHIP_ERROR_INVALID_ARGUMENT);
+ VerifyOrExit(mPendingOutput == kNone, err = CHIP_ERROR_INCORRECT_STATE);
+
+ switch (msgType)
+ {
+ case kBdxMsg_SendInit:
+ case kBdxMsg_ReceiveInit:
+ HandleTransferInit(msgType, std::move(msg));
+ break;
+ case kBdxMsg_SendAccept:
+ HandleSendAccept(std::move(msg));
+ break;
+ case kBdxMsg_ReceiveAccept:
+ HandleReceiveAccept(std::move(msg));
+ break;
+ case kBdxMsg_BlockQuery:
+ HandleBlockQuery(std::move(msg));
+ break;
+ case kBdxMsg_Block:
+ HandleBlock(std::move(msg));
+ break;
+ case kBdxMsg_BlockEOF:
+ HandleBlockEOF(std::move(msg));
+ break;
+ case kBdxMsg_BlockAck:
+ HandleBlockAck(std::move(msg));
+ break;
+ case kBdxMsg_BlockAckEOF:
+ HandleBlockAckEOF(std::move(msg));
+ break;
+ default:
+ err = CHIP_ERROR_INVALID_MESSAGE_TYPE;
+ break;
+ }
+
+exit:
+ return err;
+}
+
+/**
+ * @brief
+ * Parse a StatusReport message and prepare to emit an OutputEvent with the message data.
+ *
+ * NOTE: BDX does not currently expect to ever use a "Success" general code, so it will be treated as an error along with any
+ * other code.
+ */
+CHIP_ERROR TransferSession::HandleStatusReportMessage(PayloadHeader & header, System::PacketBufferHandle msg)
+{
+ VerifyOrReturnError(!msg.IsNull(), CHIP_ERROR_INVALID_ARGUMENT);
+
+ mState = kErrorState;
+ mAwaitingResponse = false;
+
+ uint16_t generalCode = 0;
+ uint32_t protocolId = 0;
+ uint16_t protocolCode = 0;
+ Encoding::LittleEndian::Reader reader(msg->Start(), msg->DataLength());
+ ReturnErrorOnFailure(reader.Read16(&generalCode).Read32(&protocolId).Read16(&protocolCode).StatusCode());
+ VerifyOrReturnError((protocolId == Protocols::kProtocol_BDX), CHIP_ERROR_INVALID_MESSAGE_TYPE);
+
+ mStatusReportData.StatusCode = protocolCode;
+
+ mPendingOutput = kStatusReceived;
+
+ return CHIP_NO_ERROR;
+}
+
+void TransferSession::HandleTransferInit(MessageType msgType, System::PacketBufferHandle msgData)
+{
+ CHIP_ERROR err = CHIP_NO_ERROR;
+ TransferInit transferInit;
+
+ VerifyOrExit(mState == kAwaitingInitMsg, PrepareStatusReport(kStatus_ServerBadState));
+
+ if (mRole == kRole_Sender)
+ {
+ VerifyOrExit(msgType == kBdxMsg_ReceiveInit, PrepareStatusReport(kStatus_ServerBadState));
+ }
+ else
+ {
+ VerifyOrExit(msgType == kBdxMsg_SendInit, PrepareStatusReport(kStatus_ServerBadState));
+ }
+
+ err = transferInit.Parse(msgData.Retain());
+ VerifyOrExit(err == CHIP_NO_ERROR, PrepareStatusReport(kStatus_BadMessageContents));
+
+ ResolveTransferControlOptions(transferInit.TransferCtlOptions);
+ mTransferVersion = ::chip::min(kBdxVersion, transferInit.Version);
+ mTransferMaxBlockSize = ::chip::min(mMaxSupportedBlockSize, transferInit.MaxBlockSize);
+
+ // Accept for now, they may be changed or rejected by the peer if this is a ReceiveInit
+ mStartOffset = transferInit.StartOffset;
+ mTransferLength = transferInit.MaxLength;
+
+ // Store the Request data to share with the caller for verification
+ mTransferRequestData.TransferCtlFlagsRaw = transferInit.TransferCtlOptions.Raw(),
+ mTransferRequestData.MaxBlockSize = transferInit.MaxBlockSize;
+ mTransferRequestData.StartOffset = transferInit.StartOffset;
+ mTransferRequestData.Length = transferInit.MaxLength;
+ mTransferRequestData.FileDesignator = transferInit.FileDesignator;
+ mTransferRequestData.FileDesLength = transferInit.FileDesLength;
+ mTransferRequestData.Metadata = transferInit.Metadata;
+ mTransferRequestData.MetadataLength = transferInit.MetadataLength;
+
+ mPendingMsgHandle = std::move(msgData);
+ mPendingOutput = kInitReceived;
+
+ mState = kNegotiateTransferParams;
+
+exit:
+ return;
+}
+
+void TransferSession::HandleReceiveAccept(System::PacketBufferHandle msgData)
+{
+ CHIP_ERROR err = CHIP_NO_ERROR;
+ ReceiveAccept rcvAcceptMsg;
+
+ VerifyOrExit(mRole == kRole_Receiver, PrepareStatusReport(kStatus_ServerBadState));
+ VerifyOrExit(mState == kAwaitingAccept, PrepareStatusReport(kStatus_ServerBadState));
+
+ err = rcvAcceptMsg.Parse(msgData.Retain());
+ VerifyOrExit(err == CHIP_NO_ERROR, PrepareStatusReport(kStatus_BadMessageContents));
+
+ // Verify that Accept parameters are compatible with the original proposed parameters
+ err = VerifyProposedMode(rcvAcceptMsg.TransferCtlFlags);
+ SuccessOrExit(err);
+
+ mTransferMaxBlockSize = rcvAcceptMsg.MaxBlockSize;
+ mStartOffset = rcvAcceptMsg.StartOffset;
+ mTransferLength = rcvAcceptMsg.Length;
+
+ // Note: if VerifyProposedMode() returned with no error, then mControlMode must match the proposed mode in the ReceiveAccept
+ // message
+ mTransferAcceptData.ControlMode = mControlMode;
+ mTransferAcceptData.MaxBlockSize = rcvAcceptMsg.MaxBlockSize;
+ mTransferAcceptData.StartOffset = rcvAcceptMsg.StartOffset;
+ mTransferAcceptData.Length = rcvAcceptMsg.Length;
+ mTransferAcceptData.Metadata = rcvAcceptMsg.Metadata;
+ mTransferAcceptData.MetadataLength = rcvAcceptMsg.MetadataLength;
+
+ mPendingMsgHandle = std::move(msgData);
+ mPendingOutput = kAcceptReceived;
+
+ mAwaitingResponse = (mControlMode == kControl_SenderDrive);
+ mState = kTransferInProgress;
+
+exit:
+ return;
+}
+
+void TransferSession::HandleSendAccept(System::PacketBufferHandle msgData)
+{
+ CHIP_ERROR err = CHIP_NO_ERROR;
+ SendAccept sendAcceptMsg;
+
+ VerifyOrExit(mRole == kRole_Sender, PrepareStatusReport(kStatus_ServerBadState));
+ VerifyOrExit(mState == kAwaitingAccept, PrepareStatusReport(kStatus_ServerBadState));
+
+ err = sendAcceptMsg.Parse(msgData.Retain());
+ VerifyOrExit(err == CHIP_NO_ERROR, PrepareStatusReport(kStatus_BadMessageContents));
+
+ // Verify that Accept parameters are compatible with the original proposed parameters
+ err = VerifyProposedMode(sendAcceptMsg.TransferCtlFlags);
+ SuccessOrExit(err);
+
+ // Note: if VerifyProposedMode() returned with no error, then mControlMode must match the proposed mode in the SendAccept
+ // message
+ mTransferMaxBlockSize = sendAcceptMsg.MaxBlockSize;
+
+ mTransferAcceptData.ControlMode = mControlMode;
+ mTransferAcceptData.MaxBlockSize = sendAcceptMsg.MaxBlockSize;
+ mTransferAcceptData.StartOffset = mStartOffset; // Not included in SendAccept msg, so use member
+ mTransferAcceptData.Length = mTransferLength; // Not included in SendAccept msg, so use member
+ mTransferAcceptData.Metadata = sendAcceptMsg.Metadata;
+ mTransferAcceptData.MetadataLength = sendAcceptMsg.MetadataLength;
+
+ mPendingMsgHandle = std::move(msgData);
+ mPendingOutput = kAcceptReceived;
+
+ mAwaitingResponse = (mControlMode == kControl_ReceiverDrive);
+ mState = kTransferInProgress;
+
+exit:
+ return;
+}
+
+void TransferSession::HandleBlockQuery(System::PacketBufferHandle msgData)
+{
+ CHIP_ERROR err = CHIP_NO_ERROR;
+ BlockQuery query;
+
+ VerifyOrExit(mRole == kRole_Sender, PrepareStatusReport(kStatus_ServerBadState));
+ VerifyOrExit(mState == kTransferInProgress, PrepareStatusReport(kStatus_ServerBadState));
+ VerifyOrExit(mAwaitingResponse, PrepareStatusReport(kStatus_ServerBadState));
+
+ err = query.Parse(std::move(msgData));
+ VerifyOrExit(err == CHIP_NO_ERROR, PrepareStatusReport(kStatus_BadMessageContents));
+
+ VerifyOrExit(query.BlockCounter == mNextBlockNum, PrepareStatusReport(kStatus_BadBlockCounter));
+
+ mPendingOutput = kQueryReceived;
+
+ mAwaitingResponse = false;
+ mLastQueryNum = query.BlockCounter;
+
+exit:
+ return;
+}
+
+void TransferSession::HandleBlock(System::PacketBufferHandle msgData)
+{
+ CHIP_ERROR err = CHIP_NO_ERROR;
+ Block blockMsg;
+
+ VerifyOrExit(mRole == kRole_Receiver, PrepareStatusReport(kStatus_ServerBadState));
+ VerifyOrExit(mState == kTransferInProgress, PrepareStatusReport(kStatus_ServerBadState));
+ VerifyOrExit(mAwaitingResponse, PrepareStatusReport(kStatus_ServerBadState));
+
+ err = blockMsg.Parse(msgData.Retain());
+ VerifyOrExit(err == CHIP_NO_ERROR, PrepareStatusReport(kStatus_BadMessageContents));
+
+ VerifyOrExit(blockMsg.BlockCounter == mLastQueryNum, PrepareStatusReport(kStatus_BadBlockCounter));
+ VerifyOrExit((blockMsg.DataLength > 0) && (blockMsg.DataLength <= mTransferMaxBlockSize),
+ PrepareStatusReport(kStatus_BadMessageContents));
+
+ if (IsTransferLengthDefinite())
+ {
+ VerifyOrExit(mNumBytesProcessed + blockMsg.DataLength <= mTransferLength, PrepareStatusReport(kStatus_LengthMismatch));
+ }
+
+ mBlockEventData.Data = blockMsg.Data;
+ mBlockEventData.Length = blockMsg.DataLength;
+ mBlockEventData.IsEof = false;
+
+ mPendingMsgHandle = std::move(msgData);
+ mPendingOutput = kBlockReceived;
+
+ mNumBytesProcessed += blockMsg.DataLength;
+ mLastBlockNum = blockMsg.BlockCounter;
+
+ mAwaitingResponse = false;
+
+exit:
+ return;
+}
+
+void TransferSession::HandleBlockEOF(System::PacketBufferHandle msgData)
+{
+ CHIP_ERROR err = CHIP_NO_ERROR;
+ BlockEOF blockEOFMsg;
+
+ VerifyOrExit(mRole == kRole_Receiver, PrepareStatusReport(kStatus_ServerBadState));
+ VerifyOrExit(mState == kTransferInProgress, PrepareStatusReport(kStatus_ServerBadState));
+ VerifyOrExit(mAwaitingResponse, PrepareStatusReport(kStatus_ServerBadState));
+
+ err = blockEOFMsg.Parse(msgData.Retain());
+ VerifyOrExit(err == CHIP_NO_ERROR, PrepareStatusReport(kStatus_BadMessageContents));
+
+ VerifyOrExit(blockEOFMsg.BlockCounter == mLastQueryNum, PrepareStatusReport(kStatus_BadBlockCounter));
+ VerifyOrExit(blockEOFMsg.DataLength <= mTransferMaxBlockSize, PrepareStatusReport(kStatus_BadMessageContents));
+
+ mBlockEventData.Data = blockEOFMsg.Data;
+ mBlockEventData.Length = blockEOFMsg.DataLength;
+ mBlockEventData.IsEof = true;
+
+ mPendingMsgHandle = std::move(msgData);
+ mPendingOutput = kBlockReceived;
+
+ mNumBytesProcessed += blockEOFMsg.DataLength;
+ mLastBlockNum = blockEOFMsg.BlockCounter;
+
+ mAwaitingResponse = false;
+ mState = kReceivedEOF;
+
+exit:
+ return;
+}
+
+void TransferSession::HandleBlockAck(System::PacketBufferHandle msgData)
+{
+ CHIP_ERROR err = CHIP_NO_ERROR;
+ BlockAck ackMsg;
+
+ VerifyOrExit(mRole == kRole_Sender, PrepareStatusReport(kStatus_ServerBadState));
+ VerifyOrExit(mState == kTransferInProgress, PrepareStatusReport(kStatus_ServerBadState));
+ VerifyOrExit(mAwaitingResponse, PrepareStatusReport(kStatus_ServerBadState));
+
+ err = ackMsg.Parse(std::move(msgData));
+ VerifyOrExit(err == CHIP_NO_ERROR, PrepareStatusReport(kStatus_BadMessageContents));
+ VerifyOrExit(ackMsg.BlockCounter == mLastBlockNum, PrepareStatusReport(kStatus_BadBlockCounter));
+
+ mPendingOutput = kAckReceived;
+
+ // In Receiver Drive, the Receiver can send a BlockAck to indicate receipt of the message and reset the timeout.
+ // In this case, the Sender should wait to receive a BlockQuery next.
+ mAwaitingResponse = (mControlMode == kControl_ReceiverDrive);
+
+exit:
+ return;
+}
+
+void TransferSession::HandleBlockAckEOF(System::PacketBufferHandle msgData)
+{
+ CHIP_ERROR err = CHIP_NO_ERROR;
+ BlockAckEOF ackMsg;
+
+ VerifyOrExit(mRole == kRole_Sender, PrepareStatusReport(kStatus_ServerBadState));
+ VerifyOrExit(mState == kAwaitingEOFAck, PrepareStatusReport(kStatus_ServerBadState));
+ VerifyOrExit(mAwaitingResponse, PrepareStatusReport(kStatus_ServerBadState));
+
+ err = ackMsg.Parse(std::move(msgData));
+ VerifyOrExit(err == CHIP_NO_ERROR, PrepareStatusReport(kStatus_BadMessageContents));
+ VerifyOrExit(ackMsg.BlockCounter == mLastBlockNum, PrepareStatusReport(kStatus_BadBlockCounter));
+
+ mPendingOutput = kAckEOFReceived;
+
+ mAwaitingResponse = false;
+
+ mState = kTransferDone;
+
+exit:
+ return;
+}
+
+void TransferSession::ResolveTransferControlOptions(const BitFlags<uint8_t, TransferControlFlags> & proposed)
+{
+ // Must specify at least one synchronous option
+ if (!proposed.Has(kControl_SenderDrive) && !proposed.Has(kControl_ReceiverDrive))
+ {
+ PrepareStatusReport(kStatus_TransferMethodNotSupported);
+ return;
+ }
+
+ // Ensure there are options supported by both nodes. Async gets priority.
+ // If there is only one common option, choose that one. Otherwise the application must pick.
+ BitFlags<uint8_t, TransferControlFlags> commonOpts;
+ commonOpts.SetRaw(proposed.Raw() & mSuppportedXferOpts.Raw());
+ if (commonOpts.Raw() == 0)
+ {
+ PrepareStatusReport(kStatus_TransferMethodNotSupported);
+ }
+ else if (commonOpts.HasOnly(kControl_Async))
+ {
+ mControlMode = kControl_Async;
+ }
+ else if (commonOpts.HasOnly(kControl_ReceiverDrive))
+ {
+ mControlMode = kControl_ReceiverDrive;
+ }
+ else if (commonOpts.HasOnly(kControl_SenderDrive))
+ {
+ mControlMode = kControl_SenderDrive;
+ }
+}
+
+CHIP_ERROR TransferSession::VerifyProposedMode(const BitFlags<uint8_t, TransferControlFlags> & proposed)
+{
+ TransferControlFlags mode;
+
+ // Must specify only one mode in Accept messages
+ if (proposed.HasOnly(kControl_Async))
+ {
+ mode = kControl_Async;
+ }
+ else if (proposed.HasOnly(kControl_ReceiverDrive))
+ {
+ mode = kControl_ReceiverDrive;
+ }
+ else if (proposed.HasOnly(kControl_SenderDrive))
+ {
+ mode = kControl_SenderDrive;
+ }
+ else
+ {
+ PrepareStatusReport(kStatus_BadMessageContents);
+ return CHIP_ERROR_INTERNAL;
+ }
+
+ // Verify the proposed mode is supported by this instance
+ if (mSuppportedXferOpts.Has(mode))
+ {
+ mControlMode = mode;
+ }
+ else
+ {
+ PrepareStatusReport(kStatus_TransferMethodNotSupported);
+ return CHIP_ERROR_INTERNAL;
+ }
+
+ return CHIP_NO_ERROR;
+}
+
+void TransferSession::PrepareStatusReport(StatusCode code)
+{
+ mStatusReportData.StatusCode = code;
+
+ System::PacketBufBound bbuf(kStatusReportMinSize);
+ VerifyOrReturn(!bbuf.IsNull());
+
+ bbuf.Put16(static_cast<uint16_t>(Protocols::Common::StatusCode::Failure));
+ bbuf.Put32(Protocols::kProtocol_BDX);
+ bbuf.Put16(mStatusReportData.StatusCode);
+
+ mPendingMsgHandle = bbuf.Finalize();
+ if (mPendingMsgHandle.IsNull())
+ {
+ mPendingOutput = kInternalError;
+ }
+ else
+ {
+ CHIP_ERROR err = AttachHeader(Protocols::kProtocol_Protocol_Common,
+ static_cast<uint8_t>(Protocols::Common::MsgType::StatusReport), mPendingMsgHandle);
+ VerifyOrReturn(err == CHIP_NO_ERROR);
+
+ mPendingOutput = kMsgToSend;
+ }
+
+ mState = kErrorState;
+ mAwaitingResponse = false; // Prevent triggering timeout
+}
+
+bool TransferSession::IsTransferLengthDefinite()
+{
+ return (mTransferLength > 0);
+}
+
+TransferSession::OutputEvent TransferSession::OutputEvent::TransferInitEvent(TransferInitData data, System::PacketBufferHandle msg)
+{
+ OutputEvent event(kInitReceived);
+ event.MsgData = std::move(msg);
+ event.transferInitData = data;
+ return event;
+}
+
+/**
+ * @brief
+ * Convenience method for constructing an OutputEvent with TransferAcceptData that does not contain Metadata
+ */
+TransferSession::OutputEvent TransferSession::OutputEvent::TransferAcceptEvent(TransferAcceptData data)
+{
+ OutputEvent event(kAcceptReceived);
+ event.transferAcceptData = data;
+ return event;
+}
+/**
+ * @brief
+ * Convenience method for constructing an OutputEvent with TransferAcceptData that contains Metadata
+ */
+TransferSession::OutputEvent TransferSession::OutputEvent::TransferAcceptEvent(TransferAcceptData data,
+ System::PacketBufferHandle msg)
+{
+ OutputEvent event = TransferAcceptEvent(data);
+ event.MsgData = std::move(msg);
+ return event;
+}
+
+TransferSession::OutputEvent TransferSession::OutputEvent::BlockDataEvent(BlockData data, System::PacketBufferHandle msg)
+{
+ OutputEvent event(kBlockReceived);
+ event.MsgData = std::move(msg);
+ event.blockdata = data;
+ return event;
+}
+
+/**
+ * @brief
+ * Convenience method for constructing an event with kInternalError or kOutputStatusReceived
+ */
+TransferSession::OutputEvent TransferSession::OutputEvent::StatusReportEvent(OutputEventType type, StatusReportData data)
+{
+ OutputEvent event(type);
+ event.statusData = data;
+ return event;
+}
+
+} // namespace bdx
+} // namespace chip
diff --git a/src/protocols/bdx/BdxTransferSession.h b/src/protocols/bdx/BdxTransferSession.h
new file mode 100644
index 0000000..e3b5831
--- /dev/null
+++ b/src/protocols/bdx/BdxTransferSession.h
@@ -0,0 +1,331 @@
+/**
+ * @file
+ * This file defines a TransferSession state machine that contains the main logic governing a Bulk Data Transfer session. It
+ * provides APIs for starting a transfer or preparing to receive a transfer request, providing input to be processed, and
+ * accessing output data (including messages to be sent, message data received by the TransferSession, or state information).
+ */
+
+#pragma once
+
+#include <core/CHIPError.h>
+#include <protocols/bdx/BdxMessages.h>
+#include <system/SystemPacketBuffer.h>
+#include <transport/raw/MessageHeader.h>
+
+namespace chip {
+namespace bdx {
+
+enum TransferRole : uint8_t
+{
+ kRole_Receiver = 0,
+ kRole_Sender = 1,
+};
+
+class DLL_EXPORT TransferSession
+{
+public:
+ enum OutputEventType : uint16_t
+ {
+ kNone = 0,
+ kMsgToSend,
+ kInitReceived,
+ kAcceptReceived,
+ kBlockReceived,
+ kQueryReceived,
+ kAckReceived,
+ kAckEOFReceived,
+ kStatusReceived,
+ kInternalError,
+ kTransferTimeout
+ };
+
+ struct TransferInitData
+ {
+ uint8_t TransferCtlFlagsRaw = 0;
+
+ uint16_t MaxBlockSize = 0;
+ uint64_t StartOffset = 0;
+ uint64_t Length = 0;
+
+ const uint8_t * FileDesignator = nullptr;
+ uint16_t FileDesLength = 0;
+
+ // Additional metadata (optional, TLV format)
+ const uint8_t * Metadata = nullptr;
+ uint16_t MetadataLength = 0;
+ };
+
+ struct TransferAcceptData
+ {
+ TransferControlFlags ControlMode;
+
+ uint16_t MaxBlockSize = 0;
+ uint64_t StartOffset = 0; ///< Not used for SendAccept message
+ uint64_t Length = 0; ///< Not used for SendAccept message
+
+ // Additional metadata (optional, TLV format)
+ const uint8_t * Metadata = nullptr;
+ uint16_t MetadataLength = 0;
+ };
+
+ struct StatusReportData
+ {
+ uint16_t StatusCode;
+ };
+
+ struct BlockData
+ {
+ const uint8_t * Data = nullptr;
+ uint16_t Length = 0;
+ bool IsEof = false;
+ };
+
+ /**
+ * @brief
+ * All output data processed by the TransferSession object will be passed to the caller using this struct via PollOutput().
+ *
+ * NOTE: Some sub-structs may contain pointers to data in a PacketBuffer. In this case, the MsgData field MUST be populated
+ * with a PacketBufferHandle that encapsulates the respective PacketBuffer, in order to ensure valid memory access.
+ */
+ struct OutputEvent
+ {
+ OutputEventType EventType;
+ System::PacketBufferHandle MsgData;
+ union
+ {
+ TransferInitData transferInitData;
+ TransferAcceptData transferAcceptData;
+ BlockData blockdata;
+ StatusReportData statusData;
+ };
+
+ OutputEvent() : EventType(kNone) { statusData = { kStatus_None }; }
+ OutputEvent(OutputEventType type) : EventType(type) { statusData = { kStatus_None }; }
+
+ static OutputEvent TransferInitEvent(TransferInitData data, System::PacketBufferHandle msg);
+ static OutputEvent TransferAcceptEvent(TransferAcceptData data);
+ static OutputEvent TransferAcceptEvent(TransferAcceptData data, System::PacketBufferHandle msg);
+ static OutputEvent BlockDataEvent(BlockData data, System::PacketBufferHandle msg);
+ static OutputEvent StatusReportEvent(OutputEventType type, StatusReportData data);
+ };
+
+ /**
+ * @brief
+ * Indicates the presence of pending output and includes any data for the caller to take action on.
+ *
+ * This method should be called frequently in order to be notified about any messages received. It should also be called after
+ * most other methods in order to notify the user of any message that needs to be sent, or errors that occurred internally.
+ *
+ * It is possible that consecutive calls to this method may emit different outputs depending on the state of the
+ * TransferSession object.
+ *
+ * Note that if the type outputted is kMsgToSend, it is assumed that the message will be send immediately, and the
+ * session timeout timer will begin at curTimeMs.
+ *
+ * See OutputEventType for all possible output event types.
+ *
+ * @param event Reference to an OutputEvent struct that will be filled out with any pending output data
+ * @param curTimeMs Current time indicated by the number of milliseconds since some epoch defined by the platform
+ */
+ void PollOutput(OutputEvent & event, uint64_t curTimeMs);
+
+ /**
+ * @brief
+ * Initializes the TransferSession object and prepares a TransferInit message (emitted via PollOutput()).
+ *
+ * A TransferSession object must be initialized with either StartTransfer() or WaitForTransfer().
+ *
+ * @param role Inidcates whether this object will be sending or receiving data
+ * @param initData Data for initializing this object and for populating a TransferInit message
+ * The role parameter will determine whether to populate a ReceiveInit or SendInit
+ * @param timeoutMs The amount of time to wait for a response before considering the transfer failed (milliseconds)
+ * @param curTimeMs The current time since epoch in milliseconds. Needed to set a start time for the transfer timeout.
+ *
+ * @return CHIP_ERROR Result of initialization and preparation of a TransferInit message. May also indicate if the
+ * TransferSession object is unable to handle this request.
+ */
+ CHIP_ERROR StartTransfer(TransferRole role, const TransferInitData & initData, uint32_t timeoutMs);
+
+ /**
+ * @brief
+ * Initialize the TransferSession object and prepare to receive a TransferInit message at some point.
+ *
+ * A TransferSession object must be initialized with either StartTransfer() or WaitForTransfer().
+ *
+ * @param role Inidcates whether this object will be sending or receiving data
+ * @param xferControlOpts Indicates all supported control modes. Used to respond to a TransferInit message
+ * @param maxBlockSize The max Block size that this object supports.
+ * @param timeoutMs The amount of time to wait for a response before considering the transfer failed (milliseconds)
+ *
+ * @return CHIP_ERROR Result of initialization. May also indicate if the TransferSession object is unable to handle this
+ * request.
+ */
+ CHIP_ERROR WaitForTransfer(TransferRole role, BitFlags<uint8_t, TransferControlFlags> xferControlOpts, uint16_t maxBlockSize,
+ uint32_t timeoutMs);
+
+ /**
+ * @brief
+ * Indicate that all transfer parameters are acceptable and prepare a SendAccept or ReceiveAccept message (depending on role).
+ *
+ * @param acceptData Data used to populate an Accept message (some fields may differ from the original Init message)
+ *
+ * @return CHIP_ERROR Result of preparation of an Accept message. May also indicate if the TransferSession object is unable to
+ * handle this request.
+ */
+ CHIP_ERROR AcceptTransfer(const TransferAcceptData & acceptData);
+
+ /**
+ * @brief
+ * Reject a TransferInit message. Use Reset() to prepare this object for another transfer.
+ *
+ * @param reason A StatusCode indicating the reason for rejecting the transfer
+ *
+ * @return CHIP_ERROR The result of the preparation of a StatusReport message. May also indicate if the TransferSession object
+ * is unable to handle this request.
+ */
+ CHIP_ERROR RejectTransfer(StatusCode reason);
+
+ /**
+ * @brief
+ * Prepare a BlockQuery message. The Block counter will be populated automatically.
+ *
+ * @return CHIP_ERROR The result of the preparation of a BlockQuery message. May also indicate if the TransferSession object
+ * is unable to handle this request.
+ */
+ CHIP_ERROR PrepareBlockQuery();
+
+ /**
+ * @brief
+ * Prepare a Block message. The Block counter will be populated automatically.
+ *
+ * @param inData Contains data for filling out the Block message
+ *
+ * @return CHIP_ERROR The result of the preparation of a Block message. May also indicate if the TransferSession object
+ * is unable to handle this request.
+ */
+ CHIP_ERROR PrepareBlock(const BlockData & inData);
+
+ /**
+ * @brief
+ * Prepare a BlockAck message. The Block counter will be populated automatically.
+ *
+ * @return CHIP_ERROR The result of the preparation of a BlockAck message. May also indicate if the TransferSession object
+ * is unable to handle this request.
+ */
+ CHIP_ERROR PrepareBlockAck();
+
+ /**
+ * @brief
+ * Prematurely end a transfer with a StatusReport. Must still call Reset() to prepare the TransferSession for another
+ * transfer.
+ *
+ * @param reason The StatusCode reason for ending the transfer.
+ *
+ * @return CHIP_ERROR May return an error if there is no transfer in progress.
+ */
+ CHIP_ERROR AbortTransfer(StatusCode reason);
+
+ /**
+ * @brief
+ * Reset all TransferSession parameters. The TransferSession object must then be re-initialized with StartTransfer() or
+ * WaitForTransfer().
+ */
+ void Reset();
+
+ /**
+ * @brief
+ * Process a message intended for this TransferSession object.
+ *
+ * @param msg A PacketBufferHandle pointing to the message buffer to process. May be BDX or StatusReport protocol.
+ * @param curTimeMs Current time indicated by the number of milliseconds since some epoch defined by the platform
+ *
+ * @return CHIP_ERROR Indicates any problems in decoding the message, or if the message is not of the BDX or StatusReport
+ * protocols.
+ */
+ CHIP_ERROR HandleMessageReceived(System::PacketBufferHandle msg, uint64_t curTimeMs);
+
+ TransferControlFlags GetControlMode() const { return mControlMode; }
+ uint64_t GetStartOffset() const { return mStartOffset; }
+ uint64_t GetTransferLength() const { return mTransferLength; }
+ uint16_t GetTransferBlockSize() const { return mTransferMaxBlockSize; }
+
+ TransferSession();
+
+private:
+ enum TransferState : uint8_t
+ {
+ kUnitialized,
+ kAwaitingInitMsg,
+ kAwaitingAccept,
+ kNegotiateTransferParams,
+ kTransferInProgress,
+ kAwaitingEOFAck,
+ kReceivedEOF,
+ kTransferDone,
+ kErrorState,
+ };
+
+ // Incoming message handlers
+ CHIP_ERROR HandleBdxMessage(PayloadHeader & header, System::PacketBufferHandle msg);
+ CHIP_ERROR HandleStatusReportMessage(PayloadHeader & header, System::PacketBufferHandle msg);
+ void HandleTransferInit(MessageType msgType, System::PacketBufferHandle msgData);
+ void HandleReceiveAccept(System::PacketBufferHandle msgData);
+ void HandleSendAccept(System::PacketBufferHandle msgData);
+ void HandleBlockQuery(System::PacketBufferHandle msgData);
+ void HandleBlock(System::PacketBufferHandle msgData);
+ void HandleBlockEOF(System::PacketBufferHandle msgData);
+ void HandleBlockAck(System::PacketBufferHandle msgData);
+ void HandleBlockAckEOF(System::PacketBufferHandle msgData);
+
+ /**
+ * @brief
+ * Used when handling a TransferInit message. Determines if there are any compatible Transfer control modes between the two
+ * transfer peers.
+ */
+ void ResolveTransferControlOptions(const BitFlags<uint8_t, TransferControlFlags> & proposed);
+
+ /**
+ * @brief
+ * Used when handling an Accept message. Verifies that the chosen control mode is compatible with the orignal supported modes.
+ */
+ CHIP_ERROR VerifyProposedMode(const BitFlags<uint8_t, TransferControlFlags> & proposed);
+
+ void PrepareStatusReport(StatusCode code);
+ bool IsTransferLengthDefinite();
+
+ OutputEventType mPendingOutput = kNone;
+ TransferState mState = kUnitialized;
+ TransferRole mRole;
+
+ // Indicate supported options pre- transfer accept
+ BitFlags<uint8_t, TransferControlFlags> mSuppportedXferOpts;
+ uint16_t mMaxSupportedBlockSize = 0;
+
+ // Used to govern transfer once it has been accepted
+ TransferControlFlags mControlMode;
+ uint8_t mTransferVersion = 0;
+ uint64_t mStartOffset = 0; ///< 0 represents no offset
+ uint64_t mTransferLength = 0; ///< 0 represents indefinite length
+ uint16_t mTransferMaxBlockSize = 0;
+
+ System::PacketBufferHandle mPendingMsgHandle;
+ StatusReportData mStatusReportData;
+ TransferInitData mTransferRequestData;
+ TransferAcceptData mTransferAcceptData;
+ BlockData mBlockEventData;
+
+ uint32_t mNumBytesProcessed = 0;
+
+ uint32_t mLastBlockNum = 0;
+ uint32_t mNextBlockNum = 0;
+ uint32_t mLastQueryNum = 0;
+ uint32_t mNextQueryNum = 0;
+
+ uint32_t mTimeoutMs = 0;
+ uint64_t mTimeoutStartTimeMs = 0;
+ bool mShouldInitTimeoutStart = true;
+ bool mAwaitingResponse = false;
+};
+
+} // namespace bdx
+} // namespace chip
diff --git a/src/protocols/bdx/tests/BUILD.gn b/src/protocols/bdx/tests/BUILD.gn
index cc5e88f..f489fb7 100644
--- a/src/protocols/bdx/tests/BUILD.gn
+++ b/src/protocols/bdx/tests/BUILD.gn
@@ -22,7 +22,10 @@
chip_test_suite("tests") {
output_name = "libBDXTests"
- test_sources = [ "TestBdxMessages.cpp" ]
+ test_sources = [
+ "TestBdxMessages.cpp",
+ "TestBdxTransferSession.cpp",
+ ]
public_deps = [
"${chip_root}/src/lib/core",
diff --git a/src/protocols/bdx/tests/TestBdxMessages.cpp b/src/protocols/bdx/tests/TestBdxMessages.cpp
index 3c0f37c..cfc4859 100644
--- a/src/protocols/bdx/tests/TestBdxMessages.cpp
+++ b/src/protocols/bdx/tests/TestBdxMessages.cpp
@@ -8,7 +8,7 @@
#include <limits>
using namespace chip;
-using namespace chip::BDX;
+using namespace chip::bdx;
/**
* Helper method for testing that WriteToBuffer() and Parse() are successful, and that the parsed message
@@ -42,7 +42,7 @@
TransferInit testMsg;
testMsg.TransferCtlOptions.SetRaw(0);
- testMsg.TransferCtlOptions.Set(kReceiverDrive, true);
+ testMsg.TransferCtlOptions.Set(kControl_ReceiverDrive, true);
testMsg.Version = 1;
// Make sure MaxLength is greater than UINT32_MAX to test widerange being set
@@ -68,7 +68,7 @@
testMsg.Version = 1;
testMsg.TransferCtlFlags.SetRaw(0);
- testMsg.TransferCtlFlags.Set(kReceiverDrive, true);
+ testMsg.TransferCtlFlags.Set(kControl_ReceiverDrive, true);
testMsg.MaxBlockSize = 256;
uint8_t fakeData[5] = { 7, 6, 5, 4, 3 };
@@ -84,7 +84,7 @@
testMsg.Version = 1;
testMsg.TransferCtlFlags.SetRaw(0);
- testMsg.TransferCtlFlags.Set(kReceiverDrive, true);
+ testMsg.TransferCtlFlags.Set(kControl_ReceiverDrive, true);
// Make sure Length is greater than UINT32_MAX to test widerange being set
testMsg.Length = static_cast<uint64_t>(std::numeric_limits<uint32_t>::max()) + 1;
diff --git a/src/protocols/bdx/tests/TestBdxTransferSession.cpp b/src/protocols/bdx/tests/TestBdxTransferSession.cpp
new file mode 100644
index 0000000..7d8b8c0
--- /dev/null
+++ b/src/protocols/bdx/tests/TestBdxTransferSession.cpp
@@ -0,0 +1,768 @@
+#include <protocols/Protocols.h>
+#include <protocols/bdx/BdxMessages.h>
+#include <protocols/bdx/BdxTransferSession.h>
+
+#include <string.h>
+
+#include <nlunit-test.h>
+
+#include <core/CHIPTLV.h>
+#include <protocols/common/Constants.h>
+#include <support/BufferReader.h>
+#include <support/CodeUtils.h>
+#include <support/ReturnMacros.h>
+#include <support/UnitTestRegistration.h>
+#include <system/SystemPacketBuffer.h>
+
+using namespace ::chip;
+using namespace ::chip::bdx;
+
+namespace {
+// Use this as a timestamp if not needing to test BDX timeouts.
+constexpr uint64_t kNoAdvanceTime = 0;
+
+const uint64_t tlvStrTag = TLV::ContextTag(4);
+const uint64_t 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 WriteChipTLVString(uint8_t * buf, uint32_t bufLen, const char * data, uint32_t & written)
+{
+ CHIP_ERROR err = CHIP_NO_ERROR;
+ written = 0;
+ TLV::TLVWriter writer;
+ writer.Init(buf, bufLen);
+
+ {
+ TLV::TLVWriter listWriter;
+ err = writer.OpenContainer(tlvListTag, TLV::kTLVType_List, listWriter);
+ SuccessOrExit(err);
+ err = listWriter.PutString(tlvStrTag, data);
+ SuccessOrExit(err);
+ err = writer.CloseContainer(listWriter);
+ SuccessOrExit(err);
+ }
+
+ err = writer.Finalize();
+ SuccessOrExit(err);
+ written = writer.GetLengthWritten();
+
+exit:
+ return err;
+}
+
+// Helper method: read a TLV structure with a single tag and string and verify it matches expected string.
+CHIP_ERROR ReadAndVerifyTLVString(nlTestSuite * inSuite, void * inContext, const uint8_t * dataStart, uint32_t len,
+ const char * expected, uint16_t expectedLen)
+{
+ CHIP_ERROR err = CHIP_NO_ERROR;
+ TLV::TLVReader reader;
+ char tmp[64] = { 0 };
+ uint32_t readLength = 0;
+ VerifyOrExit(sizeof(tmp) > len, err = CHIP_ERROR_INTERNAL);
+
+ reader.Init(dataStart, len);
+ err = reader.Next();
+
+ VerifyOrExit(reader.GetTag() == tlvListTag, err = CHIP_ERROR_INTERNAL);
+
+ // Metadata must have a top-level list
+ {
+ TLV::TLVReader listReader;
+ err = reader.OpenContainer(listReader);
+ SuccessOrExit(err);
+
+ err = listReader.Next();
+ SuccessOrExit(err);
+
+ VerifyOrExit(listReader.GetTag() == tlvStrTag, err = CHIP_ERROR_INTERNAL);
+ readLength = listReader.GetLength();
+ VerifyOrExit(readLength == expectedLen, err = CHIP_ERROR_INTERNAL);
+ err = listReader.GetString(tmp, sizeof(tmp));
+ SuccessOrExit(err);
+ VerifyOrExit(!memcmp(expected, tmp, readLength), err = CHIP_ERROR_INTERNAL);
+
+ err = reader.CloseContainer(listReader);
+ SuccessOrExit(err);
+ }
+
+exit:
+ return err;
+}
+
+// Helper method for verifying that a PacketBufferHandle contains a valid BDX header and message type matches expected.
+void VerifyBdxMessageType(nlTestSuite * inSuite, void * inContext, const System::PacketBufferHandle & msg, MessageType expected)
+{
+ CHIP_ERROR err = CHIP_NO_ERROR;
+ uint16_t headerSize = 0;
+ PayloadHeader payloadHeader;
+
+ if (msg.IsNull())
+ {
+ NL_TEST_ASSERT(inSuite, false);
+ return;
+ }
+
+ err = payloadHeader.Decode(msg->Start(), msg->DataLength(), &headerSize);
+ NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
+ NL_TEST_ASSERT(inSuite, payloadHeader.GetProtocolID() == Protocols::kProtocol_BDX);
+ NL_TEST_ASSERT(inSuite, payloadHeader.GetMessageType() == expected);
+}
+
+// Helper method for verifying that a PacketBufferHandle contains a valid StatusReport message and contains a specific StatusCode.
+void VerifyStatusReport(nlTestSuite * inSuite, void * inContext, const System::PacketBufferHandle & msg, StatusCode code)
+{
+ CHIP_ERROR err = CHIP_NO_ERROR;
+ uint16_t headerSize = 0;
+ PayloadHeader payloadHeader;
+ uint16_t generalCode = 0;
+ uint32_t protocolId = 0;
+ uint16_t protocolCode = 0;
+
+ if (msg.IsNull())
+ {
+ NL_TEST_ASSERT(inSuite, false);
+ return;
+ }
+
+ err = payloadHeader.Decode(msg->Start(), msg->DataLength(), &headerSize);
+ NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
+ NL_TEST_ASSERT(inSuite, payloadHeader.GetProtocolID() == Protocols::kProtocol_Protocol_Common);
+ NL_TEST_ASSERT(inSuite, payloadHeader.GetMessageType() == static_cast<uint8_t>(Protocols::Common::MsgType::StatusReport));
+
+ Encoding::LittleEndian::Reader reader(msg->Start() + headerSize, msg->DataLength());
+ err = reader.Read16(&generalCode).Read32(&protocolId).Read16(&protocolCode).StatusCode();
+ NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
+ NL_TEST_ASSERT(inSuite, generalCode == static_cast<uint16_t>(Protocols::Common::StatusCode::Failure));
+ NL_TEST_ASSERT(inSuite, protocolId == Protocols::kProtocol_BDX);
+ NL_TEST_ASSERT(inSuite, protocolCode == code);
+}
+
+void VerifyNoMoreOutput(nlTestSuite * inSuite, void * inContext, TransferSession & transferSession)
+{
+ TransferSession::OutputEvent event;
+ transferSession.PollOutput(event, kNoAdvanceTime);
+ NL_TEST_ASSERT(inSuite, event.EventType == TransferSession::kNone);
+}
+
+// Helper method for initializing two TransferSession objects, generating a TransferInit message, and passing it to a responding
+// TransferSession.
+void SendAndVerifyTransferInit(nlTestSuite * inSuite, void * inContext, TransferSession::OutputEvent & outEvent, uint32_t timeoutMs,
+ TransferSession & initiator, TransferRole initiatorRole, TransferSession::TransferInitData initData,
+ TransferSession & responder, BitFlags<uint8_t, TransferControlFlags> & responderControlOpts,
+ uint16_t responderMaxBlock)
+{
+ CHIP_ERROR err = CHIP_NO_ERROR;
+ TransferRole responderRole = (initiatorRole == kRole_Sender) ? kRole_Receiver : kRole_Sender;
+ MessageType expectedInitMsg = (initiatorRole == kRole_Sender) ? kBdxMsg_SendInit : kBdxMsg_ReceiveInit;
+
+ // Initializer responder to wait for transfer
+ err = responder.WaitForTransfer(responderRole, responderControlOpts, responderMaxBlock, timeoutMs);
+ NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
+ VerifyNoMoreOutput(inSuite, inContext, responder);
+
+ // Verify initiator outputs respective Init message (depending on role) after StartTransfer()
+ err = initiator.StartTransfer(initiatorRole, initData, timeoutMs);
+ NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
+ initiator.PollOutput(outEvent, kNoAdvanceTime);
+ NL_TEST_ASSERT(inSuite, outEvent.EventType == TransferSession::kMsgToSend);
+ VerifyBdxMessageType(inSuite, inContext, outEvent.MsgData, expectedInitMsg);
+ VerifyNoMoreOutput(inSuite, inContext, initiator);
+
+ // Verify that all parsed TransferInit fields match what was sent by the initiator
+ err = responder.HandleMessageReceived(std::move(outEvent.MsgData), kNoAdvanceTime);
+ NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
+ responder.PollOutput(outEvent, kNoAdvanceTime);
+ VerifyNoMoreOutput(inSuite, inContext, responder);
+ NL_TEST_ASSERT(inSuite, outEvent.EventType == TransferSession::kInitReceived);
+ NL_TEST_ASSERT(inSuite, outEvent.transferInitData.TransferCtlFlagsRaw == initData.TransferCtlFlagsRaw);
+ NL_TEST_ASSERT(inSuite, outEvent.transferInitData.MaxBlockSize == initData.MaxBlockSize);
+ NL_TEST_ASSERT(inSuite, outEvent.transferInitData.StartOffset == initData.StartOffset);
+ NL_TEST_ASSERT(inSuite, outEvent.transferInitData.Length == initData.Length);
+ NL_TEST_ASSERT(inSuite, outEvent.transferInitData.FileDesignator != nullptr);
+ NL_TEST_ASSERT(inSuite, outEvent.transferInitData.FileDesLength == initData.FileDesLength);
+ if (outEvent.EventType == TransferSession::kInitReceived && outEvent.transferInitData.FileDesignator != nullptr)
+ {
+ NL_TEST_ASSERT(
+ inSuite,
+ !memcmp(initData.FileDesignator, outEvent.transferInitData.FileDesignator, outEvent.transferInitData.FileDesLength));
+ }
+ if (outEvent.transferInitData.Metadata != nullptr)
+ {
+ NL_TEST_ASSERT(inSuite, outEvent.transferInitData.MetadataLength == initData.MetadataLength);
+ if (outEvent.transferInitData.MetadataLength == initData.MetadataLength)
+ {
+ // 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.
+ NL_TEST_ASSERT(
+ inSuite, !memcmp(initData.Metadata, outEvent.transferInitData.Metadata, outEvent.transferInitData.MetadataLength));
+ }
+ else
+ {
+ NL_TEST_ASSERT(inSuite, false); // Metadata length mismatch
+ }
+ }
+}
+
+// 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(nlTestSuite * inSuite, void * inContext, 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 == kRole_Sender) ? kBdxMsg_ReceiveAccept : kBdxMsg_SendAccept;
+
+ err = acceptSender.AcceptTransfer(acceptData);
+ NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
+
+ // Verify Sender emits ReceiveAccept message for sending
+ acceptSender.PollOutput(outEvent, kNoAdvanceTime);
+ VerifyNoMoreOutput(inSuite, inContext, acceptSender);
+ NL_TEST_ASSERT(inSuite, outEvent.EventType == TransferSession::kMsgToSend);
+ VerifyBdxMessageType(inSuite, inContext, outEvent.MsgData, expectedMsg);
+
+ // Pass Accept message to acceptReceiver
+ err = acceptReceiver.HandleMessageReceived(std::move(outEvent.MsgData), kNoAdvanceTime);
+ NL_TEST_ASSERT(inSuite, 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(inSuite, inContext, acceptReceiver);
+ NL_TEST_ASSERT(inSuite, outEvent.EventType == TransferSession::kAcceptReceived);
+ NL_TEST_ASSERT(inSuite, outEvent.transferAcceptData.ControlMode == acceptData.ControlMode);
+ NL_TEST_ASSERT(inSuite, outEvent.transferAcceptData.MaxBlockSize == acceptData.MaxBlockSize);
+ NL_TEST_ASSERT(inSuite, outEvent.transferAcceptData.StartOffset == acceptData.StartOffset);
+ NL_TEST_ASSERT(inSuite, outEvent.transferAcceptData.Length == acceptData.Length);
+ if (outEvent.transferAcceptData.Metadata != nullptr)
+ {
+ NL_TEST_ASSERT(inSuite, outEvent.transferAcceptData.MetadataLength == acceptData.MetadataLength);
+ if (outEvent.transferAcceptData.MetadataLength == acceptData.MetadataLength)
+ {
+ // 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.
+ NL_TEST_ASSERT(
+ inSuite,
+ !memcmp(acceptData.Metadata, outEvent.transferAcceptData.Metadata, outEvent.transferAcceptData.MetadataLength));
+ }
+ else
+ {
+ NL_TEST_ASSERT(inSuite, false); // Metadata length mismatch
+ }
+ }
+
+ // Verify that MaxBlockSize was set appropriately
+ NL_TEST_ASSERT(inSuite, acceptReceiver.GetTransferBlockSize() <= initData.MaxBlockSize);
+}
+
+// Helper method for preparing a sending a BlockQuery message between two TransferSession objects.
+void SendAndVerifyQuery(nlTestSuite * inSuite, void * inContext, TransferSession & queryReceiver, TransferSession & querySender,
+ TransferSession::OutputEvent & outEvent)
+{
+ // Verify that querySender emits BlockQuery message
+ CHIP_ERROR err = querySender.PrepareBlockQuery();
+ NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
+ querySender.PollOutput(outEvent, kNoAdvanceTime);
+ NL_TEST_ASSERT(inSuite, outEvent.EventType == TransferSession::kMsgToSend);
+ VerifyBdxMessageType(inSuite, inContext, outEvent.MsgData, kBdxMsg_BlockQuery);
+ VerifyNoMoreOutput(inSuite, inContext, querySender);
+
+ // Pass BlockQuery to queryReceiver and verify queryReceiver emits QueryReceived event
+ err = queryReceiver.HandleMessageReceived(std::move(outEvent.MsgData), kNoAdvanceTime);
+ NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
+ queryReceiver.PollOutput(outEvent, kNoAdvanceTime);
+ NL_TEST_ASSERT(inSuite, outEvent.EventType == TransferSession::kQueryReceived);
+ VerifyNoMoreOutput(inSuite, inContext, 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(nlTestSuite * inSuite, void * inContext, TransferSession & sender, TransferSession & receiver,
+ TransferSession::OutputEvent & outEvent, bool isEof)
+{
+ CHIP_ERROR err = CHIP_NO_ERROR;
+ static uint8_t dataCount = 0;
+ uint16_t maxBlockSize = sender.GetTransferBlockSize();
+
+ NL_TEST_ASSERT(inSuite, maxBlockSize > 0);
+ System::PacketBufferHandle fakeDataBuf = System::PacketBufferHandle::New(maxBlockSize);
+ if (fakeDataBuf.IsNull())
+ {
+ NL_TEST_ASSERT(inSuite, false);
+ return;
+ }
+
+ uint8_t * fakeBlockData = fakeDataBuf->Start();
+ fakeBlockData[0] = dataCount++;
+
+ TransferSession::BlockData blockData;
+ blockData.Data = fakeBlockData;
+ blockData.Length = maxBlockSize;
+ blockData.IsEof = isEof;
+
+ MessageType expected = isEof ? kBdxMsg_BlockEOF : kBdxMsg_Block;
+
+ // Provide Block data and verify sender emits Block message
+ err = sender.PrepareBlock(blockData);
+ NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
+ sender.PollOutput(outEvent, kNoAdvanceTime);
+ NL_TEST_ASSERT(inSuite, outEvent.EventType == TransferSession::kMsgToSend);
+ VerifyBdxMessageType(inSuite, inContext, outEvent.MsgData, expected);
+ VerifyNoMoreOutput(inSuite, inContext, sender);
+
+ // Pass Block message to receiver and verify matching Block is received
+ err = receiver.HandleMessageReceived(std::move(outEvent.MsgData), kNoAdvanceTime);
+ NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
+ receiver.PollOutput(outEvent, kNoAdvanceTime);
+ NL_TEST_ASSERT(inSuite, outEvent.EventType == TransferSession::kBlockReceived);
+ NL_TEST_ASSERT(inSuite, outEvent.blockdata.Data != nullptr);
+ if (outEvent.EventType == TransferSession::kBlockReceived && outEvent.blockdata.Data != nullptr)
+ {
+ NL_TEST_ASSERT(inSuite, !memcmp(fakeBlockData, outEvent.blockdata.Data, outEvent.blockdata.Length));
+ }
+ VerifyNoMoreOutput(inSuite, inContext, receiver);
+}
+
+// Helper method for sending a BlockAck or BlockAckEOF, depending on the state of the receiver.
+void SendAndVerifyBlockAck(nlTestSuite * inSuite, void * inContext, TransferSession & ackReceiver, TransferSession & ackSender,
+ TransferSession::OutputEvent & outEvent, bool expectEOF)
+{
+ TransferSession::OutputEventType expectedEventType =
+ expectEOF ? TransferSession::kAckEOFReceived : TransferSession::kAckReceived;
+ MessageType expectedMsgType = expectEOF ? kBdxMsg_BlockAckEOF : kBdxMsg_BlockAck;
+
+ // Verify PrepareBlockAck() outputs message to send
+ CHIP_ERROR err = ackSender.PrepareBlockAck();
+ NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
+ ackSender.PollOutput(outEvent, kNoAdvanceTime);
+ NL_TEST_ASSERT(inSuite, outEvent.EventType == TransferSession::kMsgToSend);
+ VerifyBdxMessageType(inSuite, inContext, outEvent.MsgData, expectedMsgType);
+ VerifyNoMoreOutput(inSuite, inContext, ackSender);
+
+ // Pass BlockAck to ackReceiver and verify it was received
+ err = ackReceiver.HandleMessageReceived(std::move(outEvent.MsgData), kNoAdvanceTime);
+ NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
+ ackReceiver.PollOutput(outEvent, kNoAdvanceTime);
+ NL_TEST_ASSERT(inSuite, outEvent.EventType == expectedEventType);
+ VerifyNoMoreOutput(inSuite, inContext, ackReceiver);
+}
+
+// Test a full transfer using a responding receiver and an initiating sender, receiver drive.
+void TestInitiatingReceiverReceiverDrive(nlTestSuite * inSuite, void * inContext)
+{
+ 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;
+ uint32_t timeoutMs = 1000 * 24;
+
+ // Chosen specifically for this test
+ TransferControlFlags driveMode = kControl_ReceiverDrive;
+
+ // ReceiveInit parameters
+ TransferSession::TransferInitData initOptions;
+ initOptions.TransferCtlFlagsRaw = driveMode;
+ initOptions.MaxBlockSize = proposedBlockSize;
+ char testFileDes[9] = { "test.txt" };
+ initOptions.FileDesLength = static_cast<uint16_t>(strlen(testFileDes));
+ initOptions.FileDesignator = reinterpret_cast<uint8_t *>(testFileDes);
+
+ // Initialize respondingSender and pass ReceiveInit message
+ BitFlags<uint8_t, TransferControlFlags> senderOpts;
+ senderOpts.Set(driveMode);
+
+ SendAndVerifyTransferInit(inSuite, inContext, outEvent, timeoutMs, initiatingReceiver, kRole_Receiver, 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 = WriteChipTLVString(tlvBuf, sizeof(tlvBuf), metadataStr, bytesWritten);
+ NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
+ uint16_t metadataSize = static_cast<uint16_t>(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(inSuite, inContext, outEvent, respondingSender, kRole_Sender, acceptData, initiatingReceiver,
+ initOptions);
+
+ // Verify that MaxBlockSize was chosen correctly
+ NL_TEST_ASSERT(inSuite, respondingSender.GetTransferBlockSize() == testSmallerBlockSize);
+ NL_TEST_ASSERT(inSuite, respondingSender.GetTransferBlockSize() == initiatingReceiver.GetTransferBlockSize());
+
+ // Verify parsed TLV metadata matches the original
+ err =
+ ReadAndVerifyTLVString(inSuite, inContext, outEvent.transferAcceptData.Metadata, outEvent.transferAcceptData.MetadataLength,
+ metadataStr, static_cast<uint16_t>(strlen(metadataStr)));
+ NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
+
+ // Test BlockQuery -> Block -> BlockAck
+ SendAndVerifyQuery(inSuite, inContext, respondingSender, initiatingReceiver, outEvent);
+ SendAndVerifyArbitraryBlock(inSuite, inContext, respondingSender, initiatingReceiver, outEvent, false);
+ 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;
+ if (fakeBuf.IsNull())
+ {
+ NL_TEST_ASSERT(inSuite, false);
+ return;
+ }
+ prematureBlock.Data = fakeBuf->Start();
+ prematureBlock.Length = testSmallerBlockSize;
+ prematureBlock.IsEof = false;
+ err = respondingSender.PrepareBlock(prematureBlock);
+ NL_TEST_ASSERT(inSuite, err != CHIP_NO_ERROR);
+ VerifyNoMoreOutput(inSuite, inContext, respondingSender);
+
+ // Test Ack -> Query -> Block
+ SendAndVerifyBlockAck(inSuite, inContext, respondingSender, initiatingReceiver, outEvent, false);
+
+ // Test multiple Blocks sent and received (last Block is BlockEOF)
+ while (numBlocksSent < numBlockSends)
+ {
+ bool isEof = (numBlocksSent == numBlockSends - 1);
+
+ SendAndVerifyQuery(inSuite, inContext, respondingSender, initiatingReceiver, outEvent);
+ SendAndVerifyArbitraryBlock(inSuite, inContext, respondingSender, initiatingReceiver, outEvent, isEof);
+
+ numBlocksSent++;
+ }
+
+ // Verify last block was BlockEOF, then verify response BlockAckEOF message
+ NL_TEST_ASSERT(inSuite, outEvent.blockdata.IsEof == true);
+ SendAndVerifyBlockAck(inSuite, inContext, respondingSender, initiatingReceiver, outEvent, true);
+}
+
+// Partial transfer test using Sender Drive to specifically test Block -> BlockAck -> Block sequence
+void TestInitiatingSenderSenderDrive(nlTestSuite * inSuite, void * inContext)
+{
+ CHIP_ERROR err = CHIP_NO_ERROR;
+ TransferSession::OutputEvent outEvent;
+ TransferSession initiatingSender;
+ TransferSession respondingReceiver;
+
+ TransferControlFlags driveMode = kControl_SenderDrive;
+
+ // Chosen arbitrarily for this test
+ uint16_t transferBlockSize = 10;
+ uint32_t timeoutMs = 1000 * 24;
+
+ // Initialize respondingReceiver
+ BitFlags<uint8_t, TransferControlFlags> 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 = WriteChipTLVString(tlvBuf, sizeof(tlvBuf), metadataStr, bytesWritten);
+ NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
+ uint16_t metadataSize = static_cast<uint16_t>(bytesWritten & 0x0000FFFF);
+
+ // Initialize struct with TransferInit parameters
+ TransferSession::TransferInitData initOptions;
+ initOptions.TransferCtlFlagsRaw = driveMode;
+ initOptions.MaxBlockSize = transferBlockSize;
+ char testFileDes[9] = { "test.txt" };
+ initOptions.FileDesLength = static_cast<uint16_t>(strlen(testFileDes));
+ initOptions.FileDesignator = reinterpret_cast<uint8_t *>(testFileDes);
+ initOptions.Metadata = tlvBuf;
+ initOptions.MetadataLength = metadataSize;
+
+ SendAndVerifyTransferInit(inSuite, inContext, outEvent, timeoutMs, initiatingSender, kRole_Sender, initOptions,
+ respondingReceiver, receiverOpts, transferBlockSize);
+
+ // Verify parsed TLV metadata matches the original
+ err = ReadAndVerifyTLVString(inSuite, inContext, outEvent.transferInitData.Metadata, outEvent.transferInitData.MetadataLength,
+ metadataStr, static_cast<uint16_t>(strlen(metadataStr)));
+ NL_TEST_ASSERT(inSuite, 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(inSuite, inContext, outEvent, respondingReceiver, kRole_Receiver, acceptData, initiatingSender,
+ initOptions);
+
+ // Test multiple Block -> BlockAck -> Block
+ for (int i = 0; i < 3; i++)
+ {
+ SendAndVerifyArbitraryBlock(inSuite, inContext, initiatingSender, respondingReceiver, outEvent, false);
+ SendAndVerifyBlockAck(inSuite, inContext, initiatingSender, respondingReceiver, outEvent, false);
+ }
+
+ SendAndVerifyArbitraryBlock(inSuite, inContext, initiatingSender, respondingReceiver, outEvent, true);
+ SendAndVerifyBlockAck(inSuite, inContext, initiatingSender, respondingReceiver, outEvent, true);
+}
+
+// Test that calls to AcceptTransfer() with bad parameters result in an error.
+void TestBadAcceptMessageFields(nlTestSuite * inSuite, void * inContext)
+{
+ CHIP_ERROR err = CHIP_NO_ERROR;
+ TransferSession::OutputEvent outEvent;
+ TransferSession initiatingReceiver;
+ TransferSession respondingSender;
+
+ uint16_t maxBlockSize = 64;
+ TransferControlFlags driveMode = kControl_ReceiverDrive;
+ uint64_t commonLength = 0;
+ uint64_t commonOffset = 0;
+ uint32_t timeoutMs = 1000 * 24;
+
+ // Initialize struct with TransferInit parameters
+ TransferSession::TransferInitData initOptions;
+ initOptions.TransferCtlFlagsRaw = driveMode;
+ initOptions.MaxBlockSize = maxBlockSize;
+ initOptions.StartOffset = commonOffset;
+ initOptions.Length = commonLength;
+ char testFileDes[9] = { "test.txt" }; // arbitrary file designator
+ initOptions.FileDesLength = static_cast<uint16_t>(strlen(testFileDes));
+ initOptions.FileDesignator = reinterpret_cast<uint8_t *>(testFileDes);
+ initOptions.Metadata = nullptr;
+ initOptions.MetadataLength = 0;
+
+ // Responder parameters
+ BitFlags<uint8_t, TransferControlFlags> responderControl;
+ responderControl.Set(driveMode);
+
+ SendAndVerifyTransferInit(inSuite, inContext, outEvent, timeoutMs, initiatingReceiver, kRole_Receiver, initOptions,
+ respondingSender, responderControl, maxBlockSize);
+
+ // Verify AcceptTransfer() returns error for choosing larger max block size
+ TransferSession::TransferAcceptData acceptData;
+ acceptData.ControlMode = driveMode;
+ acceptData.MaxBlockSize = static_cast<uint16_t>(maxBlockSize + 1); // invalid if larger than proposed
+ acceptData.StartOffset = commonOffset;
+ acceptData.Length = commonLength;
+ err = respondingSender.AcceptTransfer(acceptData);
+ NL_TEST_ASSERT(inSuite, err != CHIP_NO_ERROR);
+
+ // Verify AcceptTransfer() returns error for choosing unsupported transfer control mode
+ TransferSession::TransferAcceptData acceptData2;
+ acceptData2.ControlMode = (driveMode == kControl_ReceiverDrive) ? kControl_SenderDrive : kControl_ReceiverDrive;
+ acceptData2.MaxBlockSize = maxBlockSize;
+ acceptData2.StartOffset = commonOffset;
+ acceptData2.Length = commonLength;
+ err = respondingSender.AcceptTransfer(acceptData2);
+ NL_TEST_ASSERT(inSuite, err != CHIP_NO_ERROR);
+}
+
+// Test that a TransferSession will emit kTransferTimeout if the specified timeout is exceeded while waiting for a response.
+void TestTimeout(nlTestSuite * inSuite, void * inContext)
+{
+ CHIP_ERROR err = CHIP_NO_ERROR;
+ TransferSession initiator;
+ TransferSession::OutputEvent outEvent;
+
+ uint32_t timeoutMs = 24;
+ uint64_t startTimeMs = 100;
+ uint64_t endTimeMs = 124;
+
+ // Initialize struct with arbitrary TransferInit parameters
+ TransferSession::TransferInitData initOptions;
+ initOptions.TransferCtlFlagsRaw = kControl_ReceiverDrive;
+ initOptions.MaxBlockSize = 64;
+ initOptions.StartOffset = 0;
+ initOptions.Length = 0;
+ char testFileDes[9] = { "test.txt" }; // arbitrary file designator
+ initOptions.FileDesLength = static_cast<uint16_t>(strlen(testFileDes));
+ initOptions.FileDesignator = reinterpret_cast<uint8_t *>(testFileDes);
+ initOptions.Metadata = nullptr;
+ initOptions.MetadataLength = 0;
+
+ TransferRole role = kRole_Receiver;
+
+ // Verify initiator outputs respective Init message (depending on role) after StartTransfer()
+ err = initiator.StartTransfer(role, initOptions, timeoutMs);
+ NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
+
+ // First PollOutput() should output the TransferInit message
+ initiator.PollOutput(outEvent, startTimeMs);
+ NL_TEST_ASSERT(inSuite, outEvent.EventType == TransferSession::kMsgToSend);
+ MessageType expectedInitMsg = (role == kRole_Sender) ? kBdxMsg_SendInit : kBdxMsg_ReceiveInit;
+ VerifyBdxMessageType(inSuite, inContext, outEvent.MsgData, expectedInitMsg);
+
+ // Second PollOutput() with no call to HandleMessageReceived() should result in a timeout.
+ initiator.PollOutput(outEvent, endTimeMs);
+ NL_TEST_ASSERT(inSuite, outEvent.EventType == TransferSession::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.
+void TestDuplicateBlockError(nlTestSuite * inSuite, void * inContext)
+{
+ 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;
+ uint32_t timeoutMs = 1000 * 24;
+
+ // Chosen specifically for this test
+ TransferControlFlags driveMode = kControl_ReceiverDrive;
+
+ // ReceiveInit parameters
+ TransferSession::TransferInitData initOptions;
+ initOptions.TransferCtlFlagsRaw = driveMode;
+ initOptions.MaxBlockSize = blockSize;
+ char testFileDes[9] = { "test.txt" };
+ initOptions.FileDesLength = static_cast<uint16_t>(strlen(testFileDes));
+ initOptions.FileDesignator = reinterpret_cast<uint8_t *>(testFileDes);
+
+ // Initialize respondingSender and pass ReceiveInit message
+ BitFlags<uint8_t, TransferControlFlags> senderOpts;
+ senderOpts.Set(driveMode);
+
+ SendAndVerifyTransferInit(inSuite, inContext, outEvent, timeoutMs, initiatingReceiver, kRole_Receiver, 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(inSuite, inContext, outEvent, respondingSender, kRole_Sender, acceptData, initiatingReceiver,
+ initOptions);
+
+ SendAndVerifyQuery(inSuite, inContext, 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);
+ NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
+ respondingSender.PollOutput(eventWithBlock, kNoAdvanceTime);
+ NL_TEST_ASSERT(inSuite, eventWithBlock.EventType == TransferSession::kMsgToSend);
+ VerifyBdxMessageType(inSuite, inContext, eventWithBlock.MsgData, kBdxMsg_Block);
+ VerifyNoMoreOutput(inSuite, inContext, 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 = initiatingReceiver.HandleMessageReceived(std::move(eventWithBlock.MsgData), kNoAdvanceTime);
+ NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
+ initiatingReceiver.PollOutput(outEvent, kNoAdvanceTime);
+ NL_TEST_ASSERT(inSuite, outEvent.EventType == TransferSession::kBlockReceived);
+ NL_TEST_ASSERT(inSuite, outEvent.blockdata.Data != nullptr);
+ VerifyNoMoreOutput(inSuite, inContext, initiatingReceiver);
+
+ SendAndVerifyQuery(inSuite, inContext, respondingSender, initiatingReceiver, outEvent);
+
+ // Verify receiving same Block twice fails and results in StatusReport event, and then InternalError event
+ err = initiatingReceiver.HandleMessageReceived(std::move(blockCopy), kNoAdvanceTime);
+ NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
+ initiatingReceiver.PollOutput(outEvent, kNoAdvanceTime);
+ NL_TEST_ASSERT(inSuite, outEvent.EventType == TransferSession::kMsgToSend);
+ System::PacketBufferHandle statusReportMsg = outEvent.MsgData.Retain();
+ VerifyStatusReport(inSuite, inContext, std::move(outEvent.MsgData), kStatus_BadBlockCounter);
+
+ // All subsequent PollOutput() calls should return kInternalError
+ for (int i = 0; i < 5; ++i)
+ {
+ initiatingReceiver.PollOutput(outEvent, kNoAdvanceTime);
+ NL_TEST_ASSERT(inSuite, outEvent.EventType == TransferSession::kInternalError);
+ NL_TEST_ASSERT(inSuite, outEvent.statusData.StatusCode == kStatus_BadBlockCounter);
+ }
+
+ err = respondingSender.HandleMessageReceived(std::move(statusReportMsg), kNoAdvanceTime);
+ NL_TEST_ASSERT(inSuite, err == CHIP_NO_ERROR);
+ respondingSender.PollOutput(outEvent, kNoAdvanceTime);
+ NL_TEST_ASSERT(inSuite, outEvent.EventType == TransferSession::kStatusReceived);
+ NL_TEST_ASSERT(inSuite, outEvent.statusData.StatusCode == kStatus_BadBlockCounter);
+
+ // All subsequent PollOutput() calls should return kInternalError
+ for (int i = 0; i < 5; ++i)
+ {
+ respondingSender.PollOutput(outEvent, kNoAdvanceTime);
+ NL_TEST_ASSERT(inSuite, outEvent.EventType == TransferSession::kInternalError);
+ NL_TEST_ASSERT(inSuite, outEvent.statusData.StatusCode == kStatus_BadBlockCounter);
+ }
+}
+
+// Test Suite
+
+/**
+ * Test Suite that lists all the test functions.
+ */
+// clang-format off
+static const nlTest sTests[] =
+{
+ NL_TEST_DEF("TestInitiatingReceiverReceiverDrive", TestInitiatingReceiverReceiverDrive),
+ NL_TEST_DEF("TestInitiatingSenderSenderDrive", TestInitiatingSenderSenderDrive),
+ NL_TEST_DEF("TestBadAcceptMessageFields", TestBadAcceptMessageFields),
+ NL_TEST_DEF("TestTimeout", TestTimeout),
+ NL_TEST_DEF("TestDuplicateBlockError", TestDuplicateBlockError),
+ NL_TEST_SENTINEL()
+};
+// clang-format on
+
+// clang-format off
+static nlTestSuite sSuite =
+{
+ "Test-CHIP-TransferSession",
+ &sTests[0],
+ nullptr,
+ nullptr
+};
+// clang-format on
+
+/**
+ * Main
+ */
+int TestBdxTransferSession()
+{
+ // Run test suit against one context
+ nlTestRunner(&sSuite, nullptr);
+
+ return (nlTestRunnerStats(&sSuite));
+}
+
+CHIP_REGISTER_TEST_SUITE(TestBdxTransferSession)