| /** |
| * @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 <lib/core/CHIPError.h> |
| #include <protocols/bdx/BdxMessages.h> |
| #include <system/SystemClock.h> |
| #include <system/SystemPacketBuffer.h> |
| #include <transport/raw/MessageHeader.h> |
| |
| #include <type_traits> |
| |
| namespace chip { |
| namespace bdx { |
| |
| enum class TransferRole : uint8_t |
| { |
| kReceiver = 0, |
| kSender = 1, |
| }; |
| |
| class DLL_EXPORT TransferSession |
| { |
| public: |
| enum class OutputEventType : uint16_t |
| { |
| kNone = 0, |
| kMsgToSend, |
| kInitReceived, |
| kAcceptReceived, |
| kBlockReceived, |
| kQueryReceived, |
| kQueryWithSkipReceived, |
| kAckReceived, |
| kAckEOFReceived, |
| kStatusReceived, |
| kInternalError, |
| kTransferTimeout |
| }; |
| |
| struct TransferInitData |
| { |
| TransferControlFlags TransferCtlFlags; |
| |
| 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; |
| size_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; |
| size_t MetadataLength = 0; |
| }; |
| |
| struct StatusReportData |
| { |
| StatusCode statusCode; |
| }; |
| |
| struct BlockData |
| { |
| const uint8_t * Data = nullptr; |
| size_t Length = 0; |
| bool IsEof = false; |
| uint32_t BlockCounter = 0; |
| }; |
| |
| struct MessageTypeData |
| { |
| Protocols::Id ProtocolId; // Should only ever be SecureChannel or BDX |
| uint8_t MessageType; |
| |
| MessageTypeData() : ProtocolId(Protocols::NotSpecified), MessageType(0) {} |
| |
| bool HasProtocol(Protocols::Id protocol) const { return ProtocolId == protocol; } |
| bool HasMessageType(uint8_t type) const { return MessageType == type; } |
| template <typename TMessageType, typename = std::enable_if_t<std::is_enum<TMessageType>::value>> |
| bool HasMessageType(TMessageType type) const |
| { |
| return HasProtocol(Protocols::MessageTypeTraits<TMessageType>::ProtocolId()) && HasMessageType(to_underlying(type)); |
| } |
| }; |
| |
| struct TransferSkipData |
| { |
| uint64_t BytesToSkip = 0; |
| }; |
| |
| /** |
| * @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 (see Blockdata). In this case, the MsgData field MUST |
| * be populated with a PacketBufferHandle that encapsulates the respective PacketBuffer, in order to ensure valid memory |
| * access. |
| * |
| * NOTE: MsgData can contain messages that have been received or messages that should be sent by the caller. The underlying |
| * buffer will always start at the data, never at the payload header. Outgoing messages do not have a header prepended. |
| */ |
| struct OutputEvent |
| { |
| OutputEventType EventType; |
| System::PacketBufferHandle MsgData; |
| union |
| { |
| TransferInitData transferInitData; |
| TransferAcceptData transferAcceptData; |
| BlockData blockdata; |
| StatusReportData statusData; |
| MessageTypeData msgTypeData; |
| TransferSkipData bytesToSkip; |
| }; |
| |
| OutputEvent() : EventType(OutputEventType::kNone) { statusData = { StatusCode::kUnknown }; } |
| OutputEvent(OutputEventType type) : EventType(type) { statusData = { StatusCode::kUnknown }; } |
| |
| const char * ToString(OutputEventType outputEventType); |
| |
| 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); |
| static OutputEvent MsgToSendEvent(MessageTypeData typeData, System::PacketBufferHandle msg); |
| static OutputEvent QueryWithSkipEvent(TransferSkipData bytesToSkip); |
| }; |
| |
| /** |
| * @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, the caller is expected to send the message immediately, and the session |
| * timeout timer will begin at curTime. |
| * |
| * 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 curTime Current time |
| */ |
| void PollOutput(OutputEvent & event, System::Clock::Timestamp curTime); |
| |
| /** |
| * @brief |
| * Gets the pending output event from the transfer session in the event param passed in by the caller. |
| * The output event may contain some data for the caller to act upon. If there is no pending output event, |
| * the caller will get an event of type OutputEventType::kNone. |
| * |
| * It is possible that consecutive calls to this method may emit different outputs depending on the state of the |
| * TransferSession object. The caller is generally expected to keep calling this method until it gets an event of type |
| * OutputEventType::kNone. |
| * |
| * If the output event type is kMsgToSend, the caller is expected to send the message immediately on the |
| * relevant exchange. In this case the BDX session timeout timer will start when GetNextAction is called. |
| * |
| * See OutputEventType for all possible output event types. |
| * |
| * @param event Reference to an OutputEvent struct that will be filled out with any pending output event data |
| */ |
| void GetNextAction(OutputEvent & event); |
| |
| /** |
| * @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 timeout The amount of time to wait for a response before considering the transfer failed |
| * |
| * @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, System::Clock::Timeout timeout); |
| |
| /** |
| * @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 timeout The amount of time to wait for a response before considering the transfer failed |
| * |
| * @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<TransferControlFlags> xferControlOpts, uint16_t maxBlockSize, |
| System::Clock::Timeout timeout); |
| |
| /** |
| * @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 BlockQueryWithSkip message. The Block counter will be populated automatically. |
| * |
| * @param bytesToSkip Number of bytes to seek skip |
| * |
| * @return CHIP_ERROR The result of the preparation of a BlockQueryWithSkip message. May also indicate if the TransferSession |
| * object is unable to handle this request. |
| */ |
| CHIP_ERROR PrepareBlockQueryWithSkip(const uint64_t & bytesToSkip); |
| |
| /** |
| * @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 payloadHeader A PayloadHeader containing the Protocol type and Message Type |
| * @param msg A PacketBufferHandle pointing to the message buffer to process. May be BDX or StatusReport protocol. |
| * Buffer is expected to start at data (not header). |
| * @param curTime Current time |
| * |
| * @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(const PayloadHeader & payloadHeader, System::PacketBufferHandle msg, |
| System::Clock::Timestamp curTime); |
| |
| TransferControlFlags GetControlMode() const { return mControlMode; } |
| uint64_t GetStartOffset() const { return mStartOffset; } |
| uint64_t GetTransferLength() const { return mTransferLength; } |
| uint16_t GetTransferBlockSize() const { return mTransferMaxBlockSize; } |
| uint32_t GetNextBlockNum() const { return mNextBlockNum; } |
| uint32_t GetNextQueryNum() const { return mNextQueryNum; } |
| size_t GetNumBytesProcessed() const { return mNumBytesProcessed; } |
| const uint8_t * GetFileDesignator(uint16_t & fileDesignatorLen) const |
| { |
| fileDesignatorLen = mTransferRequestData.FileDesLength; |
| return mTransferRequestData.FileDesignator; |
| } |
| |
| TransferSession(); |
| |
| private: |
| enum class TransferState : uint8_t |
| { |
| kUnitialized, |
| kAwaitingInitMsg, |
| kAwaitingAccept, |
| kNegotiateTransferParams, |
| kTransferInProgress, |
| kAwaitingEOFAck, |
| kReceivedEOF, |
| kTransferDone, |
| kErrorState, |
| }; |
| |
| // Incoming message handlers |
| CHIP_ERROR HandleBdxMessage(const PayloadHeader & header, System::PacketBufferHandle msg); |
| CHIP_ERROR HandleStatusReportMessage(const 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 HandleBlockQueryWithSkip(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<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<TransferControlFlags> & proposed); |
| |
| void PrepareStatusReport(StatusCode code); |
| bool IsTransferLengthDefinite() const; |
| |
| OutputEventType mPendingOutput = OutputEventType::kNone; |
| TransferState mState = TransferState::kUnitialized; |
| TransferRole mRole; |
| |
| // Indicate supported options pre- transfer accept |
| BitFlags<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; |
| |
| // Used to store event data before it is emitted via PollOutput() |
| System::PacketBufferHandle mPendingMsgHandle; |
| StatusReportData mStatusReportData; |
| TransferInitData mTransferRequestData; |
| TransferAcceptData mTransferAcceptData; |
| BlockData mBlockEventData; |
| MessageTypeData mMsgTypeData; |
| TransferSkipData mBytesToSkip; |
| |
| size_t mNumBytesProcessed = 0; |
| |
| uint32_t mLastBlockNum = 0; |
| uint32_t mNextBlockNum = 0; |
| uint32_t mLastQueryNum = 0; |
| uint32_t mNextQueryNum = 0; |
| |
| System::Clock::Timeout mTimeout = System::Clock::kZero; |
| System::Clock::Timestamp mTimeoutStartTime = System::Clock::kZero; |
| bool mShouldInitTimeoutStart = true; |
| bool mAwaitingResponse = false; |
| }; |
| |
| } // namespace bdx |
| } // namespace chip |