| /* |
| * |
| * Copyright (c) 2022-2023 Project CHIP Authors |
| * All rights reserved. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| #include "LockEndpoint.h" |
| #include <app-common/zap-generated/attributes/Accessors.h> |
| #include <app-common/zap-generated/cluster-enums.h> |
| #include <cstring> |
| #include <lib/core/CHIPEncoding.h> |
| #include <platform/CHIPDeviceLayer.h> |
| #include <platform/internal/CHIPDeviceLayerInternal.h> |
| |
| using chip::ByteSpan; |
| using chip::MutableByteSpan; |
| using chip::Optional; |
| using chip::to_underlying; |
| using chip::app::DataModel::MakeNullable; |
| |
| struct LockActionData |
| { |
| chip::EndpointId endpointId; |
| DlLockState lockState; |
| OperationSourceEnum opSource; |
| Nullable<uint16_t> userIndex; |
| uint16_t credentialIndex; |
| Nullable<chip::FabricIndex> fabricIdx; |
| Nullable<chip::NodeId> nodeId; |
| bool moving = false; |
| }; |
| |
| static LockActionData gCurrentAction; |
| |
| bool LockEndpoint::Lock(const Nullable<chip::FabricIndex> & fabricIdx, const Nullable<chip::NodeId> & nodeId, |
| const Optional<chip::ByteSpan> & pin, OperationErrorEnum & err, OperationSourceEnum opSource) |
| { |
| return setLockState(fabricIdx, nodeId, DlLockState::kLocked, pin, err, opSource); |
| } |
| |
| bool LockEndpoint::Unlock(const Nullable<chip::FabricIndex> & fabricIdx, const Nullable<chip::NodeId> & nodeId, |
| const Optional<chip::ByteSpan> & pin, OperationErrorEnum & err, OperationSourceEnum opSource) |
| { |
| if (DoorLockServer::Instance().SupportsUnbolt(mEndpointId)) |
| { |
| // If Unbolt is supported Unlock is supposed to pull the latch |
| return setLockState(fabricIdx, nodeId, DlLockState::kUnlatched, pin, err, opSource); |
| } |
| |
| return setLockState(fabricIdx, nodeId, DlLockState::kUnlocked, pin, err, opSource); |
| } |
| |
| bool LockEndpoint::Unbolt(const Nullable<chip::FabricIndex> & fabricIdx, const Nullable<chip::NodeId> & nodeId, |
| const Optional<chip::ByteSpan> & pin, OperationErrorEnum & err, OperationSourceEnum opSource) |
| { |
| return setLockState(fabricIdx, nodeId, DlLockState::kUnlocked, pin, err, opSource); |
| } |
| |
| bool LockEndpoint::GetUser(uint16_t userIndex, EmberAfPluginDoorLockUserInfo & user) const |
| { |
| ChipLogProgress(Zcl, "Lock App: LockEndpoint::GetUser [endpoint=%d,userIndex=%hu]", mEndpointId, userIndex); |
| |
| auto adjustedUserIndex = static_cast<uint16_t>(userIndex - 1); |
| if (adjustedUserIndex > mLockUsers.size()) |
| { |
| ChipLogError(Zcl, "Cannot get user - index out of range [endpoint=%d,index=%hu,adjustedIndex=%d]", mEndpointId, userIndex, |
| adjustedUserIndex); |
| return false; |
| } |
| |
| const auto & userInDb = mLockUsers[adjustedUserIndex]; |
| user.userStatus = userInDb.userStatus; |
| if (UserStatusEnum::kAvailable == user.userStatus) |
| { |
| ChipLogDetail(Zcl, "Found unoccupied user [endpoint=%d,adjustedIndex=%hu]", mEndpointId, adjustedUserIndex); |
| return true; |
| } |
| |
| user.userName = chip::CharSpan(userInDb.userName, strlen(userInDb.userName)); |
| user.credentials = chip::Span<const CredentialStruct>(userInDb.credentials.data(), userInDb.credentials.size()); |
| user.userUniqueId = userInDb.userUniqueId; |
| user.userType = userInDb.userType; |
| user.credentialRule = userInDb.credentialRule; |
| // So far there's no way to actually create the credential outside the matter, so here we always set the creation/modification |
| // source to Matter |
| user.creationSource = DlAssetSource::kMatterIM; |
| user.createdBy = userInDb.createdBy; |
| user.modificationSource = DlAssetSource::kMatterIM; |
| user.lastModifiedBy = userInDb.lastModifiedBy; |
| |
| ChipLogDetail(Zcl, |
| "Found occupied user " |
| "[endpoint=%d,adjustedIndex=%hu,name=\"%.*s\",credentialsCount=%u,uniqueId=%x,type=%u,credentialRule=%u," |
| "createdBy=%d,lastModifiedBy=%d]", |
| mEndpointId, adjustedUserIndex, static_cast<int>(user.userName.size()), user.userName.data(), |
| static_cast<unsigned int>(user.credentials.size()), user.userUniqueId, to_underlying(user.userType), |
| to_underlying(user.credentialRule), user.createdBy, user.lastModifiedBy); |
| |
| return true; |
| } |
| |
| bool LockEndpoint::SetUser(uint16_t userIndex, chip::FabricIndex creator, chip::FabricIndex modifier, |
| const chip::CharSpan & userName, uint32_t uniqueId, UserStatusEnum userStatus, UserTypeEnum usertype, |
| CredentialRuleEnum credentialRule, const CredentialStruct * credentials, size_t totalCredentials) |
| { |
| ChipLogProgress(Zcl, |
| "Lock App: LockEndpoint::SetUser " |
| "[endpoint=%d,userIndex=%u,creator=%d,modifier=%d,userName=\"%.*s\",uniqueId=%" PRIx32 |
| ",userStatus=%u,userType=%u," |
| "credentialRule=%u,credentials=%p,totalCredentials=%u]", |
| mEndpointId, userIndex, creator, modifier, static_cast<int>(userName.size()), userName.data(), uniqueId, |
| to_underlying(userStatus), to_underlying(usertype), to_underlying(credentialRule), credentials, |
| static_cast<unsigned int>(totalCredentials)); |
| |
| auto adjustedUserIndex = static_cast<uint16_t>(userIndex - 1); |
| if (adjustedUserIndex > mLockUsers.size()) |
| { |
| ChipLogError(Zcl, "Cannot set user - index out of range [endpoint=%d,index=%d,adjustedUserIndex=%u]", mEndpointId, |
| userIndex, adjustedUserIndex); |
| return false; |
| } |
| |
| auto & userInStorage = mLockUsers[adjustedUserIndex]; |
| if (userName.size() > DOOR_LOCK_MAX_USER_NAME_SIZE) |
| { |
| ChipLogError(Zcl, "Cannot set user - user name is too long [endpoint=%d,index=%d,adjustedUserIndex=%u]", mEndpointId, |
| userIndex, adjustedUserIndex); |
| return false; |
| } |
| |
| if (totalCredentials > userInStorage.credentials.capacity()) |
| { |
| ChipLogError(Zcl, |
| "Cannot set user - total number of credentials is too big [endpoint=%d,index=%d,adjustedUserIndex=%u" |
| ",totalCredentials=%u,maxNumberOfCredentials=%u]", |
| mEndpointId, userIndex, adjustedUserIndex, static_cast<unsigned int>(totalCredentials), |
| static_cast<unsigned int>(userInStorage.credentials.capacity())); |
| return false; |
| } |
| |
| chip::Platform::CopyString(userInStorage.userName, userName); |
| userInStorage.userName[userName.size()] = 0; |
| userInStorage.userUniqueId = uniqueId; |
| userInStorage.userStatus = userStatus; |
| userInStorage.userType = usertype; |
| userInStorage.credentialRule = credentialRule; |
| userInStorage.lastModifiedBy = modifier; |
| userInStorage.createdBy = creator; |
| |
| userInStorage.credentials.clear(); |
| for (size_t i = 0; i < totalCredentials; ++i) |
| { |
| userInStorage.credentials.push_back(credentials[i]); |
| } |
| |
| ChipLogProgress(Zcl, "Successfully set the user [mEndpointId=%d,index=%d,adjustedIndex=%d]", mEndpointId, userIndex, |
| adjustedUserIndex); |
| |
| return true; |
| } |
| |
| DoorStateEnum LockEndpoint::GetDoorState() const |
| { |
| return mDoorState; |
| } |
| |
| bool LockEndpoint::SetDoorState(DoorStateEnum newState) |
| { |
| if (mDoorState != newState) |
| { |
| ChipLogProgress(Zcl, "Changing the door state to: %d [endpointId=%d,previousState=%d]", to_underlying(newState), |
| mEndpointId, to_underlying(mDoorState)); |
| |
| mDoorState = newState; |
| return DoorLockServer::Instance().SetDoorState(mEndpointId, mDoorState); |
| } |
| return true; |
| } |
| |
| bool LockEndpoint::SendLockAlarm(AlarmCodeEnum alarmCode) const |
| { |
| ChipLogProgress(Zcl, "Sending the LockAlarm event [endpointId=%d,alarmCode=%u]", mEndpointId, to_underlying(alarmCode)); |
| return DoorLockServer::Instance().SendLockAlarmEvent(mEndpointId, alarmCode); |
| } |
| |
| bool LockEndpoint::GetCredential(uint16_t credentialIndex, CredentialTypeEnum credentialType, |
| EmberAfPluginDoorLockCredentialInfo & credential) const |
| { |
| ChipLogProgress(Zcl, "Lock App: LockEndpoint::GetCredential [endpoint=%d,credentialIndex=%u,credentialType=%u]", mEndpointId, |
| credentialIndex, to_underlying(credentialType)); |
| |
| if (to_underlying(credentialType) >= mLockCredentials.size()) |
| { |
| ChipLogError(Zcl, "Cannot get the credential - index out of range [endpoint=%d,index=%d]", mEndpointId, credentialIndex); |
| return false; |
| } |
| |
| 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]: %d", mEndpointId, credentialIndex, |
| static_cast<int>(mLockCredentials.at(to_underlying(credentialType)).size())); |
| return false; |
| } |
| |
| const auto & credentialInStorage = mLockCredentials[to_underlying(credentialType)][credentialIndex]; |
| |
| credential.status = credentialInStorage.status; |
| if (DlCredentialStatus::kAvailable == credential.status) |
| { |
| ChipLogDetail(Zcl, "Found unoccupied credential [endpoint=%d,index=%u]", mEndpointId, credentialIndex); |
| return true; |
| } |
| credential.credentialType = credentialInStorage.credentialType; |
| credential.credentialData = chip::ByteSpan(credentialInStorage.credentialData, credentialInStorage.credentialDataSize); |
| // So far there's no way to actually create the credential outside the matter, so here we always set the creation/modification |
| // source to Matter |
| credential.creationSource = DlAssetSource::kMatterIM; |
| credential.createdBy = credentialInStorage.createdBy; |
| credential.modificationSource = DlAssetSource::kMatterIM; |
| credential.lastModifiedBy = credentialInStorage.modifiedBy; |
| |
| ChipLogDetail(Zcl, "Found occupied credential [endpoint=%d,index=%u,type=%u,dataSize=%u,createdBy=%u,modifiedBy=%u]", |
| mEndpointId, credentialIndex, to_underlying(credential.credentialType), |
| static_cast<unsigned int>(credential.credentialData.size()), credential.createdBy, credential.lastModifiedBy); |
| |
| return true; |
| } |
| |
| bool LockEndpoint::SetCredential(uint16_t credentialIndex, chip::FabricIndex creator, chip::FabricIndex modifier, |
| DlCredentialStatus credentialStatus, CredentialTypeEnum credentialType, |
| const chip::ByteSpan & credentialData) |
| { |
| ChipLogProgress( |
| Zcl, |
| "Lock App: LockEndpoint::SetCredential " |
| "[endpoint=%d,credentialIndex=%u,credentialStatus=%u,credentialType=%u,credentialDataSize=%u,creator=%u,modifier=%u]", |
| mEndpointId, credentialIndex, to_underlying(credentialStatus), to_underlying(credentialType), |
| static_cast<unsigned int>(credentialData.size()), creator, modifier); |
| |
| if (to_underlying(credentialType) >= mLockCredentials.capacity()) |
| { |
| ChipLogError(Zcl, "Cannot set the credential - type out of range [endpoint=%d,type=%d]", mEndpointId, |
| to_underlying(credentialType)); |
| return false; |
| } |
| |
| if (credentialIndex >= mLockCredentials.at(to_underlying(credentialType)).size() || |
| (0 == credentialIndex && CredentialTypeEnum::kProgrammingPIN != credentialType)) |
| { |
| ChipLogError(Zcl, "Cannot set the credential - index out of range [endpoint=%d,index=%d]", mEndpointId, credentialIndex); |
| return false; |
| } |
| |
| auto & credentialInStorage = mLockCredentials[to_underlying(credentialType)][credentialIndex]; |
| if (credentialData.size() > DOOR_LOCK_CREDENTIAL_INFO_MAX_DATA_SIZE) |
| { |
| ChipLogError(Zcl, |
| "Cannot get the credential - data size exceeds limit " |
| "[endpoint=%d,index=%d,dataSize=%u,maxDataSize=%u]", |
| mEndpointId, credentialIndex, static_cast<unsigned int>(credentialData.size()), |
| static_cast<unsigned int>(DOOR_LOCK_CREDENTIAL_INFO_MAX_DATA_SIZE)); |
| return false; |
| } |
| credentialInStorage.status = credentialStatus; |
| credentialInStorage.credentialType = credentialType; |
| credentialInStorage.createdBy = creator; |
| credentialInStorage.modifiedBy = modifier; |
| std::memcpy(credentialInStorage.credentialData, credentialData.data(), credentialData.size()); |
| credentialInStorage.credentialDataSize = credentialData.size(); |
| |
| ChipLogProgress(Zcl, "Successfully set the credential [mEndpointId=%d,index=%d,credentialType=%u,creator=%u,modifier=%u]", |
| mEndpointId, credentialIndex, to_underlying(credentialType), credentialInStorage.createdBy, |
| credentialInStorage.modifiedBy); |
| |
| return true; |
| } |
| |
| DlStatus LockEndpoint::GetSchedule(uint8_t weekDayIndex, uint16_t userIndex, EmberAfPluginDoorLockWeekDaySchedule & schedule) |
| { |
| if (0 == userIndex || userIndex > mWeekDaySchedules.size()) |
| { |
| return DlStatus::kFailure; |
| } |
| |
| if (0 == weekDayIndex || weekDayIndex > mWeekDaySchedules.at(userIndex - 1).size()) |
| { |
| return DlStatus::kFailure; |
| } |
| |
| const auto & scheduleInStorage = mWeekDaySchedules.at(userIndex - 1).at(weekDayIndex - 1); |
| if (DlScheduleStatus::kAvailable == scheduleInStorage.status) |
| { |
| return DlStatus::kNotFound; |
| } |
| |
| schedule = scheduleInStorage.schedule; |
| |
| return DlStatus::kSuccess; |
| } |
| |
| DlStatus LockEndpoint::SetSchedule(uint8_t weekDayIndex, uint16_t userIndex, DlScheduleStatus status, DaysMaskMap daysMask, |
| uint8_t startHour, uint8_t startMinute, uint8_t endHour, uint8_t endMinute) |
| { |
| if (0 == userIndex || userIndex > mWeekDaySchedules.size()) |
| { |
| return DlStatus::kFailure; |
| } |
| |
| if (0 == weekDayIndex || weekDayIndex > mWeekDaySchedules.at(userIndex - 1).size()) |
| { |
| return DlStatus::kFailure; |
| } |
| |
| auto & scheduleInStorage = mWeekDaySchedules.at(userIndex - 1).at(weekDayIndex - 1); |
| |
| scheduleInStorage.schedule.daysMask = daysMask; |
| scheduleInStorage.schedule.startHour = startHour; |
| scheduleInStorage.schedule.startMinute = startMinute; |
| scheduleInStorage.schedule.endHour = endHour; |
| scheduleInStorage.schedule.endMinute = endMinute; |
| scheduleInStorage.status = status; |
| |
| return DlStatus::kSuccess; |
| } |
| |
| DlStatus LockEndpoint::GetSchedule(uint8_t yearDayIndex, uint16_t userIndex, EmberAfPluginDoorLockYearDaySchedule & schedule) |
| { |
| if (0 == userIndex || userIndex > mYearDaySchedules.size()) |
| { |
| return DlStatus::kFailure; |
| } |
| |
| if (0 == yearDayIndex || yearDayIndex > mYearDaySchedules.at(userIndex - 1).size()) |
| { |
| return DlStatus::kFailure; |
| } |
| |
| const auto & scheduleInStorage = mYearDaySchedules.at(userIndex - 1).at(yearDayIndex - 1); |
| if (DlScheduleStatus::kAvailable == scheduleInStorage.status) |
| { |
| return DlStatus::kNotFound; |
| } |
| |
| schedule = scheduleInStorage.schedule; |
| |
| return DlStatus::kSuccess; |
| } |
| |
| DlStatus LockEndpoint::SetSchedule(uint8_t yearDayIndex, uint16_t userIndex, DlScheduleStatus status, uint32_t localStartTime, |
| uint32_t localEndTime) |
| { |
| if (0 == userIndex || userIndex > mYearDaySchedules.size()) |
| { |
| return DlStatus::kFailure; |
| } |
| |
| if (0 == yearDayIndex || yearDayIndex > mYearDaySchedules.at(userIndex - 1).size()) |
| { |
| return DlStatus::kFailure; |
| } |
| |
| auto & scheduleInStorage = mYearDaySchedules.at(userIndex - 1).at(yearDayIndex - 1); |
| scheduleInStorage.schedule.localStartTime = localStartTime; |
| scheduleInStorage.schedule.localEndTime = localEndTime; |
| scheduleInStorage.status = status; |
| |
| return DlStatus::kSuccess; |
| } |
| |
| DlStatus LockEndpoint::GetSchedule(uint8_t holidayIndex, EmberAfPluginDoorLockHolidaySchedule & schedule) |
| { |
| if (0 == holidayIndex || holidayIndex > mHolidaySchedules.size()) |
| { |
| return DlStatus::kFailure; |
| } |
| |
| const auto & scheduleInStorage = mHolidaySchedules[holidayIndex - 1]; |
| if (DlScheduleStatus::kAvailable == scheduleInStorage.status) |
| { |
| return DlStatus::kNotFound; |
| } |
| |
| schedule = scheduleInStorage.schedule; |
| return DlStatus::kSuccess; |
| } |
| |
| DlStatus LockEndpoint::SetSchedule(uint8_t holidayIndex, DlScheduleStatus status, uint32_t localStartTime, uint32_t localEndTime, |
| OperatingModeEnum operatingMode) |
| { |
| if (0 == holidayIndex || holidayIndex > mHolidaySchedules.size()) |
| { |
| return DlStatus::kFailure; |
| } |
| |
| auto & scheduleInStorage = mHolidaySchedules[holidayIndex - 1]; |
| scheduleInStorage.schedule.localStartTime = localStartTime; |
| scheduleInStorage.schedule.localEndTime = localEndTime; |
| scheduleInStorage.schedule.operatingMode = operatingMode; |
| scheduleInStorage.status = 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<uint16_t>(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<uint16_t>(mLockCredentials.at(to_underlying(CredentialTypeEnum::kAliroEvictableEndpointKey)).size() - 1); |
| } |
| |
| CHIP_ERROR LockEndpoint::SetAliroReaderConfig(const ByteSpan & signingKey, const ByteSpan & verificationKey, |
| const ByteSpan & groupIdentifier, const Optional<ByteSpan> & 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<chip::FabricIndex> & fabricIdx, const Nullable<chip::NodeId> & nodeId, |
| DlLockState lockState, const Optional<chip::ByteSpan> & pin, OperationErrorEnum & err, |
| OperationSourceEnum opSource) |
| { |
| // Assume pin is required until told otherwise |
| bool requirePin = true; |
| chip::app::Clusters::DoorLock::Attributes::RequirePINforRemoteOperation::Get(mEndpointId, &requirePin); |
| |
| // If a pin code is not given |
| if (!pin.HasValue()) |
| { |
| ChipLogDetail(Zcl, "Door Lock App: PIN code is not specified [endpointId=%d]", mEndpointId); |
| |
| // If a pin code is not required |
| if (!requirePin) |
| { |
| ChipLogProgress(Zcl, "Door Lock App: setting door lock state to \"%s\" [endpointId=%d]", lockStateToString(lockState), |
| mEndpointId); |
| |
| if (gCurrentAction.moving == true) |
| { |
| ChipLogProgress(Zcl, "Lock App: not executing lock action as another lock action is already active [endpointId=%d]", |
| mEndpointId); |
| return false; |
| } |
| |
| gCurrentAction.moving = true; |
| gCurrentAction.endpointId = mEndpointId; |
| gCurrentAction.lockState = lockState; |
| gCurrentAction.opSource = opSource; |
| gCurrentAction.userIndex = NullNullable; |
| gCurrentAction.fabricIdx = fabricIdx; |
| gCurrentAction.nodeId = nodeId; |
| |
| // Do this async as a real lock would do too but use 0s delay to speed up CI tests |
| chip::DeviceLayer::SystemLayer().StartTimer(chip::System::Clock::Seconds16(0), OnLockActionCompleteCallback, nullptr); |
| |
| return true; |
| } |
| |
| ChipLogError(Zcl, "Door Lock App: PIN code is not specified, but it is required [endpointId=%d]", mEndpointId); |
| |
| err = OperationErrorEnum::kInvalidCredential; |
| return false; |
| } |
| |
| // Find the credential so we can make sure it is not absent right away |
| auto & pinCredentials = mLockCredentials[to_underlying(CredentialTypeEnum::kPin)]; |
| auto credential = std::find_if(pinCredentials.begin(), pinCredentials.end(), [&pin](const LockCredentialInfo & c) { |
| return (c.status != DlCredentialStatus::kAvailable) && |
| chip::ByteSpan{ c.credentialData, c.credentialDataSize }.data_equal(pin.Value()); |
| }); |
| if (credential == pinCredentials.end()) |
| { |
| ChipLogDetail(Zcl, |
| "Lock App: specified PIN code was not found in the database, ignoring command to set lock state to \"%s\" " |
| "[endpointId=%d]", |
| lockStateToString(lockState), mEndpointId); |
| |
| err = OperationErrorEnum::kInvalidCredential; |
| return false; |
| } |
| |
| // Find a user that correspond to this credential |
| auto credentialIndex = static_cast<unsigned>(credential - pinCredentials.begin()); |
| auto user = std::find_if(mLockUsers.begin(), mLockUsers.end(), [credential, credentialIndex](const LockUserInfo & u) { |
| return std::any_of(u.credentials.begin(), u.credentials.end(), [&credential, credentialIndex](const CredentialStruct & c) { |
| return c.credentialIndex == credentialIndex && c.credentialType == credential->credentialType; |
| }); |
| }); |
| if (user == mLockUsers.end()) |
| { |
| ChipLogDetail(Zcl, |
| "Lock App: specified PIN code was found in the database, but the lock user is not associated with it " |
| "[endpointId=%d,credentialIndex=%u]", |
| mEndpointId, credentialIndex); |
| } |
| |
| auto userIndex = static_cast<uint8_t>(user - mLockUsers.begin()); |
| |
| // Check if schedules affect the user |
| bool haveWeekDaySchedules = false; |
| bool haveYearDaySchedules = false; |
| if (weekDayScheduleForbidsAccess(userIndex, &haveWeekDaySchedules) || |
| yearDayScheduleForbidsAccess(userIndex, &haveYearDaySchedules) || |
| // Also disallow access for a user that's supposed to have _some_ |
| // schedule but doesn't have any |
| (user->userType == UserTypeEnum::kScheduleRestrictedUser && !haveWeekDaySchedules && !haveYearDaySchedules)) |
| { |
| ChipLogDetail(Zcl, |
| "Lock App: associated user is not allowed to operate the lock due to schedules" |
| "[endpointId=%d,userIndex=%u]", |
| mEndpointId, userIndex); |
| err = OperationErrorEnum::kRestricted; |
| return false; |
| } |
| ChipLogProgress( |
| Zcl, |
| "Lock App: specified PIN code was found in the database, setting door lock state to \"%s\" [endpointId=%d,userIndex=%u]", |
| lockStateToString(lockState), mEndpointId, userIndex); |
| |
| if (gCurrentAction.moving == true) |
| { |
| ChipLogProgress(Zcl, |
| "Lock App: not executing lock action as another lock action is already active [endpointId=%d,userIndex=%u]", |
| mEndpointId, userIndex); |
| return false; |
| } |
| |
| gCurrentAction.moving = true; |
| gCurrentAction.endpointId = mEndpointId; |
| gCurrentAction.lockState = lockState; |
| gCurrentAction.opSource = opSource; |
| gCurrentAction.userIndex = MakeNullable(static_cast<uint16_t>(userIndex + 1)); |
| gCurrentAction.credentialIndex = static_cast<uint16_t>(credentialIndex); |
| gCurrentAction.fabricIdx = fabricIdx; |
| gCurrentAction.nodeId = nodeId; |
| |
| // Do this async as a real lock would do too but use 0s delay to speed up CI tests |
| chip::DeviceLayer::SystemLayer().StartTimer(chip::System::Clock::Seconds16(0), OnLockActionCompleteCallback, nullptr); |
| |
| return true; |
| } |
| |
| void LockEndpoint::OnLockActionCompleteCallback(chip::System::Layer *, void * callbackContext) |
| { |
| if (gCurrentAction.userIndex.IsNull()) |
| { |
| DoorLockServer::Instance().SetLockState(gCurrentAction.endpointId, gCurrentAction.lockState, gCurrentAction.opSource, |
| NullNullable, NullNullable, gCurrentAction.fabricIdx, gCurrentAction.nodeId); |
| } |
| else |
| { |
| LockOpCredentials userCredential[] = { { CredentialTypeEnum::kPin, gCurrentAction.credentialIndex } }; |
| auto userCredentials = MakeNullable<List<const LockOpCredentials>>(userCredential); |
| |
| DoorLockServer::Instance().SetLockState(gCurrentAction.endpointId, gCurrentAction.lockState, gCurrentAction.opSource, |
| gCurrentAction.userIndex, userCredentials, gCurrentAction.fabricIdx, |
| gCurrentAction.nodeId); |
| } |
| |
| // move back to Unlocked after Unlatch |
| if (gCurrentAction.lockState == DlLockState::kUnlatched) |
| { |
| gCurrentAction.lockState = DlLockState::kUnlocked; |
| |
| // Do this async as a real lock would do too but use 0s delay to speed up CI tests |
| chip::DeviceLayer::SystemLayer().StartTimer(chip::System::Clock::Seconds16(0), OnLockActionCompleteCallback, nullptr); |
| } |
| else |
| { |
| gCurrentAction.moving = false; |
| } |
| } |
| |
| bool LockEndpoint::weekDayScheduleForbidsAccess(uint16_t userIndex, bool * haveSchedule) const |
| { |
| *haveSchedule = std::any_of(mWeekDaySchedules[userIndex].begin(), mWeekDaySchedules[userIndex].end(), |
| [](const WeekDaysScheduleInfo & s) { return s.status == DlScheduleStatus::kOccupied; }); |
| |
| const auto & user = mLockUsers[userIndex]; |
| if (user.userType != UserTypeEnum::kScheduleRestrictedUser && user.userType != UserTypeEnum::kWeekDayScheduleUser) |
| { |
| // Weekday schedules don't apply to this user. |
| return false; |
| } |
| |
| if (user.userType == UserTypeEnum::kScheduleRestrictedUser && !*haveSchedule) |
| { |
| // It's valid to not have any schedules of a given type; on its own this |
| // does not prevent access. |
| return false; |
| } |
| |
| chip::System::Clock::Milliseconds64 cTMs; |
| auto chipError = chip::System::SystemClock().GetClock_RealTimeMS(cTMs); |
| if (chipError != CHIP_NO_ERROR) |
| { |
| ChipLogError(Zcl, "Lock App: unable to get current time to check user schedules [endpointId=%d,error=%d (%s)]", mEndpointId, |
| chipError.AsInteger(), chipError.AsString()); |
| return true; |
| } |
| time_t unixEpoch = std::chrono::duration_cast<chip::System::Clock::Seconds32>(cTMs).count(); |
| |
| tm calendarTime{}; |
| localtime_r(&unixEpoch, &calendarTime); |
| |
| auto currentTime = |
| calendarTime.tm_hour * chip::kSecondsPerHour + calendarTime.tm_min * chip::kSecondsPerMinute + calendarTime.tm_sec; |
| |
| // Now check whether any schedule allows the current time. If it does, |
| // access is not forbidden. |
| return !std::any_of( |
| mWeekDaySchedules[userIndex].begin(), mWeekDaySchedules[userIndex].end(), |
| [currentTime, calendarTime](const WeekDaysScheduleInfo & s) { |
| auto startTime = s.schedule.startHour * chip::kSecondsPerHour + s.schedule.startMinute * chip::kSecondsPerMinute; |
| auto endTime = s.schedule.endHour * chip::kSecondsPerHour + s.schedule.endMinute * chip::kSecondsPerMinute; |
| return s.status == DlScheduleStatus::kOccupied && (to_underlying(s.schedule.daysMask) & (1 << calendarTime.tm_wday)) && |
| startTime <= currentTime && currentTime <= endTime; |
| }); |
| } |
| |
| bool LockEndpoint::yearDayScheduleForbidsAccess(uint16_t userIndex, bool * haveSchedule) const |
| { |
| *haveSchedule = std::any_of(mYearDaySchedules[userIndex].begin(), mYearDaySchedules[userIndex].end(), |
| [](const YearDayScheduleInfo & sch) { return sch.status == DlScheduleStatus::kOccupied; }); |
| |
| const auto & user = mLockUsers[userIndex]; |
| if (user.userType != UserTypeEnum::kScheduleRestrictedUser && user.userType != UserTypeEnum::kYearDayScheduleUser) |
| { |
| return false; |
| } |
| |
| if (user.userType == UserTypeEnum::kScheduleRestrictedUser && !*haveSchedule) |
| { |
| // It's valid to not have any schedules of a given type; on its own this |
| // does not prevent access. |
| return false; |
| } |
| |
| chip::System::Clock::Milliseconds64 cTMs; |
| auto chipError = chip::System::SystemClock().GetClock_RealTimeMS(cTMs); |
| if (chipError != CHIP_NO_ERROR) |
| { |
| ChipLogError(Zcl, "Lock App: unable to get current time to check user schedules [endpointId=%d,error=%d (%s)]", mEndpointId, |
| chipError.AsInteger(), chipError.AsString()); |
| return true; |
| } |
| auto unixEpoch = std::chrono::duration_cast<chip::System::Clock::Seconds32>(cTMs).count(); |
| uint32_t chipEpoch = 0; |
| if (!chip::UnixEpochToChipEpochTime(unixEpoch, chipEpoch)) |
| { |
| ChipLogError(Zcl, |
| "Lock App: unable to convert Unix Epoch time to Matter Epoch Time to check user schedules " |
| "[endpointId=%d,userIndex=%d]", |
| mEndpointId, userIndex); |
| return false; |
| } |
| |
| return !std::any_of(mYearDaySchedules[userIndex].begin(), mYearDaySchedules[userIndex].end(), |
| [chipEpoch](const YearDayScheduleInfo & sch) { |
| return sch.status == DlScheduleStatus::kOccupied && sch.schedule.localStartTime <= chipEpoch && |
| chipEpoch <= sch.schedule.localEndTime; |
| }); |
| } |
| |
| const char * LockEndpoint::lockStateToString(DlLockState lockState) const |
| { |
| switch (lockState) |
| { |
| case DlLockState::kNotFullyLocked: |
| return "Not Fully Locked"; |
| case DlLockState::kLocked: |
| return "Locked"; |
| case DlLockState::kUnlocked: |
| return "Unlocked"; |
| case DlLockState::kUnlatched: |
| return "Unlatched"; |
| case DlLockState::kUnknownEnumValue: |
| break; |
| } |
| |
| return "Unknown"; |
| } |