/* * * 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 #include #include #include #include #include #include #include /* TODO:(#12520) remove */ #include #include 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(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(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(outEvent.EventType)); break; } return CHIP_NO_ERROR; } void BDXDownloader::SetState(State state, OTAChangeReasonEnum reason) { mState = state; if (mStateDelegate) { mStateDelegate->OnDownloadStateChanged(state, reason); } } } // namespace chip