| /* |
| * 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 |