blob: 5c3f2213427035c3f8c56947b45bcfc21f3ff771 [file] [log] [blame]
/*
*
* Copyright (c) 2021 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 <ota-provider-common/BdxOtaSender.h>
#include <lib/core/CHIPError.h>
#include <lib/support/BitFlags.h>
#include <lib/support/CHIPMemString.h>
#include <messaging/ExchangeContext.h>
#include <messaging/Flags.h>
#include <protocols/bdx/BdxTransferSession.h>
#include <fstream>
using chip::bdx::StatusCode;
using chip::bdx::TransferControlFlags;
using chip::bdx::TransferSession;
BdxOtaSender::BdxOtaSender()
{
memset(mFileDesignator, 0, chip::bdx::kMaxFileDesignatorLen);
}
CHIP_ERROR BdxOtaSender::InitializeTransfer(chip::FabricIndex fabricIndex, chip::NodeId nodeId)
{
if (mInitialized)
{
// Reset stale connection from the Same Node if exists
if ((mFabricIndex.HasValue() && mFabricIndex.Value() == fabricIndex) && (mNodeId.HasValue() && mNodeId.Value() == nodeId))
{
Reset();
}
// Prevent a new node connection since another is active
else if ((mFabricIndex.HasValue() && mFabricIndex.Value() != fabricIndex) ||
(mNodeId.HasValue() && mNodeId.Value() != nodeId))
{
return CHIP_ERROR_BUSY;
}
else
{
return CHIP_ERROR_INTERNAL;
}
}
mFabricIndex.SetValue(fabricIndex);
mNodeId.SetValue(nodeId);
mInitialized = true;
return CHIP_NO_ERROR;
}
void BdxOtaSender::HandleTransferSessionOutput(TransferSession::OutputEvent & event)
{
CHIP_ERROR err = CHIP_NO_ERROR;
if (event.EventType != TransferSession::OutputEventType::kNone)
{
ChipLogDetail(BDX, "OutputEvent type: %s", event.ToString(event.EventType));
}
switch (event.EventType)
{
case TransferSession::OutputEventType::kNone:
break;
case TransferSession::OutputEventType::kMsgToSend: {
chip::Messaging::SendFlags sendFlags;
if (!event.msgTypeData.HasMessageType(chip::Protocols::SecureChannel::MsgType::StatusReport))
{
// All messages sent from the Sender expect a response, except for a StatusReport which would indicate an error and the
// end of the transfer.
sendFlags.Set(chip::Messaging::SendMessageFlags::kExpectResponse);
}
VerifyOrReturn(mExchangeCtx != nullptr);
err = mExchangeCtx->SendMessage(event.msgTypeData.ProtocolId, event.msgTypeData.MessageType, std::move(event.MsgData),
sendFlags);
if (err == CHIP_NO_ERROR)
{
if (!sendFlags.Has(chip::Messaging::SendMessageFlags::kExpectResponse))
{
// After sending the StatusReport, exchange context gets closed so, set mExchangeCtx to null
mExchangeCtx = nullptr;
}
}
else
{
ChipLogError(BDX, "SendMessage failed: %" CHIP_ERROR_FORMAT, err.Format());
Reset();
}
break;
}
case TransferSession::OutputEventType::kInitReceived: {
// TransferSession will automatically reject a transfer if there are no
// common supported control modes. It will also default to the smaller
// block size.
TransferSession::TransferAcceptData acceptData;
acceptData.ControlMode = TransferControlFlags::kReceiverDrive; // OTA must use receiver drive
acceptData.MaxBlockSize = mTransfer.GetTransferBlockSize();
acceptData.StartOffset = mTransfer.GetStartOffset();
acceptData.Length = mTransfer.GetTransferLength();
VerifyOrReturn(mTransfer.AcceptTransfer(acceptData) == CHIP_NO_ERROR,
ChipLogError(BDX, "AcceptTransfer failed: %" CHIP_ERROR_FORMAT, err.Format()));
// Store the file designator used during block query
uint16_t fdl = 0;
const uint8_t * fd = mTransfer.GetFileDesignator(fdl);
VerifyOrReturn(fdl < chip::bdx::kMaxFileDesignatorLen,
ChipLogError(BDX, "Cannot store file designator with length = %d", fdl));
memcpy(mFileDesignator, fd, fdl);
mFileDesignator[fdl] = 0;
break;
}
case TransferSession::OutputEventType::kQueryReceived: {
TransferSession::BlockData blockData;
uint16_t blockSize = mTransfer.GetTransferBlockSize();
uint16_t bytesToRead = blockSize;
// TODO: This should be a utility function in TransferSession
if (mTransfer.GetTransferLength() > 0 && mNumBytesSent + blockSize > mTransfer.GetTransferLength())
{
// cast should be safe because of condition above
bytesToRead = static_cast<uint16_t>(mTransfer.GetTransferLength() - mNumBytesSent);
}
chip::System::PacketBufferHandle blockBuf = chip::System::PacketBufferHandle::New(bytesToRead);
if (blockBuf.IsNull())
{
// TODO(#13981): AbortTransfer() needs to support GeneralStatusCode failures as well as BDX specific errors.
mTransfer.AbortTransfer(StatusCode::kUnknown);
return;
}
std::ifstream otaFile(mFileDesignator, std::ifstream::in);
if (!otaFile.good())
{
ChipLogError(BDX, "OTA file open failed");
mTransfer.AbortTransfer(StatusCode::kFileDesignatorUnknown);
return;
}
otaFile.seekg(mNumBytesSent);
otaFile.read(reinterpret_cast<char *>(blockBuf->Start()), bytesToRead);
if (!(otaFile.good() || otaFile.eof()))
{
ChipLogError(BDX, "OTA file read failed");
mTransfer.AbortTransfer(StatusCode::kFileDesignatorUnknown);
return;
}
blockData.Data = blockBuf->Start();
blockData.Length = static_cast<size_t>(otaFile.gcount());
blockData.IsEof = (blockData.Length < blockSize) ||
(mNumBytesSent + static_cast<uint64_t>(blockData.Length) == mTransfer.GetTransferLength() || (otaFile.peek() == EOF));
mNumBytesSent = static_cast<uint32_t>(mNumBytesSent + blockData.Length);
otaFile.close();
err = mTransfer.PrepareBlock(blockData);
if (err != CHIP_NO_ERROR)
{
ChipLogError(BDX, "PrepareBlock failed: %" CHIP_ERROR_FORMAT, err.Format());
mTransfer.AbortTransfer(StatusCode::kUnknown);
}
break;
}
case TransferSession::OutputEventType::kAckReceived:
break;
case TransferSession::OutputEventType::kAckEOFReceived:
ChipLogDetail(BDX, "Transfer completed, got AckEOF");
mStopPolling = true; // Stop polling the TransferSession only after receiving BlockAckEOF
Reset();
break;
case TransferSession::OutputEventType::kStatusReceived:
ChipLogError(BDX, "Got StatusReport %x", static_cast<uint16_t>(event.statusData.statusCode));
Reset();
break;
case TransferSession::OutputEventType::kInternalError:
ChipLogError(BDX, "InternalError");
Reset();
break;
case TransferSession::OutputEventType::kTransferTimeout:
ChipLogError(BDX, "Transfer timed out");
Reset();
break;
case TransferSession::OutputEventType::kAcceptReceived:
case TransferSession::OutputEventType::kBlockReceived:
default:
// TransferSession should prevent this case from happening.
ChipLogError(BDX, "Unsupported event type");
}
}
/* Reset() calls bdx::TransferSession::Reset() which sets the output event type to
* TransferSession::OutputEventType::kNone. So, bdx::TransferFacilitator::PollForOutput()
* will call HandleTransferSessionOutput() with event TransferSession::OutputEventType::kNone.
* Since we are ignoring kNone events so, it is okay HandleTransferSessionOutput() being called with event kNone
*/
void BdxOtaSender::Reset()
{
mFabricIndex.ClearValue();
mNodeId.ClearValue();
Responder::ResetTransfer();
if (mExchangeCtx != nullptr)
{
mExchangeCtx->Close();
mExchangeCtx = nullptr;
}
mInitialized = false;
mNumBytesSent = 0;
memset(mFileDesignator, 0, chip::bdx::kMaxFileDesignatorLen);
}