Skip to content

Commit

Permalink
Doorlock: support user management and PIN (#20863)
Browse files Browse the repository at this point in the history
Co-authored-by: Nikita Solianik <[email protected]>
  • Loading branch information
tima-q and Nikita Solianik authored Jul 18, 2022
1 parent e79fcbe commit e4cfa0a
Show file tree
Hide file tree
Showing 5 changed files with 224 additions and 21 deletions.
7 changes: 7 additions & 0 deletions examples/lock-app/qpg/include/AppConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,11 @@
#define SWU_INTERVAl_WINDOW_MIN_MS (23 * 60 * 60 * 1000) // 23 hours
#define SWU_INTERVAl_WINDOW_MAX_MS (24 * 60 * 60 * 1000) // 24 hours

// Maximum number of users supported by lock
#define CONFIG_LOCK_NUM_USERS (5)
// Maximum number of credentials supported by lock
#define CONFIG_LOCK_NUM_CREDENTIALS (10)
// Maximum number of credentials per user supported by lock
#define CONFIG_LOCK_NUM_CREDENTIALS_PER_USER (2)

#endif // APP_CONFIG_H
33 changes: 33 additions & 0 deletions examples/lock-app/qpg/include/BoltLockManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,21 @@
#include <stdbool.h>
#include <stdint.h>

#include "AppConfig.h"
#include "AppEvent.h"

#include <app/clusters/door-lock-server/door-lock-server.h>

#include "FreeRTOS.h"
#include "timers.h" // provides FreeRTOS timer support

#include <lib/core/CHIPError.h>
#include <lib/core/ClusterEnums.h>

class BoltLockManager
{
public:
static constexpr size_t kMaxCredentialLength = 128;
enum Action_t
{
LOCK_ACTION = 0,
Expand All @@ -47,6 +52,17 @@ class BoltLockManager
kState_UnlockingCompleted,
} State;

struct UserData
{
char mName[DOOR_LOCK_USER_NAME_BUFFER_SIZE];
DlCredential mCredentials[CONFIG_LOCK_NUM_CREDENTIALS_PER_USER];
};

struct CredentialData
{
chip::Platform::ScopedMemoryBuffer<uint8_t> mSecret;
};

CHIP_ERROR Init();
bool IsUnlocked();
void EnableAutoRelock(bool aOn);
Expand All @@ -57,6 +73,17 @@ class BoltLockManager
typedef void (*Callback_fn_initiated)(Action_t, int32_t aActor);
typedef void (*Callback_fn_completed)(Action_t);
void SetCallbacks(Callback_fn_initiated aActionInitiated_CB, Callback_fn_completed aActionCompleted_CB);
bool GetUser(uint16_t userIndex, EmberAfPluginDoorLockUserInfo & user) const;
bool SetUser(uint16_t userIndex, chip::FabricIndex creator, chip::FabricIndex modifier, const chip::CharSpan & userName,
uint32_t uniqueId, DlUserStatus userStatus, DlUserType userType, DlCredentialRule credentialRule,
const DlCredential * credentials, size_t totalCredentials);

bool GetCredential(uint16_t credentialIndex, DlCredentialType credentialType,
EmberAfPluginDoorLockCredentialInfo & credential) const;
bool SetCredential(uint16_t credentialIndex, chip::FabricIndex creator, chip::FabricIndex modifier,
DlCredentialStatus credentialStatus, DlCredentialType credentialType, const chip::ByteSpan & secret);

bool ValidatePIN(const Optional<chip::ByteSpan> & pinCode, DlOperationError & err) const;

private:
friend BoltLockManager & BoltLockMgr(void);
Expand All @@ -77,6 +104,12 @@ class BoltLockManager
static void ActuatorMovementTimerEventHandler(AppEvent * aEvent);

static BoltLockManager sLock;

UserData mUserData[CONFIG_LOCK_NUM_USERS];
EmberAfPluginDoorLockUserInfo mUsers[CONFIG_LOCK_NUM_USERS] = {};

CredentialData mCredentialData[CONFIG_LOCK_NUM_CREDENTIALS];
EmberAfPluginDoorLockCredentialInfo mCredentials[CONFIG_LOCK_NUM_CREDENTIALS] = {};
};

inline BoltLockManager & BoltLockMgr(void)
Expand Down
113 changes: 113 additions & 0 deletions examples/lock-app/qpg/src/BoltLockManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
#include "AppTask.h"
#include <FreeRTOS.h>

using namespace chip;

BoltLockManager BoltLockManager::sLock;

TimerHandle_t sLockTimer;
Expand Down Expand Up @@ -90,6 +92,117 @@ void BoltLockManager::SetAutoLockDuration(uint32_t aDurationInSecs)
mAutoLockDuration = aDurationInSecs;
}

