blob: 94cb3f1f3c2b55b436368e404e728c9dfa3b43c3 [file] [log] [blame]
/*
* Copyright (c) 2024 Project CHIP Authors
*
* 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.
*/
#include "AsyncTransferFacilitator.h"
#include <protocols/bdx/StatusCode.h>
#include <system/SystemClock.h>
namespace chip {
namespace bdx {
AsyncTransferFacilitator::~AsyncTransferFacilitator() {}
CHIP_ERROR AsyncTransferFacilitator::Init(System::Layer * layer, Messaging::ExchangeContext * exchangeCtx,
System::Clock::Timeout timeout)
{
VerifyOrReturnError(layer != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(exchangeCtx != nullptr, CHIP_ERROR_INVALID_ARGUMENT);
VerifyOrReturnError(!mExchange, CHIP_ERROR_INCORRECT_STATE);
mSystemLayer = layer;
mExchange.Grab(exchangeCtx);
mTimeout = timeout;
mProcessingOutputEvents = false;
mDestroySelfAfterProcessingEvents = false;
return CHIP_NO_ERROR;
}
/**
* Get events one by one from the TransferSession and process them,
* until there are no more events to process.
*/
void AsyncTransferFacilitator::ProcessOutputEvents()
{
if (mProcessingOutputEvents)
{
ChipLogDetail(BDX,
"ProcessOutputEvents: we are already in the middle of processing events, so nothing to do here; when we "
"unwind to the processing loop the events will get processed.");
return;
}
mProcessingOutputEvents = true;
// Get the next output event and handle it based on the type of event.
// If its of type kMsgToSend send it over the exchange, otherwise call the HandleTransferSessionOutput
// virtual method that must be implemeted by the subclass of this class to handle the BDX message.
TransferSession::OutputEvent outEvent;
mTransfer.GetNextAction(outEvent);
while (outEvent.EventType != TransferSession::OutputEventType::kNone)
{
// If the transfer session state machine generates an event of type TransferSession::OutputEventType::kInternalError,
// indicating that the session is in a bad state, it will keep doing that thereafter.
//
// So stop trying to process events, and go ahead and destroy ourselves to clean up the transfer.
if (outEvent.EventType == TransferSession::OutputEventType::kInternalError)
{
mDestroySelfAfterProcessingEvents = true;
break;
}
if (outEvent.EventType == TransferSession::OutputEventType::kMsgToSend)
{
CHIP_ERROR err = SendMessage(outEvent.msgTypeData, outEvent.MsgData);
// If we failed to send the message across the exchange, just abort the transfer.
// We have no way to notify our peer we are doing that (we can't send them a
// message!) but eventually they will time out.
if (err != CHIP_NO_ERROR)
{
DestroySelf();
return;
}
// If we send out a status report across the exchange, that means there was an error.
// We've sent our report about that error and can now abort the transfer. Our peer
// will respond to the status report by tearing down their side.
if (outEvent.msgTypeData.HasMessageType(Protocols::SecureChannel::MsgType::StatusReport))
{
mDestroySelfAfterProcessingEvents = true;
break;
}
}
else
{
HandleTransferSessionOutput(outEvent);
}
mTransfer.GetNextAction(outEvent);
}
mProcessingOutputEvents = false;
// If mDestroySelfAfterProcessingEvents is set (by our code above or by NotifyEventHandled), we need
// to call DestroySelf() after processing all pending output events.
if (mDestroySelfAfterProcessingEvents)
{
DestroySelf();
}
}
CHIP_ERROR AsyncTransferFacilitator::SendMessage(const TransferSession::MessageTypeData msgTypeData,
System::PacketBufferHandle & msgBuf)
{
VerifyOrReturnError(mExchange, CHIP_ERROR_INCORRECT_STATE);
Messaging::SendFlags sendFlags;
// All messages that are sent expect a response, except for a StatusReport which would indicate an error and
// the end of the transfer.
if (!msgTypeData.HasMessageType(Protocols::SecureChannel::MsgType::StatusReport))
{
sendFlags.Set(Messaging::SendMessageFlags::kExpectResponse);
}
Messaging::ExchangeContext * ec = mExchange.Get();
// Set the response timeout on the exchange before sending the message.
ec->SetResponseTimeout(mTimeout);
return ec->SendMessage(msgTypeData.ProtocolId, msgTypeData.MessageType, std::move(msgBuf), sendFlags);
}
CHIP_ERROR AsyncTransferFacilitator::OnMessageReceived(Messaging::ExchangeContext * ec, const PayloadHeader & payloadHeader,
System::PacketBufferHandle && payload)
{
VerifyOrReturnError(mExchange, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(ec == mExchange.Get(), CHIP_ERROR_INCORRECT_STATE);
CHIP_ERROR err =
mTransfer.HandleMessageReceived(payloadHeader, std::move(payload), System::SystemClock().GetMonotonicTimestamp());
if (err != CHIP_NO_ERROR)
{
ChipLogError(BDX, "OnMessageReceived: Failed to handle message: %" CHIP_ERROR_FORMAT, err.Format());
// This should notify the transfer object to abort transfer so it can send a status report across the exchange
// when we call ProcessOutputEvents below.
mTransfer.AbortTransfer(GetBdxStatusCodeFromChipError(err));
}
else if (!payloadHeader.HasMessageType(MessageType::BlockAckEOF))
{
// Almost every BDX message expect BlockAckEOF will follow up with a response on the exchange.
ec->WillSendMessage();
}
ProcessOutputEvents();
return err;
}
void AsyncTransferFacilitator::OnResponseTimeout(Messaging::ExchangeContext * ec)
{
ChipLogDetail(BDX, "OnResponseTimeout, ec: " ChipLogFormatExchange, ChipLogValueExchange(ec));
DestroySelf();
}
CHIP_ERROR AsyncResponder::Init(System::Layer * layer, Messaging::ExchangeContext * exchangeCtx, TransferRole role,
BitFlags<TransferControlFlags> xferControlOpts, uint16_t maxBlockSize,
System::Clock::Timeout timeout)
{
ReturnErrorOnFailure(AsyncTransferFacilitator::Init(layer, exchangeCtx, timeout));
ReturnErrorOnFailure(mTransfer.WaitForTransfer(role, xferControlOpts, maxBlockSize, timeout));
return CHIP_NO_ERROR;
}
void AsyncResponder::NotifyEventHandled(const TransferSession::OutputEventType eventType, CHIP_ERROR status)
{
// If this is the end of the transfer (whether a clean end, or some sort of error condition), ensure
// that we destroy ourselves after unwinding the processing loop in the ProcessOutputEvents API.
// We can ignore the status for these output events because none of them are supposed to result in
// us sending a StatusReport, and that's all we use the status for.
//
// In particular, for kTransferTimeout, kAckEOFReceived, and kStatusReceived per spec we
// are not supposed to reply with a StatusReport. And for kInternalError the state machine
// is in an unrecoverable state of some sort, and we should stop trying to make use of it.
if (eventType == TransferSession::OutputEventType::kAckEOFReceived ||
eventType == TransferSession::OutputEventType::kInternalError ||
eventType == TransferSession::OutputEventType::kTransferTimeout ||
eventType == TransferSession::OutputEventType::kStatusReceived)
{
ChipLogProgress(BDX, "NotifyEventHandled : Event %s Error %" CHIP_ERROR_FORMAT,
TransferSession::OutputEvent::TypeToString(eventType), status.Format());
mDestroySelfAfterProcessingEvents = true;
}
else if (status != CHIP_NO_ERROR)
{
// If there was an error handling the output event, this should notify the transfer object to abort transfer
// so it can send a status report across the exchange when we call ProcessOutputEvents below.
mTransfer.AbortTransfer(GetBdxStatusCodeFromChipError(status));
}
ProcessOutputEvents();
}
} // namespace bdx
} // namespace chip