Skip to content

Commit

Permalink
[chip-tool] Add bdx download command to chip-tool
Browse files Browse the repository at this point in the history
[chip-tool] Make it possible to listen for transfer end when doing a download over BDX
  • Loading branch information
vivien-apple committed Feb 7, 2024
1 parent 5aeef05 commit 8cd1153
Show file tree
Hide file tree
Showing 9 changed files with 455 additions and 14 deletions.
2 changes: 2 additions & 0 deletions examples/chip-tool/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ static_library("chip-tool-utils") {
"${chip_root}/src/controller/ExamplePersistentStorage.h",
"${chip_root}/zzz_generated/chip-tool/zap-generated/cluster/ComplexArgumentParser.cpp",
"${chip_root}/zzz_generated/chip-tool/zap-generated/cluster/logging/DataModelLogger.cpp",
"commands/Bdx/DownloadLogCommand.cpp",
"commands/Bdx/DownloadLogCommand.h",
"commands/clusters/ModelCommand.cpp",
"commands/clusters/ModelCommand.h",
"commands/common/BDXDiagnosticLogsServerDelegate.cpp",
Expand Down
32 changes: 32 additions & 0 deletions examples/chip-tool/commands/bdx/Commands.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright (c) 2023 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.
*
*/

#pragma once

#include "commands/bdx/DownloadLogCommand.h"
#include "commands/common/Commands.h"

void registerCommandsBdx(Commands & commands, CredentialIssuerCommands * credsIssuerConfig)
{
const char * clusterName = "Bdx";
commands_list clusterCommands = {
make_unique<DownloadLogCommand>(credsIssuerConfig), //
};

commands.RegisterCommandSet(clusterName, clusterCommands, "Commands related to BDX");
}
214 changes: 214 additions & 0 deletions examples/chip-tool/commands/bdx/DownloadLogCommand.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
/*
* Copyright (c) 2024 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 "DownloadLogCommand.h"

#include <commands/clusters/DataModelLogger.h>
#include <commands/common/RemoteDataModelLogger.h>
#include <lib/support/BytesToHex.h>

constexpr uint8_t kEndPointId = 0;
constexpr const char kTmpDir[] = "/tmp/";

CHIP_ERROR DownloadLogCommand::RunCommand()
{
ChipLogProgress(chipTool, "Downloading logs from node 0x" ChipLogFormatX64, ChipLogValueX64(mDestinationId));
return CurrentCommissioner().GetConnectedDevice(mDestinationId, &mOnDeviceConnectedCallback,
&mOnDeviceConnectionFailureCallback);
}

void DownloadLogCommand::SendRetrieveLogsRequest(chip::DeviceProxy * device)
{
auto clusterId = chip::app::Clusters::DiagnosticLogs::Id;
auto commandId = chip::app::Clusters::DiagnosticLogs::Commands::RetrieveLogsRequest::Id;

// If there is no file designator specified, we will just generate a random one as an hex string.
char fileDesignatorBuffer[chip::bdx::DiagnosticLogs::kMaxFileDesignatorLen + 1] = { 0 };
if (!mRequest.transferFileDesignator.HasValue())
{
uint8_t fileDesignatorBytes[chip::bdx::DiagnosticLogs::kMaxFileDesignatorLen / 2];
chip::MutableByteSpan mac(fileDesignatorBytes);
chip::Crypto::DRBG_get_bytes(fileDesignatorBytes, sizeof(fileDesignatorBytes));

auto err = chip::Encoding::BytesToHex(fileDesignatorBytes, sizeof(fileDesignatorBytes), fileDesignatorBuffer,
sizeof(fileDesignatorBuffer) - 1, chip::Encoding::HexFlags::kUppercase);
VerifyOrReturn(CHIP_NO_ERROR == err, SetCommandExitStatus(err));

auto fileDesignator = chip::CharSpan(fileDesignatorBuffer, strlen(fileDesignatorBuffer));
mRequest.transferFileDesignator.SetValue(fileDesignator);
}

CHIP_ERROR err = InteractionModelCommands::SendCommand(device, kEndPointId, clusterId, commandId, mRequest);
VerifyOrReturn(CHIP_NO_ERROR == err, SetCommandExitStatus(err));

auto sender = mCommandSender.back().get();
auto fileDesignator = mRequest.transferFileDesignator.Value();
BDXDiagnosticLogsServerDelegate::GetInstance().AddFileDesignator(sender, fileDesignator, this);

if (mTimeout > 0)
{
chip::DeviceLayer::SystemLayer().StartTimer(chip::System::Clock::Seconds16(mTimeout + 3), OnTransferTimeout, sender);
}
}

void DownloadLogCommand::OnTransferTimeout(chip::System::Layer * layer, void * context)
{
auto * sender = static_cast<chip::app::CommandSender *>(context);
BDXDiagnosticLogsServerDelegate::GetInstance().Abort(sender, CHIP_ERROR_TIMEOUT);
}

void DownloadLogCommand::OnDeviceConnectedFn(void * context, chip::Messaging::ExchangeManager & exchangeMgr,
const chip::SessionHandle & sessionHandle)
{
DownloadLogCommand * command = reinterpret_cast<DownloadLogCommand *>(context);
VerifyOrReturn(command != nullptr, ChipLogError(chipTool, "OnDeviceConnectedFn: context is null"));

chip::OperationalDeviceProxy device(&exchangeMgr, sessionHandle);
command->SendRetrieveLogsRequest(&device);
}

void DownloadLogCommand::OnDeviceConnectionFailureFn(void * context, const chip::ScopedNodeId & peerId, CHIP_ERROR err)
{
LogErrorOnFailure(err);

DownloadLogCommand * command = reinterpret_cast<DownloadLogCommand *>(context);
VerifyOrReturn(command != nullptr, ChipLogError(chipTool, "OnDeviceConnectionFailureFn: context is null"));
command->SetCommandExitStatus(err);
}

void DownloadLogCommand::Shutdown()
{
mOnDeviceConnectedCallback.Cancel();
mOnDeviceConnectionFailureCallback.Cancel();

CHIPCommand::Shutdown();
mError = CHIP_NO_ERROR;
}

void DownloadLogCommand::OnResponse(chip::app::CommandSender * sender, const chip::app::ConcreteCommandPath & path,
const chip::app::StatusIB & status, chip::TLV::TLVReader * data)
{
CHIP_ERROR error = status.ToChipError();
if (CHIP_NO_ERROR != error)
{
ChipLogError(chipTool, "Response Failure: %s", chip::ErrorStr(error));
mError = error;
return;
}

if (data != nullptr)
{
chip::app::Clusters::DiagnosticLogs::Commands::RetrieveLogsResponse::DecodableType value;
error = chip::app::DataModel::Decode(*data, value);
if (CHIP_NO_ERROR != error)
{
ChipLogError(chipTool, "Response Failure: Can not decode response");
mError = error;
return;
}

switch (value.status)
{
case chip::app::Clusters::DiagnosticLogs::StatusEnum::kSuccess:
// The file is going to be transferred over BDX. The result will be logged when OnTransferDone is called.
break;
case chip::app::Clusters::DiagnosticLogs::StatusEnum::kBusy:
mDontWaitForDownload.SetValue(true);
mError = CHIP_ERROR_BUSY;
LogErrorOnFailure(RemoteDataModelLogger::LogBdxDownload(nullptr, mError));
return;
case chip::app::Clusters::DiagnosticLogs::StatusEnum::kDenied:
mDontWaitForDownload.SetValue(true);
mError = CHIP_ERROR_ACCESS_DENIED;
LogErrorOnFailure(RemoteDataModelLogger::LogBdxDownload(nullptr, mError));
break;
case chip::app::Clusters::DiagnosticLogs::StatusEnum::kExhausted:
case chip::app::Clusters::DiagnosticLogs::StatusEnum::kNoLogs: {
char content[chip::bdx::DiagnosticLogs::kMaxLogContentSize + 1 /* for null */] = { 0 };
memcpy(content, value.logContent.data(), value.logContent.size());

mDontWaitForDownload.SetValue(true);
LogErrorOnFailure(RemoteDataModelLogger::LogBdxDownload(content));
break;
}
case chip::app::Clusters::DiagnosticLogs::StatusEnum::kUnknownEnumValue:
// This should not happen.
chipDie();
break;
}
}
}

void DownloadLogCommand::OnError(const chip::app::CommandSender * sender, CHIP_ERROR error)
{
ChipLogProgress(chipTool, "Error: %s", chip::ErrorStr(error));
mError = error;
LogErrorOnFailure(RemoteDataModelLogger::LogBdxDownload(nullptr, mError));
}

void DownloadLogCommand::OnDone(chip::app::CommandSender * sender)
{
if (mCommandSender.size())
{
mCommandSender.front().reset();
mCommandSender.erase(mCommandSender.begin());
}

BDXDiagnosticLogsServerDelegate::GetInstance().RemoveFileDesignator(sender);

if (mDontWaitForDownload.ValueOr(false))
{
chip::DeviceLayer::SystemLayer().CancelTimer(OnTransferTimeout, sender);
SetCommandExitStatus(mError);
}
}

void DownloadLogCommand::OnTransferDone(chip::app::CommandSender * sender, const chip::CharSpan & fileDesignator, CHIP_ERROR error)
{
std::string targetFileDesignator(fileDesignator.data(), fileDesignator.size());
std::string filePath = kTmpDir + targetFileDesignator;

std::ifstream in(filePath);
std::string content((std::istreambuf_iterator<char>(in)), std::istreambuf_iterator<char>());

const char * logContent = nullptr;
if (content.length())
{
logContent = content.c_str();
}

if (!mDontWaitForDownload.ValueOr(false))
{
chip::DeviceLayer::SystemLayer().CancelTimer(OnTransferTimeout, sender);
LogErrorOnFailure(RemoteDataModelLogger::LogBdxDownload(logContent, error));

// When the transfer is over we need to
mError = error;
auto * systemLayer = &chip::DeviceLayer::SystemLayer();
systemLayer->ScheduleWork(
[](auto * systemLayer, auto * appState) -> void {
systemLayer->ScheduleWork(
[](auto * systemLayer, auto * appState) -> void {
auto * _this = static_cast<DownloadLogCommand *>(appState);
_this->SetCommandExitStatus(_this->mError);
},
appState);
},
this);
}
}
85 changes: 85 additions & 0 deletions examples/chip-tool/commands/bdx/DownloadLogCommand.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* Copyright (c) 2024 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.
*
*/

#pragma once

#include "../common/CHIPCommand.h"

#include <app/tests/suites/commands/interaction_model/InteractionModel.h>

class DownloadLogCommand : public InteractionModelCommands,
public CHIPCommand,
public chip::app::CommandSender::Callback,
public BDXDiagnosticLogsServerTransferListener
{
public:
DownloadLogCommand(CredentialIssuerCommands * credIssuerCommands) :
InteractionModelCommands(this), CHIPCommand("download", credIssuerCommands),
mOnDeviceConnectedCallback(OnDeviceConnectedFn, this), mOnDeviceConnectionFailureCallback(OnDeviceConnectionFailureFn, this)
{
AddArgument("destination-id", 0, UINT64_MAX, &mDestinationId, "Node to download the logs from.");
AddArgument("log-type", 0, 2, &mRequest.intent,
"The type of log being requested. This should correspond to a value in the enum MTRDiagnosticLogType.");
AddArgument("timeout", 0, UINT16_MAX, &mTimeout,
"The timeout for getting the log. If the timeout expires, completion will be called with whatever has been "
"retrieved by that point (which might be none or a partial log). If the timeout is set to 0, the request will "
"not expire and completion will not be called until the log is fully retrieved or an error occurs.");
AddArgument("async", 0, 1, &mDontWaitForDownload,
"By default the command waits for the download to finish before returning. If async is true the command will "
"not wait and the download will proceed in the background");
AddArgument("filepath", &mRequest.transferFileDesignator, "An optional filepath to save the download log content to.");

// The protocol is always BDX
mRequest.requestedProtocol = chip::app::Clusters::DiagnosticLogs::TransferProtocolEnum::kBdx;
}

/////////// CommandSender Callback Interface /////////
void OnResponse(chip::app::CommandSender * client, const chip::app::ConcreteCommandPath & path,
const chip::app::StatusIB & status, chip::TLV::TLVReader * data) override;
void OnError(const chip::app::CommandSender * client, CHIP_ERROR error) override;
void OnDone(chip::app::CommandSender * client) override;

/////////// CHIPCommand Interface /////////
CHIP_ERROR RunCommand() override;
chip::System::Clock::Timeout GetWaitDuration() const override
{
return chip::System::Clock::Seconds16(mTimeout > 0 ? mTimeout + 10 : 300);
}
void Shutdown() override;

/////////// BDXDiagnosticLogsServerTransferListener Interface /////////
void OnTransferDone(chip::app::CommandSender * sender, const chip::CharSpan & fileDesignator, CHIP_ERROR error) override;

private:
static void OnDeviceConnectedFn(void * context, chip::Messaging::ExchangeManager & exchangeMgr,
const chip::SessionHandle & sessionHandle);
static void OnDeviceConnectionFailureFn(void * context, const chip::ScopedNodeId & peerId, CHIP_ERROR error);

chip::Callback::Callback<chip::OnDeviceConnected> mOnDeviceConnectedCallback;
chip::Callback::Callback<chip::OnDeviceConnectionFailure> mOnDeviceConnectionFailureCallback;

void SendRetrieveLogsRequest(chip::DeviceProxy * device);
static void OnTransferTimeout(chip::System::Layer * layer, void * context);

chip::NodeId mDestinationId;
uint16_t mTimeout;
chip::Optional<bool> mDontWaitForDownload;

CHIP_ERROR mError = CHIP_NO_ERROR;
chip::app::Clusters::DiagnosticLogs::Commands::RetrieveLogsRequest::Type mRequest;
};
Loading

0 comments on commit 8cd1153

Please sign in to comment.