bool BoltLockManager::GetUser(uint16_t userIndex, EmberAfPluginDoorLockUserInfo & user) const
{
user = mUsers[userIndex - 1];

ChipLogProgress(Zcl, "Getting lock user %u: %s", static_cast<unsigned>(userIndex),
user.userStatus == DlUserStatus::kAvailable ? "available" : "occupied");

return true;
}

bool BoltLockManager::SetUser(uint16_t userIndex, FabricIndex creator, FabricIndex modifier, const CharSpan & userName,
uint32_t uniqueId, DlUserStatus userStatus, DlUserType userType, DlCredentialRule credentialRule,
const DlCredential * credentials, size_t totalCredentials)
{
UserData & userData = mUserData[userIndex - 1];
auto & user = mUsers[userIndex - 1];

VerifyOrReturnError(userName.size() <= DOOR_LOCK_MAX_USER_NAME_SIZE, false);
VerifyOrReturnError(totalCredentials <= CONFIG_LOCK_NUM_CREDENTIALS_PER_USER, false);

Platform::CopyString(userData.mName, userName);
memcpy(userData.mCredentials, credentials, totalCredentials * sizeof(DlCredential));

user.userName = CharSpan(userData.mName, userName.size());
user.credentials = Span<const DlCredential>(userData.mCredentials, totalCredentials);
user.userUniqueId = uniqueId;
user.userStatus = userStatus;
user.userType = userType;
user.credentialRule = credentialRule;
user.creationSource = DlAssetSource::kMatterIM;
user.createdBy = creator;
user.modificationSource = DlAssetSource::kMatterIM;
user.lastModifiedBy = modifier;

ChipLogProgress(Zcl, "Setting lock user %u: %s", static_cast<unsigned>(userIndex),
userStatus == DlUserStatus::kAvailable ? "available" : "occupied");

return true;
}

bool BoltLockManager::GetCredential(uint16_t credentialIndex, DlCredentialType credentialType,
EmberAfPluginDoorLockCredentialInfo & credential) const
{
VerifyOrReturnError(credentialIndex > 0 && credentialIndex <= CONFIG_LOCK_NUM_CREDENTIALS, false);

credential = mCredentials[credentialIndex - 1];

ChipLogProgress(Zcl, "Getting lock credential %u: %s", static_cast<unsigned>(credentialIndex),
credential.status == DlCredentialStatus::kAvailable ? "available" : "occupied");

return true;
}

bool BoltLockManager::SetCredential(uint16_t credentialIndex, FabricIndex creator, FabricIndex modifier,
DlCredentialStatus credentialStatus, DlCredentialType credentialType, const ByteSpan & secret)
{
VerifyOrReturnError(credentialIndex > 0 && credentialIndex <= CONFIG_LOCK_NUM_CREDENTIALS, false);
VerifyOrReturnError(secret.size() <= kMaxCredentialLength, false);

CredentialData & credentialData = mCredentialData[credentialIndex - 1];
auto & credential = mCredentials[credentialIndex - 1];

if (!secret.empty())
{
memcpy(credentialData.mSecret.Alloc(secret.size()).Get(), secret.data(), secret.size());
}

credential.status = credentialStatus;
credential.credentialType = credentialType;
credential.credentialData = ByteSpan(credentialData.mSecret.Get(), secret.size());
credential.creationSource = DlAssetSource::kMatterIM;
credential.createdBy = creator;
credential.modificationSource = DlAssetSource::kMatterIM;
credential.lastModifiedBy = modifier;

ChipLogProgress(Zcl, "Setting lock credential %u: %s", static_cast<unsigned>(credentialIndex),
credential.status == DlCredentialStatus::kAvailable ? "available" : "occupied");

return true;
}

bool BoltLockManager::ValidatePIN(const Optional<ByteSpan> & pinCode, DlOperationError & err) const
{
// Optionality of the PIN code is validated by the caller, so assume it is OK not to provide the PIN code.
if (!pinCode.HasValue())
{
return true;
}
ChipLogProgress(Zcl, "ValidatePIN %.*s", static_cast<int>(pinCode.Value().size()), pinCode.Value().data());

// Check the PIN code
for (const auto & credential : mCredentials)
{
if (credential.status == DlCredentialStatus::kAvailable || credential.credentialType != DlCredentialType::kPin)
{
continue;
}

if (credential.credentialData.data_equal(pinCode.Value()))
{
ChipLogDetail(Zcl, "Valid lock PIN code provided");
return true;
}
}

ChipLogDetail(Zcl, "Invalid lock PIN code provided");
err = DlOperationError::kInvalidCredential;

return false;
}

bool BoltLockManager::InitiateAction(int32_t aActor, Action_t aAction)
{
bool action_initiated = false;
Expand Down
90 changes: 70 additions & 20 deletions examples/lock-app/qpg/src/ZclCallbacks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "AppTask.h"
#include "BoltLockManager.h"

#include <app-common/zap-generated/attributes/Accessors.h>
#include <app-common/zap-generated/ids/Attributes.h>
#include <app-common/zap-generated/ids/Clusters.h>
#include <app/ConcreteAttributePath.h>
Expand All @@ -27,13 +28,22 @@

using namespace ::chip;
using namespace ::chip::app::Clusters;
using namespace ::chip::app::Clusters::DoorLock;

void MatterPostAttributeChangeCallback(const chip::app::ConcreteAttributePath & path, uint8_t type, uint16_t size, uint8_t * value)
{
if (path.mClusterId != OnOff::Id)
VerifyOrReturn(path.mClusterId == DoorLock::Id && path.mAttributeId == DoorLock::Attributes::LockState::Id);

switch (*value)
{
ChipLogProgress(Zcl, "Unknown cluster ID: " ChipLogFormatMEI, ChipLogValueMEI(path.mClusterId));
return;
case to_underlying(DlLockState::kLocked):
BoltLockMgr().InitiateAction(0, BoltLockManager::LOCK_ACTION);
break;
case to_underlying(DlLockState::kUnlocked):
BoltLockMgr().InitiateAction(0, BoltLockManager::UNLOCK_ACTION);
break;
default:
break;
}

if (path.mAttributeId != OnOff::Attributes::OnOff::Id)
Expand All @@ -45,22 +55,62 @@ void MatterPostAttributeChangeCallback(const chip::app::ConcreteAttributePath &
BoltLockMgr().InitiateAction(0, *value ? BoltLockManager::LOCK_ACTION : BoltLockManager::UNLOCK_ACTION);
}

/** @brief OnOff Cluster Init
*
* This function is called when a specific cluster is initialized. It gives the
* application an opportunity to take care of cluster initialization procedures.
* It is called exactly once for each endpoint where cluster is present.
*
* @param endpoint Ver.: always
*
* TODO Issue #3841
* emberAfOnOffClusterInitCallback happens before the stack initialize the cluster
* attributes to the default value.
* The logic here expects something similar to the deprecated Plugins callback
* emberAfPluginOnOffClusterServerPostInitCallback.
*
*/
void emberAfOnOffClusterInitCallback(EndpointId endpoint)
bool emberAfPluginDoorLockGetUser(EndpointId endpointId, uint16_t userIndex, EmberAfPluginDoorLockUserInfo & user)
{
return BoltLockMgr().GetUser(userIndex, user);
}

bool emberAfPluginDoorLockSetUser(EndpointId endpointId, uint16_t userIndex, FabricIndex creator, FabricIndex modifier,
const CharSpan & userName, uint32_t uniqueId, DlUserStatus userStatus, DlUserType userType,
DlCredentialRule credentialRule, const DlCredential * credentials, size_t totalCredentials)
{
return BoltLockMgr().SetUser(userIndex, creator, modifier, userName, uniqueId, userStatus, userType, credentialRule,
credentials, totalCredentials);
}

bool emberAfPluginDoorLockGetCredential(EndpointId endpointId, uint16_t credentialIndex, DlCredentialType credentialType,
EmberAfPluginDoorLockCredentialInfo & credential)
{
return BoltLockMgr().GetCredential(credentialIndex, credentialType, credential);
}

bool emberAfPluginDoorLockSetCredential(EndpointId endpointId, uint16_t credentialIndex, FabricIndex creator, FabricIndex modifier,
DlCredentialStatus credentialStatus, DlCredentialType credentialType,
const ByteSpan & secret)
{
return BoltLockMgr().SetCredential(credentialIndex, creator, modifier, credentialStatus, credentialType, secret);
}

bool emberAfPluginDoorLockOnDoorLockCommand(chip::EndpointId endpointId, const Optional<ByteSpan> & pinCode, DlOperationError & err)
{
return BoltLockMgr().ValidatePIN(pinCode, err);
}

bool emberAfPluginDoorLockOnDoorUnlockCommand(chip::EndpointId endpointId, const Optional<ByteSpan> & pinCode,
DlOperationError & err)
{
return BoltLockMgr().ValidatePIN(pinCode, err);
}

void emberAfDoorLockClusterInitCallback(EndpointId endpoint)
{
GetAppTask().UpdateClusterState();
DoorLockServer::Instance().InitServer(endpoint);

const auto logOnFailure = [](EmberAfStatus status, const char * attributeName) {
if (status != EMBER_ZCL_STATUS_SUCCESS)
{
ChipLogError(Zcl, "Failed to set DoorLock %s: %x", attributeName, status);
}
};

logOnFailure(DoorLock::Attributes::LockType::Set(endpoint, DlLockType::kDeadBolt), "type");
logOnFailure(DoorLock::Attributes::NumberOfTotalUsersSupported::Set(endpoint, CONFIG_LOCK_NUM_USERS), "number of users");
logOnFailure(DoorLock::Attributes::NumberOfPINUsersSupported::Set(endpoint, CONFIG_LOCK_NUM_USERS), "number of PIN users");
logOnFailure(DoorLock::Attributes::NumberOfRFIDUsersSupported::Set(endpoint, 0), "number of RFID users");
logOnFailure(DoorLock::Attributes::NumberOfCredentialsSupportedPerUser::Set(endpoint, CONFIG_LOCK_NUM_CREDENTIALS_PER_USER),
"number of credentials per user");

// Set FeatureMap to (kUsersManagement|kPINCredentials), default is:
// (kUsersManagement|kAccessSchedules|kRFIDCredentials|kPINCredentials) 0x113
logOnFailure(DoorLock::Attributes::FeatureMap::Set(endpoint, 0x101), "feature map");
}
2 changes: 1 addition & 1 deletion src/app/clusters/door-lock-server/door-lock-server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1949,7 +1949,7 @@ DlStatus DoorLockServer::createNewCredentialAndAddItToUser(chip::EndpointId endp
// Not in the spec, but common sense: I don't think we need to modify the credential if user slot is not occupied
if (user.userStatus == DlUserStatus::kAvailable)
{
emberAfDoorLockClusterPrintln("[SetCredential] Unable to add credential to user: user clot is empty "
emberAfDoorLockClusterPrintln("[SetCredential] Unable to add credential to user: user slot is empty "
"[endpointId=%d,credentialIndex=%d,userIndex=%d]",
endpointId, credential.CredentialIndex, userIndex);
return DlStatus::kInvalidField;
Expand Down

0 comments on commit e4cfa0a

Please sign in to comment.