blob: 4a50e70fbb6c84b443d4df4ed30b20f5c747976e [file] [log] [blame]
/**
* @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