From 59ad446456e203c3e374a6d355401f4ec344021c Mon Sep 17 00:00:00 2001 From: Boris Zbarsky Date: Tue, 9 Jul 2024 23:51:13 -0400 Subject: [PATCH] Add Aliro delegate support to the example lock app. --- .../lock-common/include/LockEndpoint.h | 33 +++- .../lock-common/include/LockManager.h | 7 +- .../lock-app/lock-common/src/LockEndpoint.cpp | 151 +++++++++++++++++- .../lock-app/lock-common/src/LockManager.cpp | 13 +- .../door-lock-server/door-lock-server.cpp | 85 +++++++++- .../door-lock-server/door-lock-server.h | 6 + 6 files changed, 284 insertions(+), 11 deletions(-) diff --git a/examples/lock-app/lock-common/include/LockEndpoint.h b/examples/lock-app/lock-common/include/LockEndpoint.h index bd193d388db02b..aa67a69481f23d 100644 --- a/examples/lock-app/lock-common/include/LockEndpoint.h +++ b/examples/lock-app/lock-common/include/LockEndpoint.h @@ -18,7 +18,9 @@ #pragma once +#include #include +#include #include struct LockUserInfo @@ -38,10 +40,10 @@ struct WeekDaysScheduleInfo; struct YearDayScheduleInfo; struct HolidayScheduleInfo; -static constexpr size_t DOOR_LOCK_CREDENTIAL_INFO_MAX_DATA_SIZE = 20; -static constexpr size_t DOOR_LOCK_CREDENTIAL_INFO_MAX_TYPES = 6; +static constexpr size_t DOOR_LOCK_CREDENTIAL_INFO_MAX_DATA_SIZE = 65; +static constexpr size_t DOOR_LOCK_CREDENTIAL_INFO_MAX_TYPES = 9; -class LockEndpoint +class LockEndpoint : public chip::app::Clusters::DoorLock::Delegate { public: LockEndpoint(chip::EndpointId endpointId, uint16_t numberOfLockUsersSupported, uint16_t numberOfCredentialsSupported, @@ -60,6 +62,7 @@ class LockEndpoint } DoorLockServer::Instance().SetDoorState(endpointId, mDoorState); DoorLockServer::Instance().SetLockState(endpointId, mLockState); + chip::Crypto::DRBG_get_bytes(mAliroReaderGroupSubIdentifier, sizeof(mAliroReaderGroupSubIdentifier)); } inline chip::EndpointId GetEndpointId() const { return mEndpointId; } @@ -100,6 +103,22 @@ class LockEndpoint DlStatus SetSchedule(uint8_t holidayIndex, DlScheduleStatus status, uint32_t localStartTime, uint32_t localEndTime, OperatingModeEnum operatingMode); + // DoorLock::Delegate API. + CHIP_ERROR GetAliroReaderVerificationKey(chip::MutableByteSpan & verificationKey) override; + CHIP_ERROR GetAliroReaderGroupIdentifier(chip::MutableByteSpan & groupIdentifier) override; + CHIP_ERROR GetAliroReaderGroupSubIdentifier(chip::MutableByteSpan & groupSubIdentifier) override; + CHIP_ERROR GetAliroExpeditedTransactionSupportedProtocolVersionAtIndex(size_t index, + chip::MutableByteSpan & protocolVersion) override; + CHIP_ERROR GetAliroGroupResolvingKey(chip::MutableByteSpan & groupResolvingKey) override; + CHIP_ERROR GetAliroSupportedBLEUWBProtocolVersionAtIndex(size_t index, chip::MutableByteSpan & protocolVersion) override; + uint8_t GetAliroBLEAdvertisingVersion() override; + uint16_t GetNumberOfAliroCredentialIssuerKeysSupported() override; + uint16_t GetNumberOfAliroEndpointKeysSupported() override; + CHIP_ERROR SetAliroReaderConfig(const chip::ByteSpan & signingKey, const chip::ByteSpan & verificationKey, + const chip::ByteSpan & groupIdentifier, + const chip::Optional & groupResolvingKey) override; + CHIP_ERROR ClearAliroReaderConfig() override; + private: bool setLockState(const Nullable & fabricIdx, const Nullable & nodeId, DlLockState lockState, const Optional & pin, OperationErrorEnum & err, @@ -130,6 +149,14 @@ class LockEndpoint std::vector> mWeekDaySchedules; std::vector> mYearDaySchedules; std::vector mHolidaySchedules; + + // Actual Aliro state would presumably be stored somewhere else, and persistently; this + // example just stores it in memory for illustration purposes. + uint8_t mAliroReaderVerificationKey[chip::app::Clusters::DoorLock::kAliroReaderVerificationKeySize]; + uint8_t mAliroReaderGroupIdentifier[chip::app::Clusters::DoorLock::kAliroReaderGroupIdentifierSize]; + uint8_t mAliroReaderGroupSubIdentifier[chip::app::Clusters::DoorLock::kAliroReaderGroupSubIdentifierSize]; + uint8_t mAliroGroupResolvingKey[chip::app::Clusters::DoorLock::kAliroGroupResolvingKeySize]; + bool mAliroStateInitialized = false; }; struct LockCredentialInfo diff --git a/examples/lock-app/lock-common/include/LockManager.h b/examples/lock-app/lock-common/include/LockManager.h index 71dcf6f02dc072..fb4299a534622c 100644 --- a/examples/lock-app/lock-common/include/LockManager.h +++ b/examples/lock-app/lock-common/include/LockManager.h @@ -70,7 +70,12 @@ class LockManager private: LockEndpoint * getEndpoint(chip::EndpointId endpointId); - std::vector mEndpoints; + /** + * We store the LockEndpoint instances by pointer, not value, so + * LockEndpoint can have a stable location in memory, which lets it + * implement DoorLock::Delegate. + */ + std::vector> mEndpoints; static LockManager instance; }; diff --git a/examples/lock-app/lock-common/src/LockEndpoint.cpp b/examples/lock-app/lock-common/src/LockEndpoint.cpp index bda27f18ba2c8c..5481d01471cfd0 100644 --- a/examples/lock-app/lock-common/src/LockEndpoint.cpp +++ b/examples/lock-app/lock-common/src/LockEndpoint.cpp @@ -17,10 +17,15 @@ */ #include "LockEndpoint.h" #include +#include #include +#include #include #include +using chip::ByteSpan; +using chip::MutableByteSpan; +using chip::Optional; using chip::to_underlying; using chip::app::DataModel::MakeNullable; @@ -204,7 +209,8 @@ bool LockEndpoint::GetCredential(uint16_t credentialIndex, CredentialTypeEnum cr if (credentialIndex >= mLockCredentials.at(to_underlying(credentialType)).size() || (0 == credentialIndex && CredentialTypeEnum::kProgrammingPIN != credentialType)) { - ChipLogError(Zcl, "Cannot get the credential - index out of range [endpoint=%d,index=%d]", mEndpointId, credentialIndex); + ChipLogError(Zcl, "Cannot get the credential - index out of range [endpoint=%d,index=%d]: %d", mEndpointId, credentialIndex, + static_cast(mLockCredentials.at(to_underlying(credentialType)).size())); return false; } @@ -407,6 +413,149 @@ DlStatus LockEndpoint::SetSchedule(uint8_t holidayIndex, DlScheduleStatus status return DlStatus::kSuccess; } +CHIP_ERROR LockEndpoint::GetAliroReaderVerificationKey(MutableByteSpan & verificationKey) +{ + if (!mAliroStateInitialized) + { + verificationKey.reduce_size(0); + return CHIP_NO_ERROR; + } + + return chip::CopySpanToMutableSpan(ByteSpan(mAliroReaderVerificationKey), verificationKey); +} + +CHIP_ERROR LockEndpoint::GetAliroReaderGroupIdentifier(MutableByteSpan & groupIdentifier) +{ + if (!mAliroStateInitialized) + { + groupIdentifier.reduce_size(0); + return CHIP_NO_ERROR; + } + + return CopySpanToMutableSpan(ByteSpan(mAliroReaderGroupIdentifier), groupIdentifier); +} + +CHIP_ERROR LockEndpoint::GetAliroReaderGroupSubIdentifier(MutableByteSpan & groupSubIdentifier) +{ + return CopySpanToMutableSpan(ByteSpan(mAliroReaderGroupSubIdentifier), groupSubIdentifier); +} + +namespace { + +CHIP_ERROR CopyProtocolVersionIntoSpan(uint16_t protocolVersionValue, MutableByteSpan & protocolVersion) +{ + using namespace chip::app::Clusters::DoorLock; + + static_assert(sizeof(protocolVersionValue) == kAliroProtocolVersionSize); + + if (protocolVersion.size() < kAliroProtocolVersionSize) + { + return CHIP_ERROR_INVALID_ARGUMENT; + } + + // Per Aliro spec, protocol version encoding is big-endian + chip::Encoding::BigEndian::Put16(protocolVersion.data(), protocolVersionValue); + protocolVersion.reduce_size(kAliroProtocolVersionSize); + return CHIP_NO_ERROR; +} + +} // anonymous namespace + +CHIP_ERROR LockEndpoint::GetAliroExpeditedTransactionSupportedProtocolVersionAtIndex(size_t index, + MutableByteSpan & protocolVersion) +{ + // Only claim support for the one known protocol version for now: 0x0100. + constexpr uint16_t knownProtocolVersion = 0x0100; + + if (index > 0) + { + return CHIP_ERROR_PROVIDER_LIST_EXHAUSTED; + } + + return CopyProtocolVersionIntoSpan(knownProtocolVersion, protocolVersion); +} + +CHIP_ERROR LockEndpoint::GetAliroGroupResolvingKey(MutableByteSpan & groupResolvingKey) +{ + if (!mAliroStateInitialized) + { + groupResolvingKey.reduce_size(0); + return CHIP_NO_ERROR; + } + + return CopySpanToMutableSpan(ByteSpan(mAliroGroupResolvingKey), groupResolvingKey); +} + +CHIP_ERROR LockEndpoint::GetAliroSupportedBLEUWBProtocolVersionAtIndex(size_t index, MutableByteSpan & protocolVersion) +{ + // Only claim support for the one known protocol version for now: 0x0100. + constexpr uint16_t knownProtocolVersion = 0x0100; + + if (index > 0) + { + return CHIP_ERROR_PROVIDER_LIST_EXHAUSTED; + } + + return CopyProtocolVersionIntoSpan(knownProtocolVersion, protocolVersion); +} + +uint8_t LockEndpoint::GetAliroBLEAdvertisingVersion() +{ + // For now the only define value of the BLE advertising version for Aliro is 0. + return 0; +} + +uint16_t LockEndpoint::GetNumberOfAliroCredentialIssuerKeysSupported() +{ + using namespace chip::app::Clusters::DoorLock; + + // Our vector has an extra entry at index 0 that is not a valid entry, so + // the actual number of credentials supported is one length than the length. + return static_cast(mLockCredentials.at(to_underlying(CredentialTypeEnum::kAliroCredentialIssuerKey)).size() - 1); +} + +uint16_t LockEndpoint::GetNumberOfAliroEndpointKeysSupported() +{ + using namespace chip::app::Clusters::DoorLock; + + // Our vector has an extra entry at index 0 that is not a valid entry, so + // the actual number of credentials supported is one length than the length. + // + // Also, our arrays are the same size, so we just return the size of one of + // the arrays: that is the cap on the total number of endpoint keys + // supported, which can be of either type. + return static_cast(mLockCredentials.at(to_underlying(CredentialTypeEnum::kAliroEvictableEndpointKey)).size() - 1); +} + +CHIP_ERROR LockEndpoint::SetAliroReaderConfig(const ByteSpan & signingKey, const ByteSpan & verificationKey, + const ByteSpan & groupIdentifier, const Optional & groupResolvingKey) +{ + // We ignore the signing key, since we never do anything with it. + + VerifyOrReturnError(verificationKey.size() == sizeof(mAliroReaderVerificationKey), CHIP_ERROR_INVALID_ARGUMENT); + memcpy(mAliroReaderVerificationKey, verificationKey.data(), sizeof(mAliroReaderVerificationKey)); + + VerifyOrReturnError(groupIdentifier.size() == sizeof(mAliroReaderGroupIdentifier), CHIP_ERROR_INVALID_ARGUMENT); + memcpy(mAliroReaderGroupIdentifier, groupIdentifier.data(), sizeof(mAliroReaderGroupIdentifier)); + + if (groupResolvingKey.HasValue()) + { + VerifyOrReturnError(groupResolvingKey.Value().size() == sizeof(mAliroGroupResolvingKey), CHIP_ERROR_INVALID_ARGUMENT); + memcpy(mAliroGroupResolvingKey, groupResolvingKey.Value().data(), sizeof(mAliroGroupResolvingKey)); + } + + mAliroStateInitialized = true; + return CHIP_NO_ERROR; +} + +CHIP_ERROR LockEndpoint::ClearAliroReaderConfig() +{ + // A real implementation would clear out key data from the other parts of + // the application that might use it. + mAliroStateInitialized = false; + return CHIP_NO_ERROR; +} + bool LockEndpoint::setLockState(const Nullable & fabricIdx, const Nullable & nodeId, DlLockState lockState, const Optional & pin, OperationErrorEnum & err, OperationSourceEnum opSource) diff --git a/examples/lock-app/lock-common/src/LockManager.cpp b/examples/lock-app/lock-common/src/LockManager.cpp index 8fd1ef5441b139..8af66051883aa4 100644 --- a/examples/lock-app/lock-common/src/LockManager.cpp +++ b/examples/lock-app/lock-common/src/LockManager.cpp @@ -20,6 +20,7 @@ #include #include +#include using chip::to_underlying; @@ -98,8 +99,9 @@ bool LockManager::InitEndpoint(chip::EndpointId endpointId) numberOfHolidaySchedules = 10; } - mEndpoints.emplace_back(endpointId, numberOfSupportedUsers, numberOfSupportedCredentials, numberOfWeekDaySchedulesPerUser, - numberOfYearDaySchedulesPerUser, numberOfCredentialsSupportedPerUser, numberOfHolidaySchedules); + mEndpoints.emplace_back(std::make_unique(endpointId, numberOfSupportedUsers, numberOfSupportedCredentials, + numberOfWeekDaySchedulesPerUser, numberOfYearDaySchedulesPerUser, + numberOfCredentialsSupportedPerUser, numberOfHolidaySchedules)); ChipLogProgress(Zcl, "Initialized new lock door endpoint " @@ -107,6 +109,7 @@ bool LockManager::InitEndpoint(chip::EndpointId endpointId) "numberOfCredentialsSupportedPerUser=%d,holidaySchedules=%d]", endpointId, numberOfSupportedUsers, numberOfSupportedCredentials, numberOfWeekDaySchedulesPerUser, numberOfYearDaySchedulesPerUser, numberOfCredentialsSupportedPerUser, numberOfHolidaySchedules); + DoorLockServer::Instance().SetDelegate(endpointId, mEndpoints.back().get()); return true; } @@ -303,11 +306,11 @@ DlStatus LockManager::SetSchedule(chip::EndpointId endpointId, uint8_t holidayIn LockEndpoint * LockManager::getEndpoint(chip::EndpointId endpointId) { - for (auto & mEndpoint : mEndpoints) + for (auto & endpoint : mEndpoints) { - if (mEndpoint.GetEndpointId() == endpointId) + if (endpoint->GetEndpointId() == endpointId) { - return &mEndpoint; + return endpoint.get(); } } return nullptr; 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 4ab8744b4dbd32..762bf54319d79c 100644 --- a/src/app/clusters/door-lock-server/door-lock-server.cpp +++ b/src/app/clusters/door-lock-server/door-lock-server.cpp @@ -957,7 +957,18 @@ void DoorLockServer::clearCredentialCommandHandler( } // Remove all the credentials of the particular type. - auto credentialType = credential.Value().credentialType; + auto credentialType = credential.Value().credentialType; + + if (!credentialTypeSupported(commandPath.mEndpointId, credentialType)) + { + ChipLogProgress(Zcl, + "[ClearCredential] Credential type is not supported [endpointId=%d,credentialType=%u" + "]", + commandPath.mEndpointId, to_underlying(credentialType)); + commandObj->AddStatus(commandPath, Status::InvalidCommand); + return; + } + auto credentialIndex = credential.Value().credentialIndex; if (0xFFFE == credentialIndex) { @@ -2410,6 +2421,42 @@ DlStatus DoorLockServer::createCredential(chip::EndpointId endpointId, chip::Fab return DlStatus::kInvalidField; } + // For Aliro endpoint keys, there is a single shared count for the total + // count of evictable and non-evictable keys that can be stored. This needs + // to be enforced specially, because none of the other logic we have handles that. + if (credentialType == CredentialTypeEnum::kAliroEvictableEndpointKey || + credentialType == CredentialTypeEnum::kAliroNonEvictableEndpointKey) + { + Delegate * delegate = GetDelegate(endpointId); + if (delegate == nullptr) + { + ChipLogError(Zcl, "Door lock delegate is null, can't handle Aliro credentials"); + return DlStatus::kFailure; + } + + size_t maxEndpointKeys = delegate->GetNumberOfAliroEndpointKeysSupported(); + size_t evictableEndpointKeys, nonEvictableEndpointKeys; + + if (!countOccupiedCredentials(endpointId, CredentialTypeEnum::kAliroEvictableEndpointKey, evictableEndpointKeys)) + { + ChipLogError(Zcl, "Unable to count Aliro evictable endpoint keys."); + return DlStatus::kFailure; + } + + if (!countOccupiedCredentials(endpointId, CredentialTypeEnum::kAliroNonEvictableEndpointKey, nonEvictableEndpointKeys)) + { + ChipLogError(Zcl, "Unable to count Aliro non-evictable endpoint keys."); + return DlStatus::kFailure; + } + + if (evictableEndpointKeys + nonEvictableEndpointKeys >= maxEndpointKeys) + { + // We have no space for another credential here. + ChipLogError(Zcl, "Unable to create Aliro endpoint key credential; too many exist already [endpointId=%d]", endpointId); + return DlStatus::kResourceExhausted; + } + } + CredentialStruct credential{ credentialType, credentialIndex }; // appclusters, 5.2.4.40: if userIndex is not provided we should create new user DlStatus status = DlStatus::kSuccess; @@ -2447,6 +2494,42 @@ DlStatus DoorLockServer::createCredential(chip::EndpointId endpointId, chip::Fab return status; } +bool DoorLockServer::countOccupiedCredentials(chip::EndpointId endpointId, CredentialTypeEnum credentialType, + size_t & occupiedCount) +{ + uint16_t maxCredentialCount; + + if (!getMaxNumberOfCredentials(endpointId, credentialType, maxCredentialCount)) + { + return false; + } + + uint16_t startIndex = 1; + // Programming PIN is a special case -- it is unique and its index assumed to be 0. + if (CredentialTypeEnum::kProgrammingPIN == credentialType) + { + startIndex = 0; + maxCredentialCount--; + } + + occupiedCount = 0; + for (uint16_t credentialIndex = startIndex; credentialIndex <= maxCredentialCount; ++credentialIndex) + { + EmberAfPluginDoorLockCredentialInfo credential; + if (!emberAfPluginDoorLockGetCredential(endpointId, credentialIndex, credentialType, credential)) + { + return false; + } + + if (credential.status == DlCredentialStatus::kOccupied) + { + ++occupiedCount; + } + } + + return true; +} + DlStatus DoorLockServer::modifyProgrammingPIN(chip::EndpointId endpointId, chip::FabricIndex modifierFabricIndex, chip::NodeId sourceNodeId, uint16_t credentialIndex, CredentialTypeEnum credentialType, diff --git a/src/app/clusters/door-lock-server/door-lock-server.h b/src/app/clusters/door-lock-server/door-lock-server.h index ccb62dde46712b..81b18c60e687e8 100644 --- a/src/app/clusters/door-lock-server/door-lock-server.h +++ b/src/app/clusters/door-lock-server/door-lock-server.h @@ -360,6 +360,12 @@ class DoorLockServer : public chip::app::AttributeAccessInterface const EmberAfPluginDoorLockCredentialInfo & existingCredential, const chip::ByteSpan & credentialData, Nullable userIndex, const Nullable & userStatus, Nullable userType, uint16_t & createdUserIndex); + /** + * countOccupiedCredentials counts the number of occupied credentials of the + * given type. Returns false on application-side errors (i.e. if the count + * cannot be determined). + */ + bool countOccupiedCredentials(chip::EndpointId endpointId, CredentialTypeEnum credentialType, size_t & occupiedCount); DlStatus modifyProgrammingPIN(chip::EndpointId endpointId, chip::FabricIndex modifierFabricIndex, chip::NodeId sourceNodeId, uint16_t credentialIndex, CredentialTypeEnum credentialType, const EmberAfPluginDoorLockCredentialInfo & existingCredential,