blob: 0a399b24564e9635d1dd79c935c6d27901ad2b3c [file] [log] [blame]
/*
* Copyright (c) 2024 Project CHIP Authors
* All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#pragma once
#include <app/MessageDef/StatusIB.h>
#include <app/data-model/WrappedStructEncoder.h>
#include <lib/core/CHIPError.h>
namespace chip {
namespace app {
namespace InteractionModel {
/// Handles encoding of an invoke response for a specific invoke request.
///
/// This class handles a single response (i.e. a CommandDataIB within the
/// matter protocol) and is responsible for constructing its corresponding
/// response (i.e. a InvokeResponseIB within the matter protocol)
///
/// Invoke responses MUST contain exactly ONE of:
/// - response data (accessed via `ResponseEncoder`)
/// - A status, which may be success or failure, both of which may
/// contain a cluster-specific error code.
///
/// To encode a response, `Complete` MUST be called.
///
/// `Complete` requirements
/// - Complete with InteractionModel::Status::Success will respond with data
/// some response data was written.
/// - Any other case (including success with cluster specific codes) implies
/// no response data and a status will be encoded instead
/// - this includes the case when some response data was written already.
/// In that case, the response data will be rolled back and only the status
/// will be encoded.
///
/// Creating a response MAY be retried at most once, if and only if `Complete`
/// returns CHIP_ERROR_BUFFER_TOO_SMALL. Retry attempts MUST not exceed 1:
/// - FlushPendingResponses MUST be called to make as much buffer space as possible
/// available for encoding
/// - The response encoding (including `ResponseEncoder` usage and calling Complete)
/// MUST be retried once more. If the final Complete returns an error, the result
/// of the invoke will be an error status.
///
class InvokeResponder
{
public:
virtual ~InvokeResponder() = default;
// Copying not allowed since underlying requirement is that on deletion of this
// object, a reply will be sent.
InvokeResponder(const InvokeResponder &) = delete;
InvokeResponder & operator=(const InvokeResponder &) = delete;
/// Flush any pending replies before encoding the current reply.
///
/// MAY be called at most once.
///
/// This function is intended to provided the ability to retry sending a reply
/// if a reply encoding fails due to insufficient buffer.
///
/// Call this if `Complete(...)` returns CHIP_ERROR_BUFFER_TOO_SMALL and try
/// again. If reply data is needed, the complete ResponseEncoder + Complete
/// call chain MUST be re-run.
virtual CHIP_ERROR FlushPendingResponses() = 0;
/// Reply with a data payload.
///
/// MUST be called at most once per reply.
/// Can be called a 2nd time after a `FlushPendingResponses()` call
///
/// - responseCommandId must correspond with the data encoded in the returned encoder
/// - Complete(CHIP_NO_ERROR) MUST be called to flush the reply
///
/// If encoder returns CHIP_ERROR_BUFFER_TOO_SMALL, FlushPendingResponses should be
/// used to attempt to free up buffer space then encoding should be tried again.
virtual DataModel::WrappedStructEncoder & ResponseEncoder(CommandId responseCommandId) = 0;
/// Signal completing of the reply.
///
/// MUST be called exactly once to signal a response is to be recorded to be sent.
/// The error code (and the data encoded by ResponseEncoder) may be buffered for
/// sending among other batched responses.
///
/// If this returns CHIP_ERROR_BUFFER_TOO_SMALL, this can be called a 2nd time after
/// a FlushPendingResponses.
///
/// Argument behavior:
/// - Commands can only be replied with ONE of the following (spec 8.9.4.4):
/// - command data (i.e. ResponseEncoder contents)
/// - A status (including success/error/cluster-specific-success-or-error )
/// - As a result there are two possible paths:
/// - IF a Status::Success is given (WITHOUT cluster specific status), then
/// the data in ResponseEncoder is sent as a reply. If no data was sent,
/// a invoke `Status::Success` with no cluster specific data is sent
/// - OTHERWISE any previously encoded data via ResponseEncoder is discarded
/// and the given reply (success with cluster status or failure) is sent
/// as a reply to the invoke.
///
///
/// Returns success/failure state. One error code MUST be handled in particular:
///
/// - CHIP_ERROR_BUFFER_TOO_SMALL will return IF AND ONLY IF the responder was unable
/// to fully serialize the given reply/error data.
///
/// If such an error is returned, the caller MUST retry by calling FlushPendingResponses
/// first and then re-encoding the reply content (use ResponseEncoder if applicable and
/// call Complete again)
///
/// - Any other error (i.e. different from CHIP_NO_ERROR) mean that the invoke response
/// will contain an error and such an error is considered permanent.
///
virtual CHIP_ERROR Complete(StatusIB error) = 0;
};
/// Enforces that once acquired, Complete will be called on the underlying writer
class AutoCompleteInvokeResponder
{
public:
// non-copyable: once you have a handle, keep it
AutoCompleteInvokeResponder(const AutoCompleteInvokeResponder &) = delete;
AutoCompleteInvokeResponder & operator=(const AutoCompleteInvokeResponder &) = delete;
AutoCompleteInvokeResponder(InvokeResponder * writer) : mWriter(writer) {}
~AutoCompleteInvokeResponder()
{
if (mCompleteState != CompleteState::kComplete)
{
mWriter->Complete(Protocols::InteractionModel::Status::Failure);
}
}
/// Direct access to reply encoding.
///
/// Use this only in conjunction with the other Raw* calls
DataModel::WrappedStructEncoder & RawResponseEncoder(CommandId replyCommandId)
{
return mWriter->ResponseEncoder(replyCommandId);
}
/// Direct access to flushing replies
///
/// Use this only in conjunction with the other Raw* calls
CHIP_ERROR RawFlushPendingReplies()
{
// allow a flush if we never called it (this may not be reasonable, however
// we accept an early flush) or if flush is expected
VerifyOrReturnError((mCompleteState == CompleteState::kNeverCalled) || (mCompleteState == CompleteState::kFlushExpected),
CHIP_ERROR_INCORRECT_STATE);
mCompleteState = CompleteState::kFlushed;
return mWriter->FlushPendingResponses();
}
/// Call "Complete" without the automatic retries.
///
/// Use this in conjunction with the other Raw* calls
CHIP_ERROR RawComplete(StatusIB status)
{
VerifyOrReturnError((mCompleteState == CompleteState::kNeverCalled) || (mCompleteState == CompleteState::kFlushed),
CHIP_ERROR_INCORRECT_STATE);
CHIP_ERROR err = mWriter->Complete(status);
if ((err == CHIP_ERROR_BUFFER_TOO_SMALL) && (mCompleteState == CompleteState::kNeverCalled))
{
mCompleteState = CompleteState::kFlushExpected;
}
else
{
mCompleteState = CompleteState::kComplete;
}
return err;
}
/// Complete the given command.
///
/// Automatically handles retries for sending.
/// Cannot be called after Raw* methods are used.
///
/// Any error returned by this are final and not retriable
/// as a retry for CHIP_ERROR_BUFFER_TOO_SMALL is already built in.
CHIP_ERROR Complete(StatusIB status)
{
VerifyOrReturnError(mCompleteState == CompleteState::kNeverCalled, CHIP_ERROR_INCORRECT_STATE);
// this is a final complete, including retry handling
mCompleteState = CompleteState::kComplete;
CHIP_ERROR err = mWriter->Complete(status);
if (err != CHIP_ERROR_BUFFER_TOO_SMALL)
{
return err;
}
// retry once. Failure to flush is permanent.
ReturnErrorOnFailure(mWriter->FlushPendingResponses());
return mWriter->Complete(status);
}
/// Sends the specified data structure as a response
///
/// This version of the send has built-in RETRY and handles
/// Flush/Complete automatically.
/// Cannot be called after Raw* methods are used.
///
/// Any error returned by this are final and not retriable
/// as a retry for CHIP_ERROR_BUFFER_TOO_SMALL is already built in.
template <typename ReplyData>
CHIP_ERROR Send(const ReplyData & data)
{
VerifyOrReturnError(mCompleteState == CompleteState::kNeverCalled, CHIP_ERROR_INCORRECT_STATE);
// this is a final complete, including retry handling
mCompleteState = CompleteState::kComplete;
CHIP_ERROR err = data.Encode(ResponseEncoder(ReplyData::GetCommandId()));
if (err != CHIP_ERROR_BUFFER_TOO_SMALL)
{
LogErrorOnFailure(err);
err = mWriter->Complete(StatusIB(err));
}
if (err != CHIP_ERROR_BUFFER_TOO_SMALL)
{
return err;
}
// retry once. Failure to flush is permanent.
ReturnErrorOnFailure(mWriter->FlushPendingResponses());
err = data.Encode(ResponseEncoder(ReplyData::GetCommandId()));
// If encoding fails, we will end up sending an error back to the other side
// the caller
LogErrorOnFailure(err);
if (err == CHIP_NO_ERROR)
{
err = mWriter->Complete(StatusIB(err));
}
else
{
// Error in "complete" is not something we can really forward anymore since
// we already got an error in Encode ... just log this.
LogErrorOnFailure(mWriter->Complete(StatusIB(err)));
}
return err;
}
private:
// Contract says that complete may only be called twice:
// - initial complete
// - again after a `Flush`
// The states here expect we are in:
//
// +----------------------------Flush---------|
// | v
// NEVER --Complete--> F_EXPECTED --Flush--> FLUSHED --Complete--> COMPLETE
// | ^
// +-------------(success or permanent error)-----------|
enum class CompleteState
{
kNeverCalled,
kFlushExpected,
kFlushed,
kComplete,
};
InvokeResponder * mWriter;
CompleteState mCompleteState = CompleteState::kNeverCalled;
};
enum ReplyAsyncFlags
{
// Some commands that are expensive to process (e.g. crypto).
// Implementations may choose to send an ack on the message right away to
// avoid MRP retransmits.
kSlowCommandHandling = 0x0001,
};
class InvokeReply
{
public:
virtual ~InvokeReply() = default;
// reply with no data
CHIP_ERROR Reply(StatusIB status) { return this->Reply().Complete(status); }
// Enqueue the content of the reply at this point in time (rather than Async sending it).
//
// Implementations will often batch several replies into one packet for batch commands,
// so it will be implementation-specific on when the actual reply packet is
// sent.
virtual AutoCompleteInvokeResponder Reply() = 0;
// Reply "later" to the command. This allows async processing. A reply will be forced
// when the returned InvokeReply is destroyed.
//
// NOTE: Each InvokeReply is associated with a separate `CommandDataIB` within batch
// commands. When replying asynchronously, each InvokeReply will set the response
// data for the given commandpath/ref only.
//
// IF empty pointer is returned, insufficient memory to reply async is available and
// this should be handled (e.g. by returning an error to the handler/replying with
// an errorcode synchronously).
virtual std::unique_ptr<InvokeReply> ReplyAsync(BitFlags<ReplyAsyncFlags> flags) = 0;
};
} // namespace InteractionModel
} // namespace app
} // namespace chip