/* * * 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 #include #include #include #include #include #include #include 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(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(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(otaFile.gcount()); blockData.IsEof = (blockData.Length < blockSize) || (mNumBytesSent + static_cast(blockData.Length) == mTransfer.GetTransferLength() || (otaFile.peek() == EOF)); mNumBytesSent = static_cast(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"); Reset(); break; case TransferSession::OutputEventType::kStatusReceived: ChipLogError(BDX, "Got StatusReport %x", static_cast(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(); ResetTransfer(); if (mExchangeCtx != nullptr) { mExchangeCtx->Close(); mExchangeCtx = nullptr; } mInitialized = false; mNumBytesSent = 0; memset(mFileDesignator, 0, chip::bdx::kMaxFileDesignatorLen); }