Skip to content

Commit

Permalink
AccountLogin Login/Logout command support (#34162)
Browse files Browse the repository at this point in the history
* AccountLogin Login/Logout command support

[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

* HandleLogin in ContentAppPlatform::ManageClientAccess

* Cache rotating ID in OnUserDirectedCommissioningRequest

* Restyled by google-java-format

* Restyled by clang-format

* Update content app

* Restyled by whitespace

* Restyled by clang-format

* Update response

* Update method name

* Restyled by google-java-format

* Update src/app/app-platform/ContentApp.h

Co-authored-by: chrisdecenzo <61757564+chrisdecenzo@users.noreply.github.com>

* Update content app platform validation

* Update validation logic

* Update logic

* Update responses

* Restyled by clang-format

* Simplify logic

* Restyled by whitespace

* clean up

* Update code

* Update content app with dynamic pin code

* Restyled by google-java-format

* Remove getRotatingIdSpan class methods

* Restyled by clang-format

---------

Co-authored-by: Restyled.io <commits@restyled.io>
Co-authored-by: Lazar Kovacic <lkovacic@amazon.com>
Co-authored-by: chrisdecenzo <61757564+chrisdecenzo@users.noreply.github.com>
  • Loading branch information
4 people authored and pull[bot] committed Nov 22, 2024
1 parent 3824ed0 commit c89f808
Show file tree
Hide file tree
Showing 14 changed files with 254 additions and 68 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ private CommandResponseHolder() {
Clusters.AccountLogin.Id,
Clusters.AccountLogin.Commands.GetSetupPIN.ID,
"{\"0\":\"20202021\"}");
setResponseValue(
Clusters.AccountLogin.Id,
Clusters.AccountLogin.Commands.Login.ID,
// 0 is for success, you can return 1 for failure
"{\"Status\":0}");
setResponseValue(
Clusters.AccountLogin.Id,
Clusters.AccountLogin.Commands.Logout.ID,
// 0 is for success, you can return 1 for failure
"{\"Status\":0}");
};

public static CommandResponseHolder getInstance() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.provider.Settings;
import android.util.Log;
import com.example.contentapp.AttributeHolder;
import com.example.contentapp.CommandResponseHolder;
import com.example.contentapp.MainActivity;
import com.example.contentapp.matter.MatterAgentClient;
import com.matter.tv.app.api.Clusters;
import com.matter.tv.app.api.MatterIntentConstants;

public class MatterCommandReceiver extends BroadcastReceiver {
Expand Down Expand Up @@ -44,6 +46,21 @@ public void onReceive(Context context, Intent intent) {
.append(command)
.toString();
Log.d(TAG, message);

int pinCode =
Settings.Secure.getInt(context.getContentResolver(), "matter_pin_code", 20202021);
Log.d(TAG, "Retrieved pin code:" + pinCode);

CommandResponseHolder.getInstance()
.setResponseValue(
Clusters.AccountLogin.Id,
Clusters.AccountLogin.Commands.GetSetupPIN.ID,
"{\""
+ Clusters.AccountLogin.Commands.GetSetupPINResponse.Fields.SetupPIN
+ "\":\""
+ pinCode
+ "\"}");

String response =
CommandResponseHolder.getInstance().getCommandResponse(clusterId, commandId);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,38 +24,105 @@
#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"("})";
}

} // namespace

AccountLoginManager::AccountLoginManager(ContentAppCommandDelegate * commandDelegate, const char * setupPin) :
mCommandDelegate(commandDelegate)
{
CopyString(mSetupPin, sizeof(mSetupPin), setupPin);
}

bool AccountLoginManager::HandleLogin(const CharSpan & tempAccountIdentifier, const CharSpan & setupPin,
bool AccountLoginManager::HandleLogin(const CharSpan & tempAccountIdentifier, const CharSpan & setupPIN,
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

if (tempAccountIdentifier.empty() || setupPIN.empty() || !nodeId.HasValue())
{
ChipLogProgress(Zcl, "AccountLoginManager::HandleLogin failed expected pin %s", mSetupPin);
ChipLogError(Zcl, "Invalid parameters");
return false;
}

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

auto status = mCommandDelegate->InvokeCommand(mEndpointId, AccountLogin::Id, AccountLogin::Commands::Login::Id,
serializeLoginCommand(cmd), commandHandled, response);
if (status == Status::Success)
{
// Format status response to verify that response is non-failure.
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 };

auto status = mCommandDelegate->InvokeCommand(mEndpointId, AccountLogin::Id, AccountLogin::Commands::Logout::Id,
serializeLogoutCommand(cmd), commandHandled, response);

if (status == Status::Success)
{
// Format status response to verify that response is non-failure.
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
2 changes: 1 addition & 1 deletion examples/tv-app/android/java/AppImpl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -417,7 +417,7 @@ void refreshConnectedClientsAcl(uint16_t vendorId, uint16_t productId, ContentAp

for (const auto & allowedVendor : app->GetApplicationBasicDelegate()->GetAllowedVendorList())
{
std::set<NodeId> tempNodeIds = ContentAppPlatform::GetInstance().GetNodeIdsForAllowVendorId(allowedVendor);
std::set<NodeId> tempNodeIds = ContentAppPlatform::GetInstance().GetNodeIdsForAllowedVendorId(allowedVendor);

nodeIds.insert(tempNodeIds.begin(), tempNodeIds.end());
}
Expand Down
66 changes: 44 additions & 22 deletions examples/tv-app/android/java/ContentAppCommandDelegate.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,9 @@
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";
const std::string FAILURE_KEY = "PlatformError";
const std::string FAILURE_STATUS_KEY = "Status";
const std::string RESPONSE_STATUS_KEY = "Status";

bool isValidJson(const char * response)
{
Expand Down Expand Up @@ -166,6 +160,7 @@ Status ContentAppCommandDelegate::InvokeCommand(EndpointId epId, ClusterId clust
ChipLogError(Zcl, "Java exception in ContentAppCommandDelegate::sendCommand");
env->ExceptionDescribe();
env->ExceptionClear();
return chip::Protocols::InteractionModel::Status::Failure;
}
else
{
Expand Down Expand Up @@ -198,7 +193,8 @@ Status ContentAppCommandDelegate::InvokeCommand(EndpointId epId, ClusterId clust
}
env->DeleteLocalRef(resp);

// handle errors from platform-app
// Parse response here in case there is failure response.
// Return non-success error code to indicate to caller it should not parse response.
if (!value[FAILURE_KEY].empty())
{
value = value[FAILURE_KEY];
Expand All @@ -209,7 +205,9 @@ Status ContentAppCommandDelegate::InvokeCommand(EndpointId epId, ClusterId clust
return chip::Protocols::InteractionModel::Status::Failure;
}

return chip::Protocols::InteractionModel::Status::UnsupportedEndpoint;
// Return success to indicate command has been sent, response returned and parsed successfully.
// Caller has to manually parse value input/output parameter to get response status/object.
return chip::Protocols::InteractionModel::Status::Success;
}
else
{
Expand Down Expand Up @@ -282,20 +280,31 @@ void ContentAppCommandDelegate::FormatResponseData(CommandHandlerInterface::Hand
}

case app::Clusters::AccountLogin::Id: {
if (app::Clusters::AccountLogin::Commands::GetSetupPIN::Id != handlerContext.mRequestPath.mCommandId)
switch (handlerContext.mRequestPath.mCommandId)
{
// No response for other commands in this cluster
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;
}
Status status;
GetSetupPINResponseType getSetupPINresponse = FormatGetSetupPINResponse(value, status);
if (status != chip::Protocols::InteractionModel::Status::Success)
{
handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, status);
case app::Clusters::AccountLogin::Commands::Login::Id: {
handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, FormatStatusResponse(value));
break;
}
else
{
handlerContext.mCommandHandler.AddResponse(handlerContext.mRequestPath, getSetupPINresponse);
case app::Clusters::AccountLogin::Commands::Logout::Id: {
handlerContext.mCommandHandler.AddStatus(handlerContext.mRequestPath, FormatStatusResponse(value));
break;
}
default:
break;
}
break;
}
Expand Down Expand Up @@ -388,10 +397,23 @@ GetSetupPINResponseType ContentAppCommandDelegate::FormatGetSetupPINResponse(Jso
}
else
{
getSetupPINresponse.setupPIN = "";
status = chip::Protocols::InteractionModel::Status::Failure;
}
return getSetupPINresponse;
}

Status ContentAppCommandDelegate::FormatStatusResponse(Json::Value value)
{
// check if JSON has "Status" key
if (!value[RESPONSE_STATUS_KEY].empty() && !value[RESPONSE_STATUS_KEY].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
35 changes: 23 additions & 12 deletions examples/tv-app/android/java/TVApp-JNI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -258,15 +258,15 @@ SampleTvAppInstallationService gSampleTvAppInstallationService;

class MyPostCommissioningListener : public PostCommissioningListener
{
void CommissioningCompleted(uint16_t vendorId, uint16_t productId, NodeId nodeId, Messaging::ExchangeManager & exchangeMgr,
const SessionHandle & sessionHandle) override
void CommissioningCompleted(uint16_t vendorId, uint16_t productId, NodeId nodeId, CharSpan rotatingId, uint32_t passcode,
Messaging::ExchangeManager & exchangeMgr, const SessionHandle & sessionHandle) override
{
// read current binding list
chip::Controller::ClusterBase cluster(exchangeMgr, sessionHandle, kTargetBindingClusterEndpointId);

ContentAppPlatform::GetInstance().StoreNodeIdForContentApp(vendorId, productId, nodeId);

cacheContext(vendorId, productId, nodeId, exchangeMgr, sessionHandle);
cacheContext(vendorId, productId, nodeId, rotatingId, passcode, exchangeMgr, sessionHandle);

CHIP_ERROR err =
cluster.ReadAttribute<Binding::Attributes::Binding::TypeInfo>(this, OnReadSuccessResponse, OnReadFailureResponse);
Expand Down Expand Up @@ -350,17 +350,23 @@ class MyPostCommissioningListener : public PostCommissioningListener

Optional<SessionHandle> opt = mSecureSession.Get();
SessionHandle & sessionHandle = opt.Value();
auto rotatingIdSpan = CharSpan{ mRotatingId.data(), mRotatingId.size() };
ContentAppPlatform::GetInstance().ManageClientAccess(*mExchangeMgr, sessionHandle, mVendorId, mProductId, localNodeId,
bindings, OnSuccessResponse, OnFailureResponse);
rotatingIdSpan, mPasscode, bindings, OnSuccessResponse,
OnFailureResponse);
clearContext();
}

void cacheContext(uint16_t vendorId, uint16_t productId, NodeId nodeId, Messaging::ExchangeManager & exchangeMgr,
const SessionHandle & sessionHandle)
void cacheContext(uint16_t vendorId, uint16_t productId, NodeId nodeId, CharSpan rotatingId, uint32_t passcode,
Messaging::ExchangeManager & exchangeMgr, const SessionHandle & sessionHandle)
{
mVendorId = vendorId;
mProductId = productId;
mNodeId = nodeId;
mVendorId = vendorId;
mProductId = productId;
mNodeId = nodeId;
mRotatingId = std::string{
rotatingId.data(), rotatingId.size()
}; // Allocates and copies to string instead of storing span to make sure lifetime is valid.
mPasscode = passcode;
mExchangeMgr = &exchangeMgr;
mSecureSession.ShiftToSession(sessionHandle);
}
Expand All @@ -370,12 +376,17 @@ class MyPostCommissioningListener : public PostCommissioningListener
mVendorId = 0;
mProductId = 0;
mNodeId = 0;
mRotatingId = {};
mPasscode = 0;
mExchangeMgr = nullptr;
mSecureSession.SessionReleased();
}
uint16_t mVendorId = 0;
uint16_t mProductId = 0;
NodeId mNodeId = 0;

uint16_t mVendorId = 0;
uint16_t mProductId = 0;
NodeId mNodeId = 0;
std::string mRotatingId;
uint32_t mPasscode = 0;
Messaging::ExchangeManager * mExchangeMgr = nullptr;
SessionHolder mSecureSession;
};
Expand Down
Loading

0 comments on commit c89f808

Please sign in to comment.