From e4cfa0a8e9875f45f883b7ebb0256ec336f234c0 Mon Sep 17 00:00:00 2001 From: Timothy Maes Date: Mon, 18 Jul 2022 15:40:40 +0200 Subject: [PATCH] Doorlock: support user management and PIN (#20863) Co-authored-by: Nikita Solianik --- examples/lock-app/qpg/include/AppConfig.h | 7 ++ .../lock-app/qpg/include/BoltLockManager.h | 33 +++++ examples/lock-app/qpg/src/BoltLockManager.cpp | 113 ++++++++++++++++++ examples/lock-app/qpg/src/ZclCallbacks.cpp | 90 ++++++++++---- .../door-lock-server/door-lock-server.cpp | 2 +- 5 files changed, 224 insertions(+), 21 deletions(-) diff --git a/examples/lock-app/qpg/include/AppConfig.h b/examples/lock-app/qpg/include/AppConfig.h index f49455c7a18846..2f2cb7cd385cf3 100644 --- a/examples/lock-app/qpg/include/AppConfig.h +++ b/examples/lock-app/qpg/include/AppConfig.h @@ -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 diff --git a/examples/lock-app/qpg/include/BoltLockManager.h b/examples/lock-app/qpg/include/BoltLockManager.h index ddb5b9bf0dcb91..005dd8711b5161 100644 --- a/examples/lock-app/qpg/include/BoltLockManager.h +++ b/examples/lock-app/qpg/include/BoltLockManager.h @@ -21,16 +21,21 @@ #include #include +#include "AppConfig.h" #include "AppEvent.h" +#include + #include "FreeRTOS.h" #include "timers.h" // provides FreeRTOS timer support #include +#include class BoltLockManager { public: + static constexpr size_t kMaxCredentialLength = 128; enum Action_t { LOCK_ACTION = 0, @@ -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 mSecret; + }; + CHIP_ERROR Init(); bool IsUnlocked(); void EnableAutoRelock(bool aOn); @@ -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 & pinCode, DlOperationError & err) const; private: friend BoltLockManager & BoltLockMgr(void); @@ -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) diff --git a/examples/lock-app/qpg/src/BoltLockManager.cpp b/examples/lock-app/qpg/src/BoltLockManager.cpp index 39a48fdbdf1981..5799916274219d 100644 --- a/examples/lock-app/qpg/src/BoltLockManager.cpp +++ b/examples/lock-app/qpg/src/BoltLockManager.cpp @@ -23,6 +23,8 @@ #include "AppTask.h" #include +using namespace chip; + BoltLockManager BoltLockManager::sLock; TimerHandle_t sLockTimer; @@ -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(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(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(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(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(credentialIndex), + credential.status == DlCredentialStatus::kAvailable ? "available" : "occupied"); + + return true; +} + +bool BoltLockManager::ValidatePIN(const Optional & 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(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; diff --git a/examples/lock-app/qpg/src/ZclCallbacks.cpp b/examples/lock-app/qpg/src/ZclCallbacks.cpp index 47163c0ab204a6..cb1ef00398bb8b 100644 --- a/examples/lock-app/qpg/src/ZclCallbacks.cpp +++ b/examples/lock-app/qpg/src/ZclCallbacks.cpp @@ -19,6 +19,7 @@ #include "AppTask.h" #include "BoltLockManager.h" +#include #include #include #include @@ -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) @@ -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 & pinCode, DlOperationError & err) +{ + return BoltLockMgr().ValidatePIN(pinCode, err); +} + +bool emberAfPluginDoorLockOnDoorUnlockCommand(chip::EndpointId endpointId, const Optional & 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"); } diff --git a/src/app/clusters/door-lock-server/door-lock-server.cpp b/src/app/clusters/door-lock-server/door-lock-server.cpp index f294e63059daa6..8151fb42618935 100644 --- a/src/app/clusters/door-lock-server/door-lock-server.cpp +++ b/src/app/clusters/door-lock-server/door-lock-server.cpp @@ -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;