blob: 9c17cf0dde2228da9573541759a3706f912b0eae [file] [log] [blame]
/*
*
* Copyright (c) 2021 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.
*/
#include "BDXDownloader.h"
#include <app/data-model/Nullable.h>
#include <lib/core/CHIPError.h>
#include <lib/support/BufferReader.h>
#include <lib/support/BytesToHex.h>
#include <lib/support/CodeUtils.h>
#include <platform/CHIPDeviceLayer.h>
#include <protocols/bdx/BdxMessages.h>
#include <system/SystemClock.h> /* TODO:(#12520) remove */
#include <system/SystemPacketBuffer.h>
#include <transport/raw/MessageHeader.h>
using chip::OTADownloader;
using chip::app::Clusters::OtaSoftwareUpdateRequestor::OTAChangeReasonEnum;
using chip::app::DataModel::Nullable;
using chip::bdx::TransferSession;
namespace chip {
void TransferTimeoutCheckHandler(System::Layer * systemLayer, void * appState)
{
VerifyOrReturn(appState != nullptr);
BDXDownloader * bdxDownloader = static_cast<BDXDownloader *>(appState);
if (bdxDownloader->HasTransferTimedOut())
{
// End download if transfer timeout has been detected
bdxDownloader->OnDownloadTimeout();
}
else
{
// Else restart the timer
systemLayer->StartTimer(bdxDownloader->GetTimeout(), TransferTimeoutCheckHandler, appState);
}
}
System::Clock::Timeout BDXDownloader::GetTimeout()
{
return mTimeout;
}
void BDXDownloader::Reset()
{
mPrevBlockCounter = 0;
DeviceLayer::SystemLayer().CancelTimer(TransferTimeoutCheckHandler, this);
}
bool BDXDownloader::HasTransferTimedOut()
{
uint32_t curBlockCounter = mBdxTransfer.GetNextQueryNum();
if (curBlockCounter > mPrevBlockCounter)
{
mPrevBlockCounter = curBlockCounter;
return false;
}
ChipLogError(BDX, "BDX transfer timeout");
return true;
}
void BDXDownloader::OnMessageReceived(const chip::PayloadHeader & payloadHeader, chip::System::PacketBufferHandle msg)
{
VerifyOrReturn(mState == State::kInProgress, ChipLogError(BDX, "Can't accept messages, no transfer in progress"));
CHIP_ERROR err =
mBdxTransfer.HandleMessageReceived(payloadHeader, std::move(msg), /* TODO:(#12520) */ chip::System::Clock::Seconds16(0));
if (err != CHIP_NO_ERROR)
{
ChipLogError(BDX, "unable to handle message: %" CHIP_ERROR_FORMAT, err.Format());
}
// HandleMessageReceived() will only decode/parse the message. Need to Poll() in order to do the message handling work in
// HandleBdxEvent().
PollTransferSession();
}
CHIP_ERROR BDXDownloader::SetBDXParams(const chip::bdx::TransferSession::TransferInitData & bdxInitData,
System::Clock::Timeout timeout)
{
mTimeout = timeout;
mState = State::kIdle;
mBdxTransfer.Reset();
VerifyOrReturnError(mState == State::kIdle, CHIP_ERROR_INCORRECT_STATE);
// Must call StartTransfer() here to store the the pointer data contained in bdxInitData in the TransferSession object.
// Otherwise it could be freed before we can use it.
ReturnErrorOnFailure(mBdxTransfer.StartTransfer(chip::bdx::TransferRole::kReceiver, bdxInitData,
/* TODO:(#12520) */ chip::System::Clock::Seconds16(30)));
return CHIP_NO_ERROR;
}
CHIP_ERROR BDXDownloader::BeginPrepareDownload()
{
VerifyOrReturnError(mState == State::kIdle, CHIP_ERROR_INCORRECT_STATE);
VerifyOrReturnError(mImageProcessor != nullptr, CHIP_ERROR_INCORRECT_STATE);
mPrevBlockCounter = 0;
// Note that due to the nature of this timer function, the actual time taken to detect a stalled BDX connection might be
// anywhere in the range of [mTimeout, 2*mTimeout)
DeviceLayer::SystemLayer().StartTimer(mTimeout, TransferTimeoutCheckHandler, this);
ReturnErrorOnFailure(mImageProcessor->PrepareDownload());
SetState(State::kPreparing, OTAChangeReasonEnum::kSuccess);
return CHIP_NO_ERROR;
}
CHIP_ERROR BDXDownloader::OnPreparedForDownload(CHIP_ERROR status)
{
VerifyOrReturnError(mState == State::kPreparing, CHIP_ERROR_INCORRECT_STATE);
if (status == CHIP_NO_ERROR)
{
SetState(State::kInProgress, OTAChangeReasonEnum::kSuccess);
// Must call here because StartTransfer() should have prepared a ReceiveInit message, and now we should send it.
PollTransferSession();
}
else
{
ChipLogError(BDX, "failed to prepare download: %" CHIP_ERROR_FORMAT, status.Format());
Reset();
mBdxTransfer.Reset();
SetState(State::kIdle, OTAChangeReasonEnum::kFailure);
}
return CHIP_NO_ERROR;
}
CHIP_ERROR BDXDownloader::FetchNextData()
{
VerifyOrReturnError(mState == State::kInProgress, CHIP_ERROR_INCORRECT_STATE);
ReturnErrorOnFailure(mBdxTransfer.PrepareBlockQuery());
PollTransferSession();
return CHIP_NO_ERROR;
}
void BDXDownloader::OnDownloadTimeout()
{
Reset();
if (mState == State::kInProgress)
{
ChipLogDetail(BDX, "aborting due to timeout");
mBdxTransfer.Reset();
if (mImageProcessor != nullptr)
{
mImageProcessor->Abort();
}
SetState(State::kIdle, OTAChangeReasonEnum::kTimeOut);
}
else
{
ChipLogError(BDX, "No download in progress");
}
}
void BDXDownloader::EndDownload(CHIP_ERROR reason)
{
Reset();
if (mState == State::kInProgress)
{
bdx::StatusCode status = bdx::StatusCode::kUnknown;
if (reason == CHIP_ERROR_INVALID_FILE_IDENTIFIER)
{
status = bdx::StatusCode::kBadMessageContents;
}
else if (reason == CHIP_ERROR_WRITE_FAILED)
{
status = bdx::StatusCode::kTransferFailedUnknownError;
}
// There is no method for a BDX receiving driver to cleanly end a transfer
mBdxTransfer.AbortTransfer(status);
if (mImageProcessor != nullptr)
{
mImageProcessor->Abort();
}
// Because AbortTransfer() will generate a StatusReport to send.
PollTransferSession();
// Now that we've sent our report, we're idle.
SetState(State::kIdle, OTAChangeReasonEnum::kSuccess);
}
else
{
ChipLogError(BDX, "No download in progress");
}
}
void BDXDownloader::PollTransferSession()
{
TransferSession::OutputEvent outEvent;
// WARNING: Is this dangerous? What happens if the loop encounters two messages that need to be sent? Does the ExchangeContext
// allow that?
do
{
mBdxTransfer.PollOutput(outEvent, /* TODO:(#12520) */ chip::System::Clock::Seconds16(0));
CHIP_ERROR err = HandleBdxEvent(outEvent);
VerifyOrReturn(err == CHIP_NO_ERROR, ChipLogError(BDX, "HandleBDXEvent: %" CHIP_ERROR_FORMAT, err.Format()));
} while (outEvent.EventType != TransferSession::OutputEventType::kNone);
}
void BDXDownloader::CleanupOnError(OTAChangeReasonEnum reason)
{
Reset();
mBdxTransfer.Reset();
SetState(State::kIdle, reason);
if (mImageProcessor)
{
mImageProcessor->Abort();
}
}
CHIP_ERROR BDXDownloader::HandleBdxEvent(const chip::bdx::TransferSession::OutputEvent & outEvent)
{
VerifyOrReturnError(mImageProcessor != nullptr, CHIP_ERROR_INCORRECT_STATE);
switch (outEvent.EventType)
{
case TransferSession::OutputEventType::kNone:
break;
case TransferSession::OutputEventType::kAcceptReceived:
ReturnErrorOnFailure(mBdxTransfer.PrepareBlockQuery());
// TODO: need to check ReceiveAccept parameters
break;
case TransferSession::OutputEventType::kMsgToSend: {
VerifyOrReturnError(mMsgDelegate != nullptr, CHIP_ERROR_INCORRECT_STATE);
ReturnErrorOnFailure(mMsgDelegate->SendMessage(outEvent));
if (outEvent.msgTypeData.HasMessageType(chip::bdx::MessageType::BlockAckEOF))
{
Reset();
// BDX transfer is not complete until BlockAckEOF has been sent
SetState(State::kComplete, OTAChangeReasonEnum::kSuccess);
}
break;
}
case TransferSession::OutputEventType::kBlockReceived: {
chip::ByteSpan blockData(outEvent.blockdata.Data, outEvent.blockdata.Length);
ReturnErrorOnFailure(mImageProcessor->ProcessBlock(blockData));
mStateDelegate->OnUpdateProgressChanged(mImageProcessor->GetPercentComplete());
// TODO: this will cause problems if Finalize() is not guaranteed to do its work after ProcessBlock().
if (outEvent.blockdata.IsEof)
{
mBdxTransfer.PrepareBlockAck();
ReturnErrorOnFailure(mImageProcessor->Finalize());
}
break;
}
case TransferSession::OutputEventType::kStatusReceived:
ChipLogError(BDX, "BDX StatusReport %x", static_cast<uint16_t>(outEvent.statusData.statusCode));
CleanupOnError(OTAChangeReasonEnum::kFailure);
break;
case TransferSession::OutputEventType::kInternalError:
ChipLogError(BDX, "TransferSession error");
CleanupOnError(OTAChangeReasonEnum::kFailure);
break;
case TransferSession::OutputEventType::kTransferTimeout:
ChipLogError(BDX, "Transfer timed out");
CleanupOnError(OTAChangeReasonEnum::kTimeOut);
break;
case TransferSession::OutputEventType::kInitReceived:
case TransferSession::OutputEventType::kAckReceived:
case TransferSession::OutputEventType::kQueryReceived:
case TransferSession::OutputEventType::kAckEOFReceived:
default:
ChipLogError(BDX, "Unexpected BDX event: %u", static_cast<uint16_t>(outEvent.EventType));
break;
}
return CHIP_NO_ERROR;
}
void BDXDownloader::SetState(State state, OTAChangeReasonEnum reason)
{
mState = state;
if (mStateDelegate)
{
mStateDelegate->OnDownloadStateChanged(state, reason);
}
}
} // namespace chip