Line data Source code
1 : /*
2 : * Copyright (c) 2024 Project CHIP Authors
3 : * All rights reserved.
4 : *
5 : * Licensed under the Apache License, Version 2.0 (the "License");
6 : * you may not use this file except in compliance with the License.
7 : * You may obtain a copy of the License at
8 : *
9 : * http://www.apache.org/licenses/LICENSE-2.0
10 : *
11 : * Unless required by applicable law or agreed to in writing, software
12 : * distributed under the License is distributed on an "AS IS" BASIS,
13 : * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 : * See the License for the specific language governing permissions and
15 : * limitations under the License.
16 : */
17 :
18 : #include "CommandResponseSender.h"
19 : #include "InteractionModelEngine.h"
20 : #include "messaging/ExchangeContext.h"
21 :
22 : namespace chip {
23 : namespace app {
24 : using Status = Protocols::InteractionModel::Status;
25 :
26 0 : CHIP_ERROR CommandResponseSender::OnMessageReceived(Messaging::ExchangeContext * apExchangeContext,
27 : const PayloadHeader & aPayloadHeader, System::PacketBufferHandle && aPayload)
28 : {
29 0 : CHIP_ERROR err = CHIP_NO_ERROR;
30 0 : Optional<Status> failureStatusToSend;
31 :
32 0 : if (mState == State::AwaitingStatusResponse &&
33 0 : aPayloadHeader.HasMessageType(Protocols::InteractionModel::MsgType::StatusResponse))
34 : {
35 0 : CHIP_ERROR statusError = CHIP_NO_ERROR;
36 0 : err = StatusResponse::ProcessStatusResponse(std::move(aPayload), statusError);
37 0 : VerifyOrExit(err == CHIP_NO_ERROR, failureStatusToSend.SetValue(Status::InvalidAction));
38 0 : err = statusError;
39 0 : VerifyOrExit(err == CHIP_NO_ERROR, failureStatusToSend.SetValue(Status::InvalidAction));
40 :
41 0 : err = SendCommandResponse();
42 : // If SendCommandResponse() fails, we must close the exchange. We signal the failure to the
43 : // requester with a StatusResponse ('Failure'). Since we're in the middle of processing an
44 : // incoming message, we close the exchange by indicating that we don't expect a further response.
45 0 : VerifyOrExit(err == CHIP_NO_ERROR, failureStatusToSend.SetValue(Status::Failure));
46 :
47 0 : bool moreToSend = !mChunks.IsNull();
48 0 : if (!moreToSend)
49 : {
50 : // We are sending the final message and do not anticipate any further responses. We are
51 : // calling ExitNow() to immediately execute Close() and subsequently return from this function.
52 0 : ExitNow();
53 : }
54 0 : return CHIP_NO_ERROR;
55 : }
56 :
57 0 : ChipLogDetail(DataManagement, "CommandResponseSender: Unexpected message type %d", aPayloadHeader.GetMessageType());
58 0 : err = CHIP_ERROR_INVALID_MESSAGE_TYPE;
59 0 : if (mState != State::AllInvokeResponsesSent)
60 : {
61 0 : failureStatusToSend.SetValue(Status::Failure);
62 0 : ExitNow();
63 : }
64 0 : StatusResponse::Send(Status::InvalidAction, mExchangeCtx.Get(), false /*aExpectResponse*/);
65 0 : return err;
66 0 : exit:
67 0 : if (failureStatusToSend.HasValue())
68 : {
69 0 : StatusResponse::Send(failureStatusToSend.Value(), mExchangeCtx.Get(), false /*aExpectResponse*/);
70 : }
71 0 : Close();
72 0 : return err;
73 0 : }
74 :
75 0 : void CommandResponseSender::OnResponseTimeout(Messaging::ExchangeContext * apExchangeContext)
76 : {
77 0 : ChipLogDetail(DataManagement, "CommandResponseSender: Timed out waiting for response from requester mState=[%10.10s]",
78 : GetStateStr());
79 0 : Close();
80 0 : }
81 :
82 30 : void CommandResponseSender::StartSendingCommandResponses()
83 : {
84 30 : VerifyOrDie(mState == State::ReadyForInvokeResponses);
85 30 : CHIP_ERROR err = SendCommandResponse();
86 30 : if (err != CHIP_NO_ERROR)
87 : {
88 2 : ChipLogError(DataManagement, "Failed to send InvokeResponseMessage");
89 : // TODO(#30453): It should be our responsibility to send a Failure StatusResponse to the requestor
90 : // if there is a SessionHandle, but legacy unit tests explicitly check the behavior where
91 : // we do not send any message. Changing this behavior should be done in a standalone
92 : // PR where only that specific change is made. Here is a possible solution that could
93 : // be used that fulfills our responsibility to send a Failure StatusResponse. This causes unit
94 : // tests to start failing.
95 : // ```
96 : // if (mExchangeCtx && mExchangeCtx->HasSessionHandle())
97 : // {
98 : // SendStatusResponse(Status::Failure);
99 : // }
100 : // ```
101 2 : Close();
102 2 : return;
103 : }
104 :
105 28 : if (HasMoreToSend())
106 : {
107 0 : MoveToState(State::AwaitingStatusResponse);
108 0 : mExchangeCtx->SetDelegate(this);
109 : }
110 : else
111 : {
112 28 : Close();
113 : }
114 : }
115 :
116 33 : void CommandResponseSender::OnDone(CommandHandlerImpl & apCommandObj)
117 : {
118 33 : if (mState == State::ErrorSentDelayCloseUntilOnDone)
119 : {
120 : // We have already sent a message to the client indicating that we are not expecting
121 : // a response.
122 3 : Close();
123 3 : return;
124 : }
125 30 : StartSendingCommandResponses();
126 : }
127 :
128 23 : void CommandResponseSender::DispatchCommand(CommandHandlerImpl & apCommandObj, const ConcreteCommandPath & aCommandPath,
129 : TLV::TLVReader & apPayload)
130 : {
131 23 : VerifyOrReturn(mpCommandHandlerCallback);
132 23 : mpCommandHandlerCallback->DispatchCommand(apCommandObj, aCommandPath, apPayload);
133 : }
134 :
135 30 : Protocols::InteractionModel::Status CommandResponseSender::ValidateCommandCanBeDispatched(const DataModel::InvokeRequest & request)
136 : {
137 30 : VerifyOrReturnValue(mpCommandHandlerCallback, Protocols::InteractionModel::Status::Failure);
138 30 : return mpCommandHandlerCallback->ValidateCommandCanBeDispatched(request);
139 : }
140 :
141 30 : CHIP_ERROR CommandResponseSender::SendCommandResponse()
142 : {
143 30 : VerifyOrReturnError(HasMoreToSend(), CHIP_ERROR_INCORRECT_STATE);
144 29 : if (mChunks.IsNull())
145 : {
146 0 : VerifyOrReturnError(mReportResponseDropped, CHIP_ERROR_INCORRECT_STATE);
147 0 : SendStatusResponse(Status::ResourceExhausted);
148 0 : mReportResponseDropped = false;
149 0 : return CHIP_NO_ERROR;
150 : }
151 29 : System::PacketBufferHandle commandResponsePayload = mChunks.PopHead();
152 :
153 29 : Messaging::SendFlags sendFlag = Messaging::SendMessageFlags::kNone;
154 29 : if (HasMoreToSend())
155 : {
156 0 : sendFlag = Messaging::SendMessageFlags::kExpectResponse;
157 0 : mExchangeCtx->UseSuggestedResponseTimeout(app::kExpectedIMProcessingTime);
158 : }
159 :
160 29 : ReturnErrorOnFailure(mExchangeCtx->SendMessage(Protocols::InteractionModel::MsgType::InvokeCommandResponse,
161 : std::move(commandResponsePayload), sendFlag));
162 :
163 28 : return CHIP_NO_ERROR;
164 29 : }
165 :
166 36 : const char * CommandResponseSender::GetStateStr() const
167 : {
168 : #if CHIP_DETAIL_LOGGING
169 36 : switch (mState)
170 : {
171 0 : case State::ReadyForInvokeResponses:
172 0 : return "ReadyForInvokeResponses";
173 :
174 0 : case State::AwaitingStatusResponse:
175 0 : return "AwaitingStatusResponse";
176 :
177 33 : case State::AllInvokeResponsesSent:
178 33 : return "AllInvokeResponsesSent";
179 :
180 3 : case State::ErrorSentDelayCloseUntilOnDone:
181 3 : return "ErrorSentDelayCloseUntilOnDone";
182 : }
183 : #endif // CHIP_DETAIL_LOGGING
184 0 : return "N/A";
185 : }
186 :
187 36 : void CommandResponseSender::MoveToState(const State aTargetState)
188 : {
189 36 : if (mState == aTargetState)
190 : {
191 0 : return;
192 : }
193 36 : mState = aTargetState;
194 36 : ChipLogDetail(DataManagement, "Command response sender moving to [%10.10s]", GetStateStr());
195 : }
196 :
197 33 : void CommandResponseSender::Close()
198 : {
199 33 : MoveToState(State::AllInvokeResponsesSent);
200 33 : mpCallback->OnDone(*this);
201 33 : }
202 :
203 33 : void CommandResponseSender::OnInvokeCommandRequest(Messaging::ExchangeContext * ec, System::PacketBufferHandle && payload,
204 : bool isTimedInvoke)
205 : {
206 33 : VerifyOrDieWithMsg(ec != nullptr, DataManagement, "Incoming exchange context should not be null");
207 33 : VerifyOrDieWithMsg(mState == State::ReadyForInvokeResponses, DataManagement, "state should be ReadyForInvokeResponses");
208 :
209 : // NOTE: we already know this is an InvokeRequestMessage because we explicitly registered with the
210 : // Exchange Manager for unsolicited InvokeRequestMessages.
211 33 : mExchangeCtx.Grab(ec);
212 33 : mExchangeCtx->WillSendMessage();
213 :
214 : // Grabbing Handle to prevent mCommandHandler from calling OnDone before OnInvokeCommandRequest returns.
215 : // This allows us to send a StatusResponse error instead of any potentially queued up InvokeResponseMessages.
216 33 : CommandHandler::Handle workHandle(&mCommandHandler);
217 33 : Status status = mCommandHandler.OnInvokeCommandRequest(*this, std::move(payload), isTimedInvoke);
218 33 : if (status != Status::Success)
219 : {
220 3 : VerifyOrDie(mState == State::ReadyForInvokeResponses);
221 3 : SendStatusResponse(status);
222 : // The API contract of OnInvokeCommandRequest requires the CommandResponder instance to outlive
223 : // the CommandHandler. Therefore, we cannot safely call Close() here, even though we have
224 : // finished sending data. Closing must be deferred until the CommandHandler::OnDone callback.
225 3 : MoveToState(State::ErrorSentDelayCloseUntilOnDone);
226 : }
227 33 : }
228 :
229 29 : size_t CommandResponseSender::GetCommandResponseMaxBufferSize()
230 : {
231 29 : if (!mExchangeCtx || !mExchangeCtx->HasSessionHandle())
232 : {
233 0 : ChipLogError(DataManagement, "Session not available. Unable to infer session-specific buffer capacities.");
234 0 : return kMaxSecureSduLengthBytes;
235 : }
236 :
237 29 : if (mExchangeCtx->GetSessionHandle()->AllowsLargePayload())
238 : {
239 0 : return kMaxLargeSecureSduLengthBytes;
240 : }
241 :
242 29 : return kMaxSecureSduLengthBytes;
243 : }
244 :
245 : #if CHIP_WITH_NLFAULTINJECTION
246 :
247 0 : void CommandResponseSender::TestOnlyInvokeCommandRequestWithFaultsInjected(Messaging::ExchangeContext * ec,
248 : System::PacketBufferHandle && payload,
249 : bool isTimedInvoke,
250 : CommandHandlerImpl::NlFaultInjectionType faultType)
251 : {
252 0 : VerifyOrDieWithMsg(ec != nullptr, DataManagement, "TH Failure: Incoming exchange context should not be null");
253 0 : VerifyOrDieWithMsg(mState == State::ReadyForInvokeResponses, DataManagement,
254 : "TH Failure: state should be ReadyForInvokeResponses, issue with TH");
255 :
256 0 : mExchangeCtx.Grab(ec);
257 0 : mExchangeCtx->WillSendMessage();
258 :
259 0 : mCommandHandler.TestOnlyInvokeCommandRequestWithFaultsInjected(*this, std::move(payload), isTimedInvoke, faultType);
260 0 : }
261 : #endif // CHIP_WITH_NLFAULTINJECTION
262 :
263 : } // namespace app
264 : } // namespace chip
|