Skip to content

Commit

Permalink
AccountLogin Login/Logout command support
Browse files Browse the repository at this point in the history
[Problem]
* ContentAppPlatform should send AccountLogin::Login command after
successfull commissioning if user was shown setupPIN prompt.
* Handling Login/Logout commands is currently not implemented in the
  Android.

[Solution]
* Call AccountLoginManager::HandleLogin if commissioning succeeded
  successfully with setupPIN prompt flow.
* Implement HandleLogin (and HandleLogout) for Android
  AccountLoginManager using the ContentAppCommandDelegate.

[Testing]
WIP
  • Loading branch information
mthiesc committed Jul 9, 2024
1 parent 82eb063 commit 9767da1
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,14 @@ private CommandResponseHolder() {
Clusters.AccountLogin.Id,
Clusters.AccountLogin.Commands.GetSetupPIN.ID,
"{\"0\":\"20202021\"}");
setResponseValue(
Clusters.AccountLogin.Id,
Clusters.AccountLogin.Commands.Login.ID,
"0");
setResponseValue(
Clusters.AccountLogin.Id,
Clusters.AccountLogin.Commands.Logout.ID,
"0");
};

public static CommandResponseHolder getInstance() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,34 @@
#include <lib/core/DataModelTypes.h>

using namespace std;
using namespace chip::app::Clusters;
using namespace chip::app::Clusters::AccountLogin;
using Status = chip::Protocols::InteractionModel::Status;

namespace {

const auto loginTempAccountIdentifierFieldId = to_string(chip::to_underlying(AccountLogin::Commands::Login::Fields::kTempAccountIdentifier));
const auto loginSetupPINFieldId = to_string(chip::to_underlying(AccountLogin::Commands::Login::Fields::kSetupPIN));
const auto loginNodeFieldId = to_string(chip::to_underlying(AccountLogin::Commands::Login::Fields::kNode));
const auto logoutNodeFieldId = to_string(chip::to_underlying(AccountLogin::Commands::Logout::Fields::kNode));

string charSpanToString(const CharSpan& charSpan) {
return { charSpan.data(), charSpan.size() };
}

std::string serializeLoginCommand(AccountLogin::Commands::Login::Type cmd) {
return
R"({")" + loginTempAccountIdentifierFieldId + R"(":")" + charSpanToString(cmd.tempAccountIdentifier) + R"(",)" +
R"(")" + loginSetupPINFieldId + R"(":")" + charSpanToString(cmd.setupPIN) + R"(",)" +
R"(")" + loginNodeFieldId + R"(":")" + to_string(cmd.node.Value()) + R"("})";
}

std::string serializeLogoutCommand(AccountLogin::Commands::Logout::Type cmd) {
return R"({")" + logoutNodeFieldId + R"(":")" + to_string(cmd.node.Value()) + R"("})";
}

}

AccountLoginManager::AccountLoginManager(ContentAppCommandDelegate * commandDelegate, const char * setupPin) :
mCommandDelegate(commandDelegate)
{
Expand All @@ -37,25 +62,61 @@ bool AccountLoginManager::HandleLogin(const CharSpan & tempAccountIdentifier, co
const chip::Optional<chip::NodeId> & nodeId)
{
ChipLogProgress(DeviceLayer, "AccountLoginManager::HandleLogin called for endpoint %d", mEndpointId);
string tempAccountIdentifierString(tempAccountIdentifier.data(), tempAccountIdentifier.size());
string setupPinString(setupPin.data(), setupPin.size());

if (strcmp(mSetupPin, setupPinString.c_str()) == 0)
if (mCommandDelegate == nullptr)
{
ChipLogProgress(Zcl, "AccountLoginManager::HandleLogin success");
return true;
ChipLogError(Zcl, "CommandDelegate not found");
return false;
}
else
{
ChipLogProgress(Zcl, "AccountLoginManager::HandleLogin failed expected pin %s", mSetupPin);

if (tempAccountIdentifier.empty() || setupPIN.empty() || !nodeId.HasValue()) {
ChipLogError(Zcl, "Invalid parameters");
return false;
}

Json::Value response;
bool commandHandled = true;
AccountLogin::Commands::Login::Type cmd = { tempAccountIdentifier, setupPin, nodeId };

// Deliberately ignore returned status
mCommandDelegate->InvokeCommand(mEndpointId, AccountLogin::Id,
AccountLogin::Commands::Login::Id,
serializeLoginCommand(cmd), commandHandled, response);

Status status = mCommandDelegate->FormatStatusResponse(response);
ChipLogProgress(Zcl, "AccountLoginManager::HandleLogin command returned with status: %d", chip::to_underlying(status));

return status == chip::Protocols::InteractionModel::Status::Success;
}

bool AccountLoginManager::HandleLogout(const chip::Optional<chip::NodeId> & nodeId)
{
// TODO: Insert your code here to send logout request
return true;
ChipLogProgress(DeviceLayer, "AccountLoginManager::HandleLogout called for endpoint %d", mEndpointId);

if (mCommandDelegate == nullptr)
{
ChipLogError(Zcl, "CommandDelegate not found");
return false;
}

if (!nodeId.HasValue()) {
ChipLogError(Zcl, "Invalid parameters");
return false;
}

Json::Value response;
bool commandHandled = true;
AccountLogin::Commands::Logout::Type cmd = { nodeId };

// Deliberately ignore returned status
mCommandDelegate->InvokeCommand(mEndpointId, AccountLogin::Id,
AccountLogin::Commands::Logout::Id,
serializeLogoutCommand(cmd), commandHandled, response);

Status status = mCommandDelegate->FormatStatusResponse(response);
ChipLogProgress(Zcl, "AccountLoginManager::HandleLogout command returned with status: %d", chip::to_underlying(status));

return status == chip::Protocols::InteractionModel::Status::Success;
}

void AccountLoginManager::HandleGetSetupPin(CommandResponseHelper<GetSetupPINResponse> & helper,
Expand Down
58 changes: 37 additions & 21 deletions examples/tv-app/android/java/ContentAppCommandDelegate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,6 @@
namespace chip {
namespace AppPlatform {

using CommandHandlerInterface = chip::app::CommandHandlerInterface;
using LaunchResponseType = chip::app::Clusters::ContentLauncher::Commands::LauncherResponse::Type;
using PlaybackResponseType = chip::app::Clusters::MediaPlayback::Commands::PlaybackResponse::Type;
using NavigateTargetResponseType = chip::app::Clusters::TargetNavigator::Commands::NavigateTargetResponse::Type;
using GetSetupPINResponseType = chip::app::Clusters::AccountLogin::Commands::GetSetupPINResponse::Type;
using Status = chip::Protocols::InteractionModel::Status;

const std::string FAILURE_KEY = "PlatformError";
const std::string FAILURE_STATUS_KEY = "Status";

Expand Down Expand Up @@ -107,6 +100,7 @@ void ContentAppCommandDelegate::InvokeCommand(CommandHandlerInterface::HandlerCo
Status ContentAppCommandDelegate::InvokeCommand(EndpointId epId, ClusterId clusterId, CommandId commandId, std::string payload,
bool & commandHandled, Json::Value & value)
{
// Q1: It seems like this InvokeCommand *never* returns success status.
if (epId >= FIXED_ENDPOINT_COUNT)
{
JNIEnv * env = JniReferences::GetInstance().GetEnvForCurrentThread();
Expand Down Expand Up @@ -153,6 +147,7 @@ Status ContentAppCommandDelegate::InvokeCommand(EndpointId epId, ClusterId clust
return chip::Protocols::InteractionModel::Status::Failure;
}

// Q: It would seem that this is the successfull return case (?)
return chip::Protocols::InteractionModel::Status::UnsupportedEndpoint;
}
else
Expand Down Expand Up @@ -231,20 +226,30 @@ void ContentAppCommandDelegate::FormatResponseData(CommandHandlerInterface::Hand
}

case app::Clusters::AccountLogin::Id: {
if (app::Clusters::AccountLogin::Commands::GetSetupPIN::Id != handlerContext.mRequestPath.mCommandId)
{
// No response for other commands in this cluster
break;
}
Status status;
GetSetupPINResponseType getSetupPINresponse = FormatGetSetupPINResponse(value, status);
if (status != chip::Protocols::InteractionModel::Status::Success)
{
handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, status);
}
else
{
handlerContext.mCommandHandler.AddResponse(handlerContext.mRequestPath, getSetupPINresponse);
switch (handlerContext.mRequestPath.mCommandId) {
case app::Clusters::AccountLogin::Commands::GetSetupPIN::Id: {
Status status;
GetSetupPINResponseType getSetupPINresponse = FormatGetSetupPINResponse(value, status);
if (status != chip::Protocols::InteractionModel::Status::Success)
{
handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, status);
}
else
{
handlerContext.mCommandHandler.AddResponse(handlerContext.mRequestPath, getSetupPINresponse);
}
break;
}
case app::Clusters::AccountLogin::Commands::Login::Id: {
handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, FormatStatusResponse(value));
break;
}
case app::Clusters::AccountLogin::Commands::Logout::Id: {
handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, FormatStatusResponse(value));
break;
}
default:
break;
}
break;
}
Expand Down Expand Up @@ -342,5 +347,16 @@ GetSetupPINResponseType ContentAppCommandDelegate::FormatGetSetupPINResponse(Jso
return getSetupPINresponse;
}

Status ContentAppCommandDelegate::FormatStatusResponse(Json::Value value)
{
if (value.isUInt()) {
return static_cast<Protocols::InteractionModel::Status>(value.asUInt());
}
else
{
return chip::Protocols::InteractionModel::Status::Failure;
}
}

} // namespace AppPlatform
} // namespace chip
2 changes: 2 additions & 0 deletions examples/tv-app/android/java/ContentAppCommandDelegate.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#include <json/json.h>
#include <lib/core/DataModelTypes.h>
#include <lib/support/JniReferences.h>
#include <protocols/interaction_model/StatusCode.h>

#include <string>

Expand Down Expand Up @@ -75,6 +76,7 @@ class ContentAppCommandDelegate : public CommandHandlerInterface
LaunchResponseType FormatContentLauncherResponse(Json::Value value, Status & status);
NavigateTargetResponseType FormatNavigateTargetResponse(Json::Value value, Status & status);
PlaybackResponseType FormatMediaPlaybackResponse(Json::Value value, Status & status);
Status FormatStatusResponse(Json::Value value);

private:
void InitializeJNIObjects(jobject manager)
Expand Down
26 changes: 26 additions & 0 deletions src/controller/CommissionerDiscoveryController.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include <controller/CommissionerDiscoveryController.h>
#include <platform/CHIPDeviceLayer.h>
#include <setup_payload/AdditionalDataPayloadGenerator.h>
#include <app/app-platform/ContentAppPlatform.h>

#if CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY

Expand Down Expand Up @@ -405,6 +406,7 @@ void CommissionerDiscoveryController::InternalHandleContentAppPasscodeResponse()
ValidateSession();
uint32_t passcode = mPasscode;

// Q: Seems like getting rotating ID is done twice - once here and once in InternalOk. Is this necessary or could it be cached?
if (mUdcServer == nullptr)
{
ChipLogError(AppServer, "UX Ok - HandleContentAppPasscodeResponse: no udc server");
Expand Down Expand Up @@ -440,6 +442,9 @@ void CommissionerDiscoveryController::InternalHandleContentAppPasscodeResponse()
}
CharSpan rotatingIdSpan(rotatingIdBuffer, 2 * rotatingIdLength);

// Store rotating ID as tempAccountIdentifier to use in AccountLogin::Login payload later.
mTempAccountIdentifier = rotatingIdSpan;

// first step of commissioner passcode
ChipLogError(AppServer, "UX Ok: commissioner passcode, sending CDC");
// generate a passcode
Expand All @@ -455,6 +460,10 @@ void CommissionerDiscoveryController::InternalHandleContentAppPasscodeResponse()
cd, Transport::PeerAddress::UDP(client->GetPeerAddress().GetIPAddress(), client->GetCdPort()));
return;
}
// Store commissioner passcode as setup PIN (string/charspan).
// If this PIN is not empty it signals that user prompt was shown to enter PIN and AccountLogin::Login command has to be sent.
mCommissionerSetupPin = CharSpan::fromCharString(std::to_string(passcode));

client->SetCachedCommissionerPasscode(passcode);
client->SetUDCClientProcessingState(UDCClientProcessingState::kWaitingForCommissionerPasscodeReady);

Expand Down Expand Up @@ -606,6 +615,23 @@ void CommissionerDiscoveryController::CommissioningSucceeded(uint16_t vendorId,
mVendorId = vendorId;
mProductId = productId;
mNodeId = nodeId;

// Send AccountLogin::Login command if user was prompted to enter setup PIN manually.
// Q: Is this the correct place to have this logic?
// Q: Is there an easier way call AccountLoginDelegate from here?
if (!mCommissionerSetupPIN.empty()) {
ChipLogProgress(AppServer, "UX ComissioningSucceeded with setupPIN prompt flow");
auto app = ContentAppPlatform::GetInstance().LoadContentAppByClient(vendorId, productId);
if (app == nullptr) {
ChipLogError(AppServer, "UX ComissioningSucceeded with setupPIN prompt flow: Failed to get ContentApp");
// Q: Any action to take?
} else {
auto status = app->GetAccountLoginDelegate()->HandleLogin(mTempAccountIdentifier, mCommissionerSetupPin, {mNodeId});
ChipLogProgress(AppServer, "UX ComissioningSucceeded with setupPIN prompt flow: HandleLogin response status: %d", status);
// Q: Any action to take here if status is true/false?
}
}

if (mPostCommissioningListener != nullptr)
{
ChipLogDetail(Controller, "CommissionerDiscoveryController calling listener");
Expand Down
3 changes: 3 additions & 0 deletions src/controller/CommissionerDiscoveryController.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@

#if CHIP_DEVICE_CONFIG_ENABLE_COMMISSIONER_DISCOVERY

using chip::CharSpan;
using chip::NodeId;
using chip::OperationalSessionSetup;
using chip::Protocols::UserDirectedCommissioning::UDCClientState;
Expand Down Expand Up @@ -451,6 +452,8 @@ class CommissionerDiscoveryController : public chip::Protocols::UserDirectedComm
uint16_t mProductId = 0;
NodeId mNodeId = 0;
uint32_t mPasscode = 0;
CharSpan mTempAccountIdentifier;
CharSpan mCommissionerSetupPin;

UserDirectedCommissioningServer * mUdcServer = nullptr;
UserPrompter * mUserPrompter = nullptr;
Expand Down

0 comments on commit 9767da1

Please sign in to comment.