| /** |
| * |
| * Copyright (c) 2020 Project CHIP Authors |
| * |
| * 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. |
| */ |
| |
| /**************************************************************************** |
| * @file |
| * @brief Routines for the Door Lock Server plugin. |
| ******************************************************************************* |
| ******************************************************************************/ |
| |
| #include "door-lock-server.h" |
| #include <app-common/zap-generated/attributes/Accessors.h> |
| #include <app-common/zap-generated/callback.h> |
| #include <app-common/zap-generated/ids/Clusters.h> |
| #include <app/EventLogging.h> |
| #include <app/server/Server.h> |
| #include <app/util/af.h> |
| #include <app/util/error-mapping.h> |
| #include <cinttypes> |
| |
| #include <app/CommandHandler.h> |
| #include <app/ConcreteAttributePath.h> |
| #include <app/ConcreteCommandPath.h> |
| #include <lib/support/CodeUtils.h> |
| |
| using namespace chip; |
| using namespace chip::app; |
| using namespace chip::app::DataModel; |
| using namespace chip::app::Clusters::DoorLock; |
| using chip::Protocols::InteractionModel::Status; |
| |
| static constexpr uint8_t DOOR_LOCK_SCHEDULE_MAX_HOUR = 23; |
| static constexpr uint8_t DOOR_LOCK_SCHEDULE_MAX_MINUTE = 59; |
| |
| static constexpr uint32_t DOOR_LOCK_MAX_LOCK_TIMEOUT_SEC = MAX_INT32U_VALUE / MILLISECOND_TICKS_PER_SECOND; |
| |
| DoorLockServer DoorLockServer::instance; |
| |
| class DoorLockClusterFabricDelegate : public chip::FabricTable::Delegate |
| { |
| void OnFabricRemoved(const FabricTable & fabricTable, FabricIndex fabricIndex) override |
| { |
| for (auto endpointId : EnabledEndpointsWithServerCluster(chip::app::Clusters::DoorLock::Id)) |
| { |
| if (!DoorLockServer::Instance().OnFabricRemoved(endpointId, fabricIndex)) |
| { |
| ChipLogError(Zcl, |
| "Unable to handle fabric removal from the Door Lock Server instance [endpointId=%d,fabricIndex=%d]", |
| endpointId, fabricIndex); |
| } |
| } |
| } |
| }; |
| static DoorLockClusterFabricDelegate gFabricDelegate; |
| |
| /********************************************************** |
| * DoorLockServer public methods |
| *********************************************************/ |
| |
| DoorLockServer & DoorLockServer::Instance() |
| { |
| return instance; |
| } |
| |
| /** |
| * @brief Initializes given endpoint for a server. |
| * |
| * @param endpointId |
| */ |
| void DoorLockServer::InitServer(chip::EndpointId endpointId) |
| { |
| emberAfDoorLockClusterPrintln("Door Lock cluster initialized at endpoint #%u", endpointId); |
| |
| auto status = Attributes::LockState::SetNull(endpointId); |
| if (EMBER_ZCL_STATUS_SUCCESS != status) |
| { |
| ChipLogError(Zcl, "[InitDoorLockServer] Unable to set the Lock State attribute to null [status=%d]", status); |
| } |
| SetActuatorEnabled(endpointId, true); |
| |
| for (auto & ep : mEndpointCtx) |
| { |
| ep.lockoutEndTimestamp = ep.lockoutEndTimestamp.zero(); |
| ep.wrongCodeEntryAttempts = 0; |
| } |
| } |
| |
| bool DoorLockServer::SetLockState(chip::EndpointId endpointId, DlLockState newLockState) |
| { |
| return SetAttribute(endpointId, Attributes::LockState::Id, Attributes::LockState::Set, newLockState); |
| } |
| |
| bool DoorLockServer::SetLockState(chip::EndpointId endpointId, DlLockState newLockState, OperationSourceEnum opSource, |
| const Nullable<uint16_t> & userIndex, const Nullable<List<const LockOpCredentials>> & credentials) |
| { |
| bool success = SetLockState(endpointId, newLockState); |
| |
| // Remote operations are handled separately as they use more data unavailable here |
| VerifyOrReturnError(OperationSourceEnum::kRemote != opSource, success); |
| |
| // DlLockState::kNotFullyLocked has no appropriate event to send. Also it is unclear whether |
| // it should schedule auto-relocking. So skip it here. Check for supported states explicitly |
| // to handle possible enum extending in future. |
| VerifyOrReturnError(DlLockState::kLocked == newLockState || DlLockState::kUnlocked == newLockState, success); |
| |
| // Send LockOperation event |
| auto opType = (DlLockState::kLocked == newLockState) ? LockOperationTypeEnum::kLock : LockOperationTypeEnum::kUnlock; |
| |
| SendLockOperationEvent(endpointId, opType, opSource, OperationErrorEnum::kUnspecified, userIndex, Nullable<chip::FabricIndex>(), |
| Nullable<chip::NodeId>(), credentials, success); |
| |
| // Reset wrong entry attempts (in case there were any incorrect credentials presented before) if lock/unlock was a success |
| // and a valid credential was presented. |
| if (success && !credentials.IsNull() && !(credentials.Value().empty())) |
| { |
| ResetWrongCodeEntryAttempts(endpointId); |
| } |
| |
| // Schedule auto-relocking |
| if (success && LockOperationTypeEnum::kUnlock == opType) |
| { |
| // appclusters.pdf 5.3.3.25: |
| // The number of seconds to wait after unlocking a lock before it automatically locks again. 0=disabled. If set, unlock |
| // operations from any source will be timed. For one time unlock with timeout use the specific command. |
| uint32_t autoRelockTime = 0; |
| |
| VerifyOrReturnError(GetAutoRelockTime(endpointId, autoRelockTime), false); |
| VerifyOrReturnError(0 != autoRelockTime, true); |
| ScheduleAutoRelock(endpointId, autoRelockTime); |
| } |
| |
| return success; |
| } |
| |
| bool DoorLockServer::SetActuatorEnabled(chip::EndpointId endpointId, bool newActuatorState) |
| { |
| return SetAttribute(endpointId, Attributes::ActuatorEnabled::Id, Attributes::ActuatorEnabled::Set, newActuatorState); |
| } |
| |
| bool DoorLockServer::SetDoorState(chip::EndpointId endpointId, DoorStateEnum newDoorState) |
| { |
| bool success = SetAttribute(endpointId, Attributes::DoorState::Id, Attributes::DoorState::Set, newDoorState); |
| |
| if (success) |
| { |
| Events::DoorStateChange::Type event{ newDoorState }; |
| SendEvent(endpointId, event); |
| } |
| |
| return success; |
| } |
| |
| bool DoorLockServer::SetLanguage(chip::EndpointId endpointId, chip::CharSpan newLanguage) |
| { |
| return SetAttribute(endpointId, Attributes::Language::Id, Attributes::Language::Set, newLanguage); |
| } |
| |
| bool DoorLockServer::SetAutoRelockTime(chip::EndpointId endpointId, uint32_t newAutoRelockTimeSec) |
| { |
| return SetAttribute(endpointId, Attributes::AutoRelockTime::Id, Attributes::AutoRelockTime::Set, newAutoRelockTimeSec); |
| } |
| |
| bool DoorLockServer::SetSoundVolume(chip::EndpointId endpointId, uint8_t newSoundVolume) |
| { |
| return SetAttribute(endpointId, Attributes::SoundVolume::Id, Attributes::SoundVolume::Set, newSoundVolume); |
| } |
| |
| bool DoorLockServer::SetOneTouchLocking(chip::EndpointId endpointId, bool isEnabled) |
| { |
| return SetAttribute(endpointId, Attributes::EnableOneTouchLocking::Id, Attributes::EnableOneTouchLocking::Set, isEnabled); |
| } |
| |
| bool DoorLockServer::SetPrivacyModeButton(chip::EndpointId endpointId, bool isEnabled) |
| { |
| return SetAttribute(endpointId, Attributes::EnablePrivacyModeButton::Id, Attributes::EnablePrivacyModeButton::Set, isEnabled); |
| } |
| |
| bool DoorLockServer::HandleWrongCodeEntry(chip::EndpointId endpointId) |
| { |
| auto endpointContext = getContext(endpointId); |
| if (nullptr == endpointContext) |
| { |
| ChipLogError(Zcl, "Failed to get endpoint index for cluster [endpoint=%d]", endpointId); |
| return false; |
| } |
| |
| uint8_t wrongCodeEntryLimit = 0xFF; |
| auto status = Attributes::WrongCodeEntryLimit::Get(endpointId, &wrongCodeEntryLimit); |
| if (EMBER_ZCL_STATUS_SUCCESS == status) |
| { |
| if (++endpointContext->wrongCodeEntryAttempts >= wrongCodeEntryLimit) |
| { |
| emberAfDoorLockClusterPrintln("Too many wrong code entry attempts, engaging lockout [endpoint=%d,wrongCodeAttempts=%d]", |
| endpointId, endpointContext->wrongCodeEntryAttempts); |
| engageLockout(endpointId); |
| } |
| } |
| else if (EMBER_ZCL_STATUS_UNSUPPORTED_ATTRIBUTE != status) |
| { |
| ChipLogError(Zcl, "Failed to read Wrong Code Entry Limit attribute, status=0x%x", to_underlying(status)); |
| return false; |
| } |
| return true; |
| } |
| |
| void DoorLockServer::ResetWrongCodeEntryAttempts(chip::EndpointId endpointId) |
| { |
| auto endpointContext = getContext(endpointId); |
| if (nullptr == endpointContext) |
| { |
| ChipLogError(Zcl, "Failed to reset wrong code entry attempts. No context for endpoint %d", endpointId); |
| return; |
| } |
| endpointContext->wrongCodeEntryAttempts = 0; |
| } |
| |
| bool DoorLockServer::engageLockout(chip::EndpointId endpointId) |
| { |
| uint8_t lockoutTimeout; |
| |
| auto endpointContext = getContext(endpointId); |
| if (nullptr == endpointContext) |
| { |
| ChipLogError(Zcl, "Failed to get endpoint index for cluster [endpoint=%d]", endpointId); |
| return false; |
| } |
| |
| auto status = Attributes::UserCodeTemporaryDisableTime::Get(endpointId, &lockoutTimeout); |
| if (EMBER_ZCL_STATUS_UNSUPPORTED_ATTRIBUTE == status) |
| { |
| return false; |
| } |
| if (EMBER_ZCL_STATUS_SUCCESS != status) |
| { |
| ChipLogError(Zcl, "Unable to read the UserCodeTemporaryDisableTime attribute [status=%d]", to_underlying(status)); |
| return false; |
| } |
| |
| endpointContext->wrongCodeEntryAttempts = 0; |
| endpointContext->lockoutEndTimestamp = |
| chip::System::SystemClock().GetMonotonicTimestamp() + chip::System::Clock::Seconds32(lockoutTimeout); |
| |
| emberAfDoorLockClusterPrintln("Lockout engaged [endpointId=%d,lockoutTimeout=%d]", endpointId, lockoutTimeout); |
| |
| SendLockAlarmEvent(endpointId, AlarmCodeEnum::kWrongCodeEntryLimit); |
| |
| emberAfPluginDoorLockLockoutStarted(endpointId, endpointContext->lockoutEndTimestamp); |
| |
| return true; |
| } |
| |
| bool DoorLockServer::GetAutoRelockTime(chip::EndpointId endpointId, uint32_t & autoRelockTime) |
| { |
| return GetAttribute(endpointId, Attributes::AutoRelockTime::Id, Attributes::AutoRelockTime::Get, autoRelockTime); |
| } |
| |
| bool DoorLockServer::GetNumberOfUserSupported(chip::EndpointId endpointId, uint16_t & numberOfUsersSupported) |
| { |
| return GetAttribute(endpointId, Attributes::NumberOfTotalUsersSupported::Id, Attributes::NumberOfTotalUsersSupported::Get, |
| numberOfUsersSupported); |
| } |
| |
| bool DoorLockServer::GetNumberOfPINCredentialsSupported(chip::EndpointId endpointId, uint16_t & numberOfPINCredentials) |
| { |
| return GetAttribute(endpointId, Attributes::NumberOfPINUsersSupported::Id, Attributes::NumberOfPINUsersSupported::Get, |
| numberOfPINCredentials); |
| } |
| |
| bool DoorLockServer::GetNumberOfRFIDCredentialsSupported(chip::EndpointId endpointId, uint16_t & numberOfRFIDCredentials) |
| { |
| return GetAttribute(endpointId, Attributes::NumberOfRFIDUsersSupported::Id, Attributes::NumberOfRFIDUsersSupported::Get, |
| numberOfRFIDCredentials); |
| } |
| |
| bool DoorLockServer::GetNumberOfWeekDaySchedulesPerUserSupported(chip::EndpointId endpointId, |
| uint8_t & numberOfWeekDaySchedulesPerUser) |
| { |
| return GetAttribute(endpointId, Attributes::NumberOfWeekDaySchedulesSupportedPerUser::Id, |
| Attributes::NumberOfWeekDaySchedulesSupportedPerUser::Get, numberOfWeekDaySchedulesPerUser); |
| } |
| |
| bool DoorLockServer::GetNumberOfYearDaySchedulesPerUserSupported(chip::EndpointId endpointId, |
| uint8_t & numberOfYearDaySchedulesPerUser) |
| { |
| return GetAttribute(endpointId, Attributes::NumberOfYearDaySchedulesSupportedPerUser::Id, |
| Attributes::NumberOfYearDaySchedulesSupportedPerUser::Get, numberOfYearDaySchedulesPerUser); |
| } |
| |
| bool DoorLockServer::GetNumberOfCredentialsSupportedPerUser(chip::EndpointId endpointId, |
| uint8_t & numberOfCredentialsSupportedPerUser) |
| { |
| return GetAttribute(endpointId, Attributes::NumberOfCredentialsSupportedPerUser::Id, |
| Attributes::NumberOfCredentialsSupportedPerUser::Get, numberOfCredentialsSupportedPerUser); |
| } |
| |
| bool DoorLockServer::GetNumberOfHolidaySchedulesSupported(chip::EndpointId endpointId, uint8_t & numberOfHolidaySchedules) |
| { |
| return GetAttribute(endpointId, Attributes::NumberOfHolidaySchedulesSupported::Id, |
| Attributes::NumberOfHolidaySchedulesSupported::Get, numberOfHolidaySchedules); |
| } |
| |
| bool DoorLockServer::SendLockAlarmEvent(chip::EndpointId endpointId, AlarmCodeEnum alarmCode) |
| { |
| Events::DoorLockAlarm::Type event{ alarmCode }; |
| SendEvent(endpointId, event); |
| |
| return true; |
| } |
| |
| namespace { |
| // Check whether this is valid UserStatus for a SetUser or SetCredential |
| // command. |
| bool IsValidUserStatusForSet(const Nullable<UserStatusEnum> & userStatus) |
| { |
| return userStatus.IsNull() || (userStatus.Value() == UserStatusEnum::kOccupiedEnabled) || |
| (userStatus.Value() == UserStatusEnum::kOccupiedDisabled); |
| } |
| } // anonymous namespace |
| |
| void DoorLockServer::setUserCommandHandler(chip::app::CommandHandler * commandObj, |
| const chip::app::ConcreteCommandPath & commandPath, |
| const chip::app::Clusters::DoorLock::Commands::SetUser::DecodableType & commandData) |
| { |
| auto & userIndex = commandData.userIndex; |
| emberAfDoorLockClusterPrintln("[SetUser] Incoming command [endpointId=%d,userIndex=%d]", commandPath.mEndpointId, userIndex); |
| |
| if (!SupportsUSR(commandPath.mEndpointId)) |
| { |
| emberAfDoorLockClusterPrintln("[SetUser] User management is not supported [endpointId=%d]", commandPath.mEndpointId); |
| sendClusterResponse(commandObj, commandPath, EMBER_ZCL_STATUS_UNSUPPORTED_COMMAND); |
| return; |
| } |
| |
| auto fabricIdx = getFabricIndex(commandObj); |
| if (chip::kUndefinedFabricIndex == fabricIdx) |
| { |
| ChipLogError(Zcl, "[SetUser] Unable to get the fabric IDX [endpointId=%d,userIndex=%d]", commandPath.mEndpointId, |
| userIndex); |
| sendClusterResponse(commandObj, commandPath, EMBER_ZCL_STATUS_FAILURE); |
| return; |
| } |
| |
| auto sourceNodeId = getNodeId(commandObj); |
| if (chip::kUndefinedNodeId == sourceNodeId) |
| { |
| ChipLogError(Zcl, "[SetUser] Unable to get the source node index [endpointId=%d,userIndex=%d]", commandPath.mEndpointId, |
| userIndex); |
| sendClusterResponse(commandObj, commandPath, EMBER_ZCL_STATUS_FAILURE); |
| return; |
| } |
| |
| auto & operationType = commandData.operationType; |
| auto & userName = commandData.userName; |
| auto & userUniqueId = commandData.userUniqueID; |
| auto & userStatus = commandData.userStatus; |
| auto & userType = commandData.userType; |
| auto & credentialRule = commandData.credentialRule; |
| |
| if (!userIndexValid(commandPath.mEndpointId, userIndex)) |
| { |
| emberAfDoorLockClusterPrintln("[SetUser] User index out of bounds [endpointId=%d,userIndex=%d]", commandPath.mEndpointId, |
| userIndex); |
| sendClusterResponse(commandObj, commandPath, EMBER_ZCL_STATUS_INVALID_COMMAND); |
| return; |
| } |
| |
| // appclusters, 5.2.4.34: UserName has maximum DOOR_LOCK_MAX_USER_NAME_SIZE (10) characters excluding NUL terminator in it. |
| if (!userName.IsNull() && userName.Value().size() > DOOR_LOCK_MAX_USER_NAME_SIZE) |
| { |
| emberAfDoorLockClusterPrintln( |
| "[SetUser] Unable to set user: userName too long [endpointId=%d,userIndex=%d,userNameSize=%u]", commandPath.mEndpointId, |
| userIndex, static_cast<unsigned int>(userName.Value().size())); |
| |
| sendClusterResponse(commandObj, commandPath, EMBER_ZCL_STATUS_INVALID_COMMAND); |
| return; |
| } |
| |
| if (!IsValidUserStatusForSet(userStatus)) |
| { |
| emberAfDoorLockClusterPrintln( |
| "[SetUser] Unable to set the user: user status is out of range [endpointId=%d,userIndex=%d,userStatus=%u]", |
| commandPath.mEndpointId, userIndex, to_underlying(userStatus.Value())); |
| |
| sendClusterResponse(commandObj, commandPath, EMBER_ZCL_STATUS_INVALID_COMMAND); |
| return; |
| } |
| |
| if (userType == UserTypeEnum::kUnknownEnumValue) |
| { |
| emberAfDoorLockClusterPrintln( |
| "[SetUser] Unable to set the user: user type is unknown [endpointId=%d,userIndex=%d,userType=%u]", |
| commandPath.mEndpointId, userIndex, to_underlying(userType.Value())); |
| |
| sendClusterResponse(commandObj, commandPath, EMBER_ZCL_STATUS_INVALID_COMMAND); |
| return; |
| } |
| |
| EmberAfStatus status = EMBER_ZCL_STATUS_SUCCESS; |
| switch (operationType) |
| { |
| case DataOperationTypeEnum::kAdd: |
| status = createUser(commandPath.mEndpointId, fabricIdx, sourceNodeId, userIndex, userName, userUniqueId, userStatus, |
| userType, credentialRule); |
| break; |
| case DataOperationTypeEnum::kModify: |
| status = modifyUser(commandPath.mEndpointId, fabricIdx, sourceNodeId, userIndex, userName, userUniqueId, userStatus, |
| userType, credentialRule); |
| break; |
| case DataOperationTypeEnum::kClear: |
| default: |
| // appclusters, 5.2.4.34: SetUser command allow only kAdd/kModify, we should respond with INVALID_COMMAND if we got kClear |
| // or anything else |
| status = EMBER_ZCL_STATUS_INVALID_COMMAND; |
| emberAfDoorLockClusterPrintln("[SetUser] Invalid operation type [endpointId=%d,operationType=%u]", commandPath.mEndpointId, |
| to_underlying(operationType)); |
| break; |
| } |
| |
| sendClusterResponse(commandObj, commandPath, status); |
| } |
| |
| void DoorLockServer::getUserCommandHandler(chip::app::CommandHandler * commandObj, |
| const chip::app::ConcreteCommandPath & commandPath, uint16_t userIndex) |
| { |
| emberAfDoorLockClusterPrintln("[GetUser] Incoming command [endpointId=%d,userIndex=%d]", commandPath.mEndpointId, userIndex); |
| |
| if (!SupportsUSR(commandPath.mEndpointId)) |
| { |
| emberAfDoorLockClusterPrintln("[GetUser] User management is not supported [endpointId=%d]", commandPath.mEndpointId); |
| commandObj->AddStatus(commandPath, Status::UnsupportedCommand); |
| return; |
| } |
| |
| uint16_t maxNumberOfUsers = 0; |
| if (!userIndexValid(commandPath.mEndpointId, userIndex, maxNumberOfUsers)) |
| { |
| emberAfDoorLockClusterPrintln("[GetUser] User index out of bounds [userIndex=%d,numberOfTotalUsersSupported=%d]", userIndex, |
| maxNumberOfUsers); |
| commandObj->AddStatus(commandPath, Status::InvalidCommand); |
| return; |
| } |
| |
| Commands::GetUserResponse::Type response; |
| |
| // appclusters, 5.2.4.36.1: We need to add next occupied user after userIndex if any. |
| // |
| // We want to do this before we call emberAfPluginDoorLockGetUser, because this will |
| // make its own emberAfPluginDoorLockGetUser calls, and a |
| // EmberAfPluginDoorLockUserInfo might be pointing into some application-static |
| // buffers (for its credentials and whatnot). |
| uint16_t nextAvailableUserIndex = 0; |
| if (findOccupiedUserSlot(commandPath.mEndpointId, static_cast<uint16_t>(userIndex + 1), nextAvailableUserIndex)) |
| { |
| response.nextUserIndex.SetNonNull(nextAvailableUserIndex); |
| } |
| |
| EmberAfPluginDoorLockUserInfo user; |
| if (!emberAfPluginDoorLockGetUser(commandPath.mEndpointId, userIndex, user)) |
| { |
| emberAfDoorLockClusterPrintln("[GetUser] Could not get user info [userIndex=%d]", userIndex); |
| commandObj->AddStatus(commandPath, Status::Failure); |
| return; |
| } |
| |
| response.userIndex = userIndex; |
| |
| // appclusters, 5.2.4.36: we should not set user-specific fields to non-null if the user status is set to Available |
| if (UserStatusEnum::kAvailable != user.userStatus) |
| { |
| emberAfDoorLockClusterPrintln("Found user in storage: " |
| "[userIndex=%d,userName=\"%.*s\",userStatus=%u,userType=%u" |
| ",credentialRule=%u,createdBy=%u,modifiedBy=%u]", |
| userIndex, static_cast<int>(user.userName.size()), user.userName.data(), |
| to_underlying(user.userStatus), to_underlying(user.userType), |
| to_underlying(user.credentialRule), user.createdBy, user.lastModifiedBy); |
| |
| response.userName.SetNonNull(user.userName); |
| if (0xFFFFFFFFU != user.userUniqueId) |
| { |
| response.userUniqueID.SetNonNull(user.userUniqueId); |
| } |
| response.userStatus.SetNonNull(user.userStatus); |
| response.userType.SetNonNull(user.userType); |
| response.credentialRule.SetNonNull(user.credentialRule); |
| response.credentials.SetNonNull(user.credentials); |
| // Set fabric indices only if the user was created/modified by matter. |
| if (user.creationSource == DlAssetSource::kMatterIM) |
| { |
| response.creatorFabricIndex.SetNonNull(user.createdBy); |
| } |
| if (user.modificationSource == DlAssetSource::kMatterIM) |
| { |
| response.lastModifiedFabricIndex.SetNonNull(user.lastModifiedBy); |
| } |
| } |
| else |
| { |
| emberAfDoorLockClusterPrintln("[GetUser] User not found [userIndex=%d]", userIndex); |
| } |
| |
| commandObj->AddResponse(commandPath, response); |
| } |
| |
| void DoorLockServer::clearUserCommandHandler(chip::app::CommandHandler * commandObj, |
| const chip::app::ConcreteCommandPath & commandPath, uint16_t userIndex) |
| { |
| emberAfDoorLockClusterPrintln("[ClearUser] Incoming command [endpointId=%d,userIndex=%d]", commandPath.mEndpointId, userIndex); |
| |
| if (!SupportsUSR(commandPath.mEndpointId)) |
| { |
| emberAfDoorLockClusterPrintln("[ClearUser] User management is not supported [endpointId=%d]", commandPath.mEndpointId); |
| commandObj->AddStatus(commandPath, Status::UnsupportedCommand); |
| return; |
| } |
| |
| auto fabricIdx = getFabricIndex(commandObj); |
| if (chip::kUndefinedFabricIndex == fabricIdx) |
| { |
| ChipLogError(Zcl, "[ClearUser] Unable to get the fabric IDX [endpointId=%d,userIndex=%d]", commandPath.mEndpointId, |
| userIndex); |
| commandObj->AddStatus(commandPath, Status::Failure); |
| return; |
| } |
| |
| auto sourceNodeId = getNodeId(commandObj); |
| if (chip::kUndefinedNodeId == sourceNodeId) |
| { |
| ChipLogError(Zcl, "[ClearUser] Unable to get the source node index [endpointId=%d,userIndex=%d]", commandPath.mEndpointId, |
| userIndex); |
| commandObj->AddStatus(commandPath, Status::Failure); |
| return; |
| } |
| |
| uint16_t maxNumberOfUsers = 0; |
| if (!userIndexValid(commandPath.mEndpointId, userIndex, maxNumberOfUsers) && userIndex != 0xFFFE) |
| { |
| emberAfDoorLockClusterPrintln("[ClearUser] User index out of bounds [userIndex=%d,numberOfTotalUsersSupported=%d]", |
| userIndex, maxNumberOfUsers); |
| commandObj->AddStatus(commandPath, Status::InvalidCommand); |
| return; |
| } |
| |
| if (0xFFFE != userIndex) |
| { |
| auto status = clearUser(commandPath.mEndpointId, fabricIdx, sourceNodeId, userIndex, true); |
| if (Status::Success != status) |
| { |
| ChipLogError(Zcl, "[ClearUser] App reported failure when resetting the user [userIndex=%d,status=0x%x]", userIndex, |
| to_underlying(status)); |
| } |
| commandObj->AddStatus(commandPath, status); |
| return; |
| } |
| |
| emberAfDoorLockClusterPrintln("[ClearUser] Removing all users from storage"); |
| for (uint16_t i = 1; i <= maxNumberOfUsers; ++i) |
| { |
| auto status = clearUser(commandPath.mEndpointId, fabricIdx, sourceNodeId, i, false); |
| if (Status::Success != status) |
| { |
| ChipLogError(Zcl, "[ClearUser] App reported failure when resetting the user [userIndex=%d,status=0x%x]", i, |
| to_underlying(status)); |
| |
| commandObj->AddStatus(commandPath, Status::Failure); |
| return; |
| } |
| } |
| emberAfDoorLockClusterPrintln("[ClearUser] Removed all users from storage [users=%d]", maxNumberOfUsers); |
| |
| sendRemoteLockUserChange(commandPath.mEndpointId, LockDataTypeEnum::kUserIndex, DataOperationTypeEnum::kClear, sourceNodeId, |
| fabricIdx, 0xFFFE, 0xFFFE); |
| |
| commandObj->AddStatus(commandPath, Status::Success); |
| } |
| |
| void DoorLockServer::setCredentialCommandHandler( |
| chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, |
| const chip::app::Clusters::DoorLock::Commands::SetCredential::DecodableType & commandData) |
| { |
| emberAfDoorLockClusterPrintln("[SetCredential] Incoming command [endpointId=%d]", commandPath.mEndpointId); |
| |
| auto fabricIdx = getFabricIndex(commandObj); |
| if (kUndefinedFabricIndex == fabricIdx) |
| { |
| ChipLogError(Zcl, "[SetCredential] Unable to get the fabric IDX [endpointId=%d]", commandPath.mEndpointId); |
| commandObj->AddStatus(commandPath, Status::Failure); |
| return; |
| } |
| |
| auto sourceNodeId = getNodeId(commandObj); |
| if (chip::kUndefinedNodeId == sourceNodeId) |
| { |
| ChipLogError(Zcl, "[SetCredential] Unable to get the source node index [endpointId=%d]", commandPath.mEndpointId); |
| commandObj->AddStatus(commandPath, Status::Failure); |
| return; |
| } |
| |
| auto & operationType = commandData.operationType; |
| auto & credentialType = commandData.credential.credentialType; |
| auto & credentialIndex = commandData.credential.credentialIndex; |
| auto & credentialData = commandData.credentialData; |
| auto & userIndex = commandData.userIndex; |
| auto & userStatus = commandData.userStatus; |
| auto & userType = commandData.userType; |
| |
| if (!credentialTypeSupported(commandPath.mEndpointId, credentialType)) |
| { |
| emberAfDoorLockClusterPrintln("[SetCredential] Credential type is not supported [endpointId=%d,credentialType=%u]", |
| commandPath.mEndpointId, to_underlying(credentialType)); |
| commandObj->AddStatus(commandPath, Status::UnsupportedCommand); |
| return; |
| } |
| |
| // appclusters, 5.2.4.41: response should contain next available credential slot |
| uint16_t nextAvailableCredentialSlot = 0; |
| findUnoccupiedCredentialSlot(commandPath.mEndpointId, credentialType, static_cast<uint16_t>(credentialIndex + 1), |
| nextAvailableCredentialSlot); |
| |
| uint16_t maxNumberOfCredentials = 0; |
| if (!credentialIndexValid(commandPath.mEndpointId, credentialType, credentialIndex, maxNumberOfCredentials)) |
| { |
| emberAfDoorLockClusterPrintln("[SetCredential] Credential index is out of range [endpointId=%d,credentialType=%u" |
| ",credentialIndex=%d]", |
| commandPath.mEndpointId, to_underlying(credentialType), credentialIndex); |
| sendSetCredentialResponse(commandObj, commandPath, DlStatus::kInvalidField, 0, nextAvailableCredentialSlot); |
| return; |
| } |
| |
| // appclusters, 5.2.4.40.3: If the credential data length is out of bounds we should return INVALID_COMMAND |
| auto status = credentialLengthWithinRange(commandPath.mEndpointId, credentialType, credentialData); |
| if (DlStatus::kSuccess != status) |
| { |
| sendSetCredentialResponse(commandObj, commandPath, status, 0, nextAvailableCredentialSlot); |
| return; |
| } |
| |
| // appclusters, 5.2.4.41.1: we should return DUPLICATE in the response if we're trying to create duplicated credential entry |
| for (uint16_t i = 1; CredentialTypeEnum::kProgrammingPIN != credentialType && (i <= maxNumberOfCredentials); ++i) |
| { |
| EmberAfPluginDoorLockCredentialInfo currentCredential; |
| if (!emberAfPluginDoorLockGetCredential(commandPath.mEndpointId, i, credentialType, currentCredential)) |
| { |
| emberAfDoorLockClusterPrintln("[SetCredential] Unable to get the credential to exclude duplicated entry " |
| "[endpointId=%d,credentialType=%u,credentialIndex=%d]", |
| commandPath.mEndpointId, to_underlying(credentialType), i); |
| sendSetCredentialResponse(commandObj, commandPath, DlStatus::kFailure, 0, nextAvailableCredentialSlot); |
| return; |
| } |
| if (DlCredentialStatus::kAvailable != currentCredential.status && currentCredential.credentialType == credentialType && |
| currentCredential.credentialData.data_equal(credentialData)) |
| { |
| emberAfDoorLockClusterPrintln( |
| "[SetCredential] Credential with the same data and type already exist " |
| "[endpointId=%d,credentialType=%u,dataLength=%u,existingCredentialIndex=%d,credentialIndex=%d]", |
| commandPath.mEndpointId, to_underlying(credentialType), static_cast<unsigned int>(credentialData.size()), i, |
| credentialIndex); |
| sendSetCredentialResponse(commandObj, commandPath, DlStatus::kDuplicate, 0, nextAvailableCredentialSlot); |
| return; |
| } |
| } |
| |
| EmberAfPluginDoorLockCredentialInfo existingCredential; |
| if (!emberAfPluginDoorLockGetCredential(commandPath.mEndpointId, credentialIndex, credentialType, existingCredential)) |
| { |
| emberAfDoorLockClusterPrintln( |
| "[SetCredential] Unable to check if credential exists: app error [endpointId=%d,credentialIndex=%d]", |
| commandPath.mEndpointId, credentialIndex); |
| |
| sendSetCredentialResponse(commandObj, commandPath, DlStatus::kFailure, 0, nextAvailableCredentialSlot); |
| return; |
| } |
| |
| if (!IsValidUserStatusForSet(userStatus)) |
| { |
| emberAfDoorLockClusterPrintln("[SetCredential] Unable to set the credential: user status is out of range " |
| "[endpointId=%d,credentialIndex=%d,userStatus=%u]", |
| commandPath.mEndpointId, credentialIndex, to_underlying(userStatus.Value())); |
| sendSetCredentialResponse(commandObj, commandPath, DlStatus::kInvalidField, 0, nextAvailableCredentialSlot); |
| return; |
| } |
| |
| if (userType == UserTypeEnum::kUnknownEnumValue) |
| { |
| emberAfDoorLockClusterPrintln("[SetCredential] Unable to set the credential: user type is unknown " |
| "[endpointId=%d,credentialIndex=%d,userType=%u]", |
| commandPath.mEndpointId, credentialIndex, to_underlying(userType.Value())); |
| sendSetCredentialResponse(commandObj, commandPath, DlStatus::kInvalidField, 0, nextAvailableCredentialSlot); |
| return; |
| } |
| |
| switch (operationType) |
| { |
| case DataOperationTypeEnum::kAdd: { |
| uint16_t createdUserIndex = 0; |
| |
| status = createCredential(commandPath.mEndpointId, fabricIdx, sourceNodeId, credentialIndex, credentialType, |
| existingCredential, credentialData, userIndex, userStatus, userType, createdUserIndex); |
| |
| sendSetCredentialResponse(commandObj, commandPath, status, createdUserIndex, nextAvailableCredentialSlot); |
| return; |
| } |
| case DataOperationTypeEnum::kModify: { |
| // appclusters, 5.2.4.41.1: should send the INVALID_COMMAND in the response when the credential is in use |
| if (DlCredentialStatus::kAvailable == existingCredential.status) |
| { |
| emberAfDoorLockClusterPrintln("[SetCredential] Unable to modify the credential: credential slot is not occupied " |
| "[endpointId=%d,credentialIndex=%d]", |
| commandPath.mEndpointId, credentialIndex); |
| |
| sendSetCredentialResponse(commandObj, commandPath, DlStatus::kInvalidField, 0, nextAvailableCredentialSlot); |
| return; |
| } |
| |
| // if userIndex is NULL then we're changing the programming user PIN |
| if (userIndex.IsNull()) |
| { |
| if (!userStatus.IsNull() || userType != UserTypeEnum::kProgrammingUser) |
| { |
| emberAfDoorLockClusterPrintln("[SetCredential] Unable to modify programming PIN: invalid argument " |
| "[endpointId=%d,credentialIndex=%d]", |
| commandPath.mEndpointId, credentialIndex); |
| } |
| status = modifyProgrammingPIN(commandPath.mEndpointId, fabricIdx, sourceNodeId, credentialIndex, credentialType, |
| existingCredential, credentialData); |
| sendSetCredentialResponse(commandObj, commandPath, status, 0, nextAvailableCredentialSlot); |
| return; |
| } |
| |
| status = modifyCredential(commandPath.mEndpointId, fabricIdx, sourceNodeId, credentialIndex, credentialType, |
| existingCredential, credentialData, userIndex.Value(), userStatus, userType); |
| sendSetCredentialResponse(commandObj, commandPath, status, 0, nextAvailableCredentialSlot); |
| return; |
| } |
| case DataOperationTypeEnum::kClear: |
| default: |
| // appclusters, 5.2.4.40: set credential command supports only Add and Modify operational type. |
| sendSetCredentialResponse(commandObj, commandPath, DlStatus::kInvalidField, 0, nextAvailableCredentialSlot); |
| } |
| } |
| |
| void DoorLockServer::getCredentialStatusCommandHandler(chip::app::CommandHandler * commandObj, |
| const chip::app::ConcreteCommandPath & commandPath, |
| CredentialTypeEnum credentialType, uint16_t credentialIndex) |
| { |
| emberAfDoorLockClusterPrintln("[GetCredentialStatus] Incoming command [endpointId=%d]", commandPath.mEndpointId); |
| |
| if (!credentialTypeSupported(commandPath.mEndpointId, credentialType)) |
| { |
| emberAfDoorLockClusterPrintln("[GetCredentialStatus] Credential type is not supported [endpointId=%d,credentialType=%u" |
| "]", |
| commandPath.mEndpointId, to_underlying(credentialType)); |
| commandObj->AddStatus(commandPath, Status::UnsupportedCommand); |
| return; |
| } |
| |
| uint16_t maxNumberOfCredentials = 0; |
| if (!credentialIndexValid(commandPath.mEndpointId, credentialType, credentialIndex, maxNumberOfCredentials)) |
| { |
| sendGetCredentialResponse(commandObj, commandPath, credentialType, credentialIndex, 0, nullptr, false); |
| return; |
| } |
| |
| EmberAfPluginDoorLockCredentialInfo credentialInfo; |
| if (!emberAfPluginDoorLockGetCredential(commandPath.mEndpointId, credentialIndex, credentialType, credentialInfo)) |
| { |
| emberAfDoorLockClusterPrintln("[GetCredentialStatus] Unable to get the credential: app error " |
| "[endpointId=%d,credentialIndex=%d,credentialType=%u,creator=%u,modifier=%u]", |
| commandPath.mEndpointId, credentialIndex, to_underlying(credentialType), |
| credentialInfo.createdBy, credentialInfo.lastModifiedBy); |
| commandObj->AddStatus(commandPath, Status::Failure); |
| return; |
| } |
| |
| bool credentialExists = DlCredentialStatus::kAvailable != credentialInfo.status; |
| uint16_t userIndexWithCredential = 0; |
| if (credentialExists) |
| { |
| if (!findUserIndexByCredential(commandPath.mEndpointId, credentialType, credentialIndex, userIndexWithCredential)) |
| { |
| // That means that there's some kind of error in our database -- there is an unassociated credential. I'm not sure how |
| // to handle that properly other than panic in the log. |
| ChipLogError(Zcl, |
| "[GetCredentialStatus] Database possibly corrupted - credential exists without user assigned " |
| "[endpointId=%d,credentialType=%u,credentialIndex=%d]", |
| commandPath.mEndpointId, to_underlying(credentialType), credentialIndex); |
| commandObj->AddStatus(commandPath, Status::Failure); |
| return; |
| } |
| } |
| |
| sendGetCredentialResponse(commandObj, commandPath, credentialType, credentialIndex, userIndexWithCredential, &credentialInfo, |
| credentialExists); |
| } |
| |
| void DoorLockServer::sendGetCredentialResponse(chip::app::CommandHandler * commandObj, |
| const chip::app::ConcreteCommandPath & commandPath, |
| CredentialTypeEnum credentialType, uint16_t credentialIndex, |
| uint16_t userIndexWithCredential, |
| EmberAfPluginDoorLockCredentialInfo * credentialInfo, bool credentialExists) |
| { |
| Commands::GetCredentialStatusResponse::Type response{ .credentialExists = credentialExists }; |
| if (credentialExists && !(nullptr == credentialInfo)) |
| { |
| if (0 != userIndexWithCredential) |
| { |
| response.userIndex.SetNonNull(userIndexWithCredential); |
| } |
| if (credentialInfo->creationSource == DlAssetSource::kMatterIM) |
| { |
| response.creatorFabricIndex.SetNonNull(credentialInfo->createdBy); |
| } |
| if (credentialInfo->modificationSource == DlAssetSource::kMatterIM) |
| { |
| response.lastModifiedFabricIndex.SetNonNull(credentialInfo->lastModifiedBy); |
| } |
| } |
| else |
| { |
| response.userIndex.SetNull(); |
| } |
| uint16_t nextCredentialIndex = 0; |
| if (findOccupiedCredentialSlot(commandPath.mEndpointId, credentialType, static_cast<uint16_t>(credentialIndex + 1), |
| nextCredentialIndex)) |
| { |
| response.nextCredentialIndex.SetNonNull(nextCredentialIndex); |
| } |
| commandObj->AddResponse(commandPath, response); |
| |
| emberAfDoorLockClusterPrintln("[GetCredentialStatus] Prepared credential status " |
| "[endpointId=%d,credentialType=%u,credentialIndex=%d,userIndex=%d,nextCredentialIndex=%d]", |
| commandPath.mEndpointId, to_underlying(credentialType), credentialIndex, userIndexWithCredential, |
| nextCredentialIndex); |
| } |
| |
| void DoorLockServer::clearCredentialCommandHandler( |
| chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, |
| const chip::app::Clusters::DoorLock::Commands::ClearCredential::DecodableType & commandData) |
| { |
| emberAfDoorLockClusterPrintln("[ClearCredential] Incoming command [endpointId=%d]", commandPath.mEndpointId); |
| |
| auto modifier = getFabricIndex(commandObj); |
| if (kUndefinedFabricIndex == modifier) |
| { |
| commandObj->AddStatus(commandPath, Status::Failure); |
| return; |
| } |
| |
| auto sourceNodeId = getNodeId(commandObj); |
| if (chip::kUndefinedNodeId == sourceNodeId) |
| { |
| commandObj->AddStatus(commandPath, Status::Failure); |
| return; |
| } |
| |
| const auto & credential = commandData.credential; |
| if (credential.IsNull()) |
| { |
| emberAfDoorLockClusterPrintln("[ClearCredential] Clearing all credentials [endpointId=%d]", commandPath.mEndpointId); |
| commandObj->AddStatus(commandPath, clearCredentials(commandPath.mEndpointId, modifier, sourceNodeId)); |
| return; |
| } |
| |
| // Remove all the credentials of the particular type. |
| auto credentialType = credential.Value().credentialType; |
| auto credentialIndex = credential.Value().credentialIndex; |
| if (0xFFFE == credentialIndex) |
| { |
| commandObj->AddStatus(commandPath, clearCredentials(commandPath.mEndpointId, modifier, sourceNodeId, credentialType)); |
| return; |
| } |
| |
| commandObj->AddStatus(commandPath, |
| clearCredential(commandPath.mEndpointId, modifier, sourceNodeId, credentialType, credentialIndex, false)); |
| } |
| |
| void DoorLockServer::setWeekDayScheduleCommandHandler(chip::app::CommandHandler * commandObj, |
| const chip::app::ConcreteCommandPath & commandPath, uint8_t weekDayIndex, |
| uint16_t userIndex, const chip::BitMask<DaysMaskMap> & daysMask, |
| uint8_t startHour, uint8_t startMinute, uint8_t endHour, uint8_t endMinute) |
| { |
| auto endpointId = commandPath.mEndpointId; |
| if (!SupportsWeekDaySchedules(endpointId)) |
| { |
| emberAfDoorLockClusterPrintln("[SetWeekDaySchedule] Ignore command (not supported) [endpointId=%d]", endpointId); |
| commandObj->AddStatus(commandPath, Status::InvalidCommand); |
| return; |
| } |
| |
| emberAfDoorLockClusterPrintln("[SetWeekDaySchedule] Incoming command [endpointId=%d]", endpointId); |
| |
| auto fabricIdx = getFabricIndex(commandObj); |
| if (kUndefinedFabricIndex == fabricIdx) |
| { |
| ChipLogError(Zcl, "[SetWeekDaySchedule] Unable to get the fabric IDX [endpointId=%d]", commandPath.mEndpointId); |
| commandObj->AddStatus(commandPath, Status::Failure); |
| return; |
| } |
| |
| auto sourceNodeId = getNodeId(commandObj); |
| if (chip::kUndefinedNodeId == sourceNodeId) |
| { |
| ChipLogError(Zcl, "[SetWeekDaySchedule] Unable to get the source node index [endpointId=%d]", commandPath.mEndpointId); |
| commandObj->AddStatus(commandPath, Status::Failure); |
| return; |
| } |
| |
| if (!weekDayIndexValid(endpointId, weekDayIndex) || !userIndexValid(endpointId, userIndex)) |
| { |
| emberAfDoorLockClusterPrintln( |
| "[SetWeekDaySchedule] Unable to add schedule - index out of range [endpointId=%d,weekDayIndex=%d,userIndex=%d]", |
| endpointId, weekDayIndex, userIndex); |
| commandObj->AddStatus(commandPath, Status::InvalidCommand); |
| return; |
| } |
| |
| if (!userExists(endpointId, userIndex)) |
| { |
| emberAfDoorLockClusterPrintln("[SetWeekDaySchedule] Unable to add schedule - user does not exist " |
| "[endpointId=%d,weekDayIndex=%d,userIndex=%d]", |
| endpointId, weekDayIndex, userIndex); |
| commandObj->AddStatus(commandPath, Status::Failure); |
| return; |
| } |
| |
| // appclusters, 5.2.4.14 - spec does not allow setting the schedule for multiple days in the bitmask |
| int setBitsInDaysMask = 0; |
| uint8_t rawDaysMask = daysMask.Raw(); |
| for (size_t i = 0; i < sizeof(rawDaysMask) * 8; ++i) |
| { |
| setBitsInDaysMask += rawDaysMask & 0x1; |
| rawDaysMask = static_cast<uint8_t>(rawDaysMask >> 1); |
| } |
| |
| // TODO: Check that bits are within range |
| if (setBitsInDaysMask == 0 || setBitsInDaysMask > 1) |
| { |
| emberAfDoorLockClusterPrintln("[SetWeekDaySchedule] Unable to add schedule - daysMask is out of range " |
| "[endpointId=%d,weekDayIndex=%d,userIndex=%d,daysMask=%x]", |
| endpointId, weekDayIndex, userIndex, daysMask.Raw()); |
| commandObj->AddStatus(commandPath, Status::InvalidCommand); |
| return; |
| } |
| |
| if (startHour > DOOR_LOCK_SCHEDULE_MAX_HOUR || startMinute > DOOR_LOCK_SCHEDULE_MAX_MINUTE || |
| endHour > DOOR_LOCK_SCHEDULE_MAX_HOUR || endMinute > DOOR_LOCK_SCHEDULE_MAX_MINUTE) |
| { |
| emberAfDoorLockClusterPrintln("[SetWeekDaySchedule] Unable to add schedule - start time out of range " |
| "[endpointId=%d,weekDayIndex=%d,userIndex=%d,startTime=\"%d:%d\",endTime=\"%d:%d\"]", |
| endpointId, weekDayIndex, userIndex, startHour, startMinute, endHour, endMinute); |
| commandObj->AddStatus(commandPath, Status::InvalidCommand); |
| return; |
| } |
| |
| if (startHour > endHour || (startHour == endHour && startMinute >= endMinute)) |
| { |
| emberAfDoorLockClusterPrintln("[SetWeekDaySchedule] Unable to add schedule - invalid time " |
| "[endpointId=%d,weekDayIndex=%d,userIndex=%d,startTime=\"%d:%d\",endTime=\"%d:%d\"]", |
| endpointId, weekDayIndex, userIndex, startHour, startMinute, endHour, endMinute); |
| commandObj->AddStatus(commandPath, Status::InvalidCommand); |
| return; |
| } |
| |
| auto status = emberAfPluginDoorLockSetSchedule(endpointId, weekDayIndex, userIndex, DlScheduleStatus::kOccupied, daysMask, |
| startHour, startMinute, endHour, endMinute); |
| if (DlStatus::kSuccess != status) |
| { |
| ChipLogError(Zcl, |
| "[SetWeekDaySchedule] Unable to add schedule - internal error " |
| "[endpointId=%d,weekDayIndex=%d,userIndex=%d,status=%u]", |
| endpointId, weekDayIndex, userIndex, to_underlying(status)); |
| commandObj->AddStatus(commandPath, Status::Failure); |
| return; |
| } |
| |
| emberAfDoorLockClusterPrintln("[SetWeekDaySchedule] Successfully created new schedule " |
| "[endpointId=%d,weekDayIndex=%d,userIndex=%d,daysMask=%d,startTime=\"%d:%d\",endTime=\"%d:%d\"]", |
| endpointId, weekDayIndex, userIndex, daysMask.Raw(), startHour, startMinute, endHour, endMinute); |
| |
| sendRemoteLockUserChange(endpointId, LockDataTypeEnum::kWeekDaySchedule, DataOperationTypeEnum::kAdd, sourceNodeId, fabricIdx, |
| userIndex, static_cast<uint16_t>(weekDayIndex)); |
| |
| commandObj->AddStatus(commandPath, Status::Success); |
| } |
| |
| void DoorLockServer::getWeekDayScheduleCommandHandler(chip::app::CommandHandler * commandObj, |
| const chip::app::ConcreteCommandPath & commandPath, uint8_t weekDayIndex, |
| uint16_t userIndex) |
| { |
| auto endpointId = commandPath.mEndpointId; |
| if (!SupportsWeekDaySchedules(endpointId)) |
| { |
| emberAfDoorLockClusterPrintln("[GetWeekDaySchedule] Ignore command (not supported) [endpointId=%d]", endpointId); |
| commandObj->AddStatus(commandPath, Status::InvalidCommand); |
| return; |
| } |
| |
| emberAfDoorLockClusterPrintln("[GetWeekDaySchedule] Incoming command [endpointId=%d]", endpointId); |
| |
| if (!weekDayIndexValid(endpointId, weekDayIndex) || !userIndexValid(endpointId, userIndex)) |
| { |
| emberAfDoorLockClusterPrintln( |
| "[GetWeekDaySchedule] Unable to get schedule - index out of range [endpointId=%d,weekDayIndex=%d,userIndex=%d]", |
| endpointId, weekDayIndex, userIndex); |
| sendGetWeekDayScheduleResponse(commandObj, commandPath, weekDayIndex, userIndex, DlStatus::kInvalidField); |
| return; |
| } |
| |
| if (!userExists(endpointId, userIndex)) |
| { |
| emberAfDoorLockClusterPrintln("[GetWeekDaySchedule] User does not exist [endpointId=%d,weekDayIndex=%d,userIndex=%d]", |
| endpointId, weekDayIndex, userIndex); |
| sendGetWeekDayScheduleResponse(commandObj, commandPath, weekDayIndex, userIndex, DlStatus::kFailure); |
| return; |
| } |
| |
| EmberAfPluginDoorLockWeekDaySchedule scheduleInfo{}; |
| auto status = emberAfPluginDoorLockGetSchedule(endpointId, weekDayIndex, userIndex, scheduleInfo); |
| if (DlStatus::kSuccess != status) |
| { |
| sendGetWeekDayScheduleResponse(commandObj, commandPath, weekDayIndex, userIndex, status); |
| return; |
| } |
| |
| sendGetWeekDayScheduleResponse(commandObj, commandPath, weekDayIndex, userIndex, DlStatus::kSuccess, scheduleInfo.daysMask, |
| scheduleInfo.startHour, scheduleInfo.startMinute, scheduleInfo.endHour, scheduleInfo.endMinute); |
| } |
| |
| void DoorLockServer::clearWeekDayScheduleCommandHandler(chip::app::CommandHandler * commandObj, |
| const chip::app::ConcreteCommandPath & commandPath, uint8_t weekDayIndex, |
| uint16_t userIndex) |
| { |
| auto endpointId = commandPath.mEndpointId; |
| if (!SupportsWeekDaySchedules(endpointId)) |
| { |
| emberAfDoorLockClusterPrintln("[ClearWeekDaySchedule] Ignore command (not supported) [endpointId=%d]", endpointId); |
| commandObj->AddStatus(commandPath, Status::InvalidCommand); |
| return; |
| } |
| |
| emberAfDoorLockClusterPrintln("[ClearWeekDaySchedule] Incoming command [endpointId=%d]", endpointId); |
| |
| auto fabricIdx = getFabricIndex(commandObj); |
| if (kUndefinedFabricIndex == fabricIdx) |
| { |
| ChipLogError(Zcl, "[ClearWeekDaySchedule] Unable to get the fabric IDX [endpointId=%d]", commandPath.mEndpointId); |
| commandObj->AddStatus(commandPath, Status::Failure); |
| return; |
| } |
| |
| auto sourceNodeId = getNodeId(commandObj); |
| if (chip::kUndefinedNodeId == sourceNodeId) |
| { |
| ChipLogError(Zcl, "[ClearWeekDaySchedule] Unable to get the source node index [endpointId=%d]", commandPath.mEndpointId); |
| commandObj->AddStatus(commandPath, Status::Failure); |
| return; |
| } |
| |
| if (!userIndexValid(endpointId, userIndex) || (!weekDayIndexValid(endpointId, weekDayIndex) && 0xFE != weekDayIndex)) |
| { |
| emberAfDoorLockClusterPrintln( |
| "[ClearWeekDaySchedule] User or WeekDay index is out of range [endpointId=%d,weekDayIndex=%d,userIndex=%d]", endpointId, |
| weekDayIndex, userIndex); |
| commandObj->AddStatus(commandPath, Status::InvalidCommand); |
| return; |
| } |
| |
| if (!userExists(endpointId, userIndex)) |
| { |
| emberAfDoorLockClusterPrintln("[ClearWeekDaySchedule] User does not exist [endpointId=%d,weekDayIndex=%d,userIndex=%d]", |
| endpointId, weekDayIndex, userIndex); |
| commandObj->AddStatus(commandPath, Status::Failure); |
| return; |
| } |
| |
| DlStatus clearStatus = DlStatus::kSuccess; |
| if (0xFE == weekDayIndex) |
| { |
| emberAfDoorLockClusterPrintln( |
| "[ClearWeekDaySchedule] Clearing all schedules for a single user [endpointId=%d,userIndex=%d]", endpointId, userIndex); |
| clearStatus = clearWeekDaySchedules(endpointId, userIndex); |
| } |
| else |
| { |
| emberAfDoorLockClusterPrintln( |
| "[ClearWeekDaySchedule] Clearing a single schedule [endpointId=%d,weekDayIndex=%d,userIndex=%d]", endpointId, |
| weekDayIndex, userIndex); |
| clearStatus = clearWeekDaySchedule(endpointId, userIndex, weekDayIndex); |
| } |
| |
| if (DlStatus::kSuccess != clearStatus) |
| { |
| emberAfDoorLockClusterPrintln( |
| "[ClearWeekDaySchedule] Unable to clear the user schedules - app error [endpointId=%d,userIndex=%d,status=%u]", |
| endpointId, userIndex, to_underlying(clearStatus)); |
| commandObj->AddStatus(commandPath, Status::Failure); |
| return; |
| } |
| |
| sendRemoteLockUserChange(endpointId, LockDataTypeEnum::kWeekDaySchedule, DataOperationTypeEnum::kClear, sourceNodeId, fabricIdx, |
| userIndex, static_cast<uint16_t>(weekDayIndex)); |
| |
| commandObj->AddStatus(commandPath, Status::Success); |
| } |
| |
| void DoorLockServer::setYearDayScheduleCommandHandler(chip::app::CommandHandler * commandObj, |
| const chip::app::ConcreteCommandPath & commandPath, uint8_t yearDayIndex, |
| uint16_t userIndex, uint32_t localStartTime, uint32_t localEndTime) |
| { |
| auto endpointId = commandPath.mEndpointId; |
| if (!SupportsYearDaySchedules(endpointId)) |
| { |
| emberAfDoorLockClusterPrintln("[SetYearDaySchedule] Ignore command (not supported) [endpointId=%d]", endpointId); |
| commandObj->AddStatus(commandPath, Status::InvalidCommand); |
| return; |
| } |
| |
| emberAfDoorLockClusterPrintln("[SetYearDaySchedule] incoming command [endpointId=%d]", endpointId); |
| |
| auto fabricIdx = getFabricIndex(commandObj); |
| if (kUndefinedFabricIndex == fabricIdx) |
| { |
| ChipLogError(Zcl, "[SetYearDaySchedule] Unable to get the fabric IDX [endpointId=%d]", commandPath.mEndpointId); |
| commandObj->AddStatus(commandPath, Status::Failure); |
| return; |
| } |
| |
| auto sourceNodeId = getNodeId(commandObj); |
| if (chip::kUndefinedNodeId == sourceNodeId) |
| { |
| ChipLogError(Zcl, "[SetYearDaySchedule] Unable to get the source node index [endpointId=%d]", commandPath.mEndpointId); |
| commandObj->AddStatus(commandPath, Status::Failure); |
| return; |
| } |
| |
| if (!yearDayIndexValid(endpointId, yearDayIndex) || !userIndexValid(endpointId, userIndex)) |
| { |
| emberAfDoorLockClusterPrintln( |
| "[SetYearDaySchedule] Unable to add schedule - index out of range [endpointId=%d,yearDayIndex=%d,userIndex=%d]", |
| endpointId, yearDayIndex, userIndex); |
| commandObj->AddStatus(commandPath, Status::InvalidCommand); |
| return; |
| } |
| |
| if (!userExists(endpointId, userIndex)) |
| { |
| emberAfDoorLockClusterPrintln("[SetYearDaySchedule] Unable to add schedule - user does not exist " |
| "[endpointId=%d,yearDayIndex=%d,userIndex=%d]", |
| endpointId, yearDayIndex, userIndex); |
| commandObj->AddStatus(commandPath, Status::Failure); |
| return; |
| } |
| |
| if (localEndTime <= localStartTime) |
| { |
| emberAfDoorLockClusterPrintln("[SetYearDaySchedule] Unable to add schedule - schedule ends earlier than starts" |
| "[endpointId=%d,yearDayIndex=%d,userIndex=%d,localStarTime=%" PRIu32 ",localEndTime=%" PRIu32 |
| "]", |
| endpointId, yearDayIndex, userIndex, localStartTime, localEndTime); |
| commandObj->AddStatus(commandPath, Status::InvalidCommand); |
| return; |
| } |
| |
| auto status = emberAfPluginDoorLockSetSchedule(endpointId, yearDayIndex, userIndex, DlScheduleStatus::kOccupied, localStartTime, |
| localEndTime); |
| if (DlStatus::kSuccess != status) |
| { |
| ChipLogError(Zcl, |
| "[SetYearDaySchedule] Unable to add schedule - internal error " |
| "[endpointId=%d,yearDayIndex=%d,userIndex=%d,status=%u]", |
| endpointId, yearDayIndex, userIndex, to_underlying(status)); |
| commandObj->AddStatus(commandPath, Status::Failure); |
| return; |
| } |
| |
| emberAfDoorLockClusterPrintln("[SetYearDaySchedule] Successfully created new schedule " |
| "[endpointId=%d,yearDayIndex=%d,userIndex=%d,localStartTime=%" PRIu32 ",endTime=%" PRIu32 "]", |
| endpointId, yearDayIndex, userIndex, localStartTime, localEndTime); |
| |
| sendRemoteLockUserChange(endpointId, LockDataTypeEnum::kYearDaySchedule, DataOperationTypeEnum::kAdd, sourceNodeId, fabricIdx, |
| userIndex, static_cast<uint16_t>(yearDayIndex)); |
| |
| commandObj->AddStatus(commandPath, Status::Success); |
| } |
| |
| void DoorLockServer::getYearDayScheduleCommandHandler(chip::app::CommandHandler * commandObj, |
| const chip::app::ConcreteCommandPath & commandPath, uint8_t yearDayIndex, |
| uint16_t userIndex) |
| { |
| auto endpointId = commandPath.mEndpointId; |
| if (!SupportsYearDaySchedules(endpointId)) |
| { |
| emberAfDoorLockClusterPrintln("[GetYearDaySchedule] Ignore command (not supported) [endpointId=%d]", endpointId); |
| commandObj->AddStatus(commandPath, Status::InvalidCommand); |
| return; |
| } |
| emberAfDoorLockClusterPrintln("[GetYearDaySchedule] incoming command [endpointId=%d]", endpointId); |
| |
| if (!yearDayIndexValid(endpointId, yearDayIndex) || !userIndexValid(endpointId, userIndex)) |
| { |
| emberAfDoorLockClusterPrintln( |
| "[GetYearDaySchedule] Unable to get schedule - index out of range [endpointId=%d,yearDayIndex=%d,userIndex=%d]", |
| endpointId, yearDayIndex, userIndex); |
| sendGetYearDayScheduleResponse(commandObj, commandPath, yearDayIndex, userIndex, DlStatus::kInvalidField); |
| return; |
| } |
| |
| if (!userExists(endpointId, userIndex)) |
| { |
| emberAfDoorLockClusterPrintln("[GetYearDaySchedule] User does not exist [endpointId=%d,yearDayIndex=%d,userIndex=%d]", |
| endpointId, yearDayIndex, userIndex); |
| sendGetYearDayScheduleResponse(commandObj, commandPath, yearDayIndex, userIndex, DlStatus::kFailure); |
| return; |
| } |
| |
| EmberAfPluginDoorLockYearDaySchedule scheduleInfo{}; |
| auto status = emberAfPluginDoorLockGetSchedule(endpointId, yearDayIndex, userIndex, scheduleInfo); |
| if (DlStatus::kSuccess != status) |
| { |
| sendGetYearDayScheduleResponse(commandObj, commandPath, yearDayIndex, userIndex, status); |
| return; |
| } |
| |
| sendGetYearDayScheduleResponse(commandObj, commandPath, yearDayIndex, userIndex, DlStatus::kSuccess, |
| scheduleInfo.localStartTime, scheduleInfo.localEndTime); |
| } |
| |
| void DoorLockServer::clearYearDayScheduleCommandHandler(chip::app::CommandHandler * commandObj, |
| const chip::app::ConcreteCommandPath & commandPath, uint8_t yearDayIndex, |
| uint16_t userIndex) |
| { |
| auto endpointId = commandPath.mEndpointId; |
| if (!SupportsYearDaySchedules(endpointId)) |
| { |
| emberAfDoorLockClusterPrintln("[ClearYearDaySchedule] Ignore command (not supported) [endpointId=%d]", endpointId); |
| commandObj->AddStatus(commandPath, Status::InvalidCommand); |
| return; |
| } |
| emberAfDoorLockClusterPrintln("[ClearYearDaySchedule] incoming command [endpointId=%d]", endpointId); |
| |
| auto fabricIdx = getFabricIndex(commandObj); |
| if (kUndefinedFabricIndex == fabricIdx) |
| { |
| ChipLogError(Zcl, "[ClearYearDaySchedule] Unable to get the fabric IDX [endpointId=%d]", commandPath.mEndpointId); |
| commandObj->AddStatus(commandPath, Status::Failure); |
| return; |
| } |
| |
| auto sourceNodeId = getNodeId(commandObj); |
| if (chip::kUndefinedNodeId == sourceNodeId) |
| { |
| ChipLogError(Zcl, "[ClearYearDaySchedule] Unable to get the source node index [endpointId=%d]", commandPath.mEndpointId); |
| commandObj->AddStatus(commandPath, Status::Failure); |
| return; |
| } |
| |
| if (!userIndexValid(endpointId, userIndex) || (!yearDayIndexValid(endpointId, yearDayIndex) && 0xFE != yearDayIndex)) |
| { |
| emberAfDoorLockClusterPrintln( |
| "[ClearYearDaySchedule] User or YearDay index is out of range [endpointId=%d,yearDayIndex=%d,userIndex=%d]", endpointId, |
| yearDayIndex, userIndex); |
| commandObj->AddStatus(commandPath, Status::InvalidCommand); |
| return; |
| } |
| |
| if (!userExists(endpointId, userIndex)) |
| { |
| emberAfDoorLockClusterPrintln("[ClearYearDaySchedule] User does not exist [endpointId=%d,yearDayIndex=%d,userIndex=%d]", |
| endpointId, yearDayIndex, userIndex); |
| commandObj->AddStatus(commandPath, Status::Failure); |
| return; |
| } |
| |
| DlStatus clearStatus = DlStatus::kSuccess; |
| if (0xFE == yearDayIndex) |
| { |
| emberAfDoorLockClusterPrintln( |
| "[ClearYearDaySchedule] Clearing all schedules for a single user [endpointId=%d,userIndex=%d]", endpointId, userIndex); |
| clearStatus = clearYearDaySchedules(endpointId, userIndex); |
| } |
| else |
| { |
| emberAfDoorLockClusterPrintln( |
| "[ClearYearDaySchedule] Clearing a single schedule [endpointId=%d,yearDayIndex=%d,userIndex=%d]", endpointId, |
| yearDayIndex, userIndex); |
| clearStatus = clearYearDaySchedule(endpointId, userIndex, yearDayIndex); |
| } |
| |
| if (DlStatus::kSuccess != clearStatus) |
| { |
| emberAfDoorLockClusterPrintln( |
| "[ClearYearDaySchedule] Unable to clear the user schedules - app error [endpointId=%d,userIndex=%d,status=%u]", |
| endpointId, userIndex, to_underlying(clearStatus)); |
| commandObj->AddStatus(commandPath, Status::Failure); |
| return; |
| } |
| |
| sendRemoteLockUserChange(endpointId, LockDataTypeEnum::kYearDaySchedule, DataOperationTypeEnum::kClear, sourceNodeId, fabricIdx, |
| userIndex, static_cast<uint16_t>(yearDayIndex)); |
| |
| commandObj->AddStatus(commandPath, Status::Success); |
| } |
| |
| chip::BitFlags<DoorLockFeature> DoorLockServer::GetFeatures(chip::EndpointId endpointId) |
| { |
| chip::BitFlags<DoorLockFeature> featureMap; |
| if (!GetAttribute(endpointId, Attributes::FeatureMap::Id, Attributes::FeatureMap::Get, *featureMap.RawStorage())) |
| { |
| ChipLogError(Zcl, "Unable to get the door lock feature map: attribute read error"); |
| featureMap.ClearAll(); |
| } |
| return featureMap; |
| } |
| |
| bool DoorLockServer::OnFabricRemoved(chip::EndpointId endpointId, chip::FabricIndex fabricIndex) |
| { |
| emberAfDoorLockClusterPrintln( |
| "[OnFabricRemoved] Handling a fabric removal from the door lock server [endpointId=%d,fabricIndex=%d]", endpointId, |
| fabricIndex); |
| |
| // Iterate over all the users and clean up the deleted fabric |
| if (!clearFabricFromUsers(endpointId, fabricIndex)) |
| { |
| ChipLogError(Zcl, "[OnFabricRemoved] Unable to cleanup fabric from users - internal error [endpointId=%d,fabricIndex=%d]", |
| endpointId, fabricIndex); |
| return false; |
| } |
| |
| // Iterate over all the credentials and clean up the fabrics |
| if (!clearFabricFromCredentials(endpointId, fabricIndex)) |
| { |
| ChipLogError(Zcl, |
| "[OnFabricRemoved] Unable to cleanup fabric from credentials - internal error [endpointId=%d,fabricIndex=%d]", |
| endpointId, fabricIndex); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| /********************************************************** |
| * DoorLockServer private methods |
| *********************************************************/ |
| |
| chip::FabricIndex DoorLockServer::getFabricIndex(const chip::app::CommandHandler * commandObj) |
| { |
| if (nullptr == commandObj || nullptr == commandObj->GetExchangeContext()) |
| { |
| ChipLogError(Zcl, "Cannot access ExchangeContext of Command Object for Fabric Index"); |
| return kUndefinedFabricIndex; |
| } |
| |
| return commandObj->GetAccessingFabricIndex(); |
| } |
| |
| chip::NodeId DoorLockServer::getNodeId(const chip::app::CommandHandler * commandObj) |
| { |
| if (nullptr == commandObj || nullptr == commandObj->GetExchangeContext()) |
| { |
| ChipLogError(Zcl, "Cannot access ExchangeContext of Command Object for Node ID"); |
| return kUndefinedNodeId; |
| } |
| |
| auto secureSession = commandObj->GetExchangeContext()->GetSessionHandle()->AsSecureSession(); |
| if (nullptr == secureSession) |
| { |
| ChipLogError(Zcl, "Cannot access Secure session handle of Command Object for Node ID"); |
| } |
| return secureSession->GetPeerNodeId(); |
| } |
| |
| bool DoorLockServer::userIndexValid(chip::EndpointId endpointId, uint16_t userIndex) |
| { |
| uint16_t maxNumberOfUsers; |
| return userIndexValid(endpointId, userIndex, maxNumberOfUsers); |
| } |
| |
| bool DoorLockServer::userIndexValid(chip::EndpointId endpointId, uint16_t userIndex, uint16_t & maxNumberOfUser) |
| { |
| if (!GetNumberOfUserSupported(endpointId, maxNumberOfUser)) |
| { |
| return false; |
| } |
| |
| // appclusters, 5.2.4.34-37: user index changes from 1 to maxNumberOfUsers |
| if (0 == userIndex || userIndex > maxNumberOfUser) |
| { |
| return false; |
| } |
| return true; |
| } |
| |
| bool DoorLockServer::userExists(chip::EndpointId endpointId, uint16_t userIndex) |
| { |
| // Check if user actually exist |
| EmberAfPluginDoorLockUserInfo user; |
| if (!emberAfPluginDoorLockGetUser(endpointId, userIndex, user)) |
| { |
| ChipLogError(Zcl, "[UserExists] Unable to get the user - internal error [endpointId=%d,userIndex=%d]", endpointId, |
| userIndex); |
| return false; |
| } |
| return UserStatusEnum::kAvailable != user.userStatus; |
| } |
| |
| bool DoorLockServer::credentialIndexValid(chip::EndpointId endpointId, CredentialTypeEnum type, uint16_t credentialIndex) |
| { |
| uint16_t maxCredentials = 0; |
| return credentialIndexValid(endpointId, type, credentialIndex, maxCredentials); |
| } |
| |
| bool DoorLockServer::credentialIndexValid(chip::EndpointId endpointId, CredentialTypeEnum type, uint16_t credentialIndex, |
| uint16_t & maxNumberOfCredentials) |
| { |
| if (!getMaxNumberOfCredentials(endpointId, type, maxNumberOfCredentials)) |
| { |
| return false; |
| } |
| |
| // appclusters, 5.2.6.3.1: 0 is allowed index for Programming PIN credential only |
| if (CredentialTypeEnum::kProgrammingPIN == type) |
| { |
| return (0 == credentialIndex); |
| } |
| |
| if (0 == credentialIndex || credentialIndex > maxNumberOfCredentials) |
| { |
| return false; |
| } |
| |
| return true; |
| } |
| |
| DlStatus DoorLockServer::credentialLengthWithinRange(chip::EndpointId endpointId, CredentialTypeEnum type, |
| const chip::ByteSpan & credentialData) |
| { |
| bool statusMin = true, statusMax = true; |
| uint8_t minLen, maxLen; |
| |
| switch (type) |
| { |
| case CredentialTypeEnum::kProgrammingPIN: |
| case CredentialTypeEnum::kPin: |
| statusMin = GetAttribute(endpointId, Attributes::MinPINCodeLength::Id, Attributes::MinPINCodeLength::Get, minLen); |
| statusMax = GetAttribute(endpointId, Attributes::MaxPINCodeLength::Id, Attributes::MaxPINCodeLength::Get, maxLen); |
| break; |
| case CredentialTypeEnum::kRfid: |
| statusMin = GetAttribute(endpointId, Attributes::MinRFIDCodeLength::Id, Attributes::MinRFIDCodeLength::Get, minLen); |
| statusMax = GetAttribute(endpointId, Attributes::MaxRFIDCodeLength::Id, Attributes::MaxRFIDCodeLength::Get, maxLen); |
| break; |
| case CredentialTypeEnum::kFingerprint: |
| statusMin = statusMax = emberAfPluginDoorLockGetFingerprintCredentialLengthConstraints(endpointId, minLen, maxLen); |
| break; |
| case CredentialTypeEnum::kFingerVein: |
| statusMin = statusMax = emberAfPluginDoorLockGetFingerVeinCredentialLengthConstraints(endpointId, minLen, maxLen); |
| break; |
| case CredentialTypeEnum::kFace: |
| statusMin = statusMax = emberAfPluginDoorLockGetFaceCredentialLengthConstraints(endpointId, minLen, maxLen); |
| break; |
| default: |
| return DlStatus::kFailure; |
| } |
| |
| if (!statusMin || !statusMax) |
| { |
| ChipLogError(Zcl, "Unable to read attributes to get min/max length for credentials [endpointId=%d,credentialType=%u]", |
| endpointId, to_underlying(type)); |
| return DlStatus::kFailure; |
| } |
| |
| if (credentialData.size() < minLen || credentialData.size() > maxLen) |
| { |
| emberAfDoorLockClusterPrintln("Credential data size is out of range " |
| "[endpointId=%d,credentialType=%u,minLength=%u,maxLength=%u,length=%u]", |
| endpointId, to_underlying(type), minLen, maxLen, |
| static_cast<unsigned int>(credentialData.size())); |
| return DlStatus::kInvalidField; |
| } |
| |
| return DlStatus::kSuccess; |
| } |
| |
| bool DoorLockServer::getMaxNumberOfCredentials(chip::EndpointId endpointId, CredentialTypeEnum credentialType, |
| uint16_t & maxNumberOfCredentials) |
| { |
| maxNumberOfCredentials = 0; |
| bool status = false; |
| switch (credentialType) |
| { |
| case CredentialTypeEnum::kProgrammingPIN: |
| maxNumberOfCredentials = 1; |
| return true; |
| case CredentialTypeEnum::kPin: |
| status = GetNumberOfPINCredentialsSupported(endpointId, maxNumberOfCredentials); |
| break; |
| case CredentialTypeEnum::kRfid: |
| status = GetNumberOfRFIDCredentialsSupported(endpointId, maxNumberOfCredentials); |
| break; |
| case CredentialTypeEnum::kFingerprint: |
| status = emberAfPluginDoorLockGetNumberOfFingerprintCredentialsSupported(endpointId, maxNumberOfCredentials); |
| break; |
| case CredentialTypeEnum::kFingerVein: |
| status = emberAfPluginDoorLockGetNumberOfFingerVeinCredentialsSupported(endpointId, maxNumberOfCredentials); |
| break; |
| case CredentialTypeEnum::kFace: |
| status = emberAfPluginDoorLockGetNumberOfFaceCredentialsSupported(endpointId, maxNumberOfCredentials); |
| break; |
| default: |
| return false; |
| } |
| |
| return status; |
| } |
| |
| bool DoorLockServer::findOccupiedUserSlot(chip::EndpointId endpointId, uint16_t startIndex, uint16_t & userIndex) |
| { |
| uint16_t maxNumberOfUsers; |
| VerifyOrReturnError(GetAttribute(endpointId, Attributes::NumberOfTotalUsersSupported::Id, |
| Attributes::NumberOfTotalUsersSupported::Get, maxNumberOfUsers), |
| false); |
| |
| userIndex = 0; |
| for (uint16_t i = startIndex; i <= maxNumberOfUsers; ++i) |
| { |
| EmberAfPluginDoorLockUserInfo user; |
| if (!emberAfPluginDoorLockGetUser(endpointId, i, user)) |
| { |
| ChipLogError(Zcl, "Unable to get user to check if slot is occupied: app error [userIndex=%d]", i); |
| return false; |
| } |
| |
| if (UserStatusEnum::kAvailable != user.userStatus) |
| { |
| userIndex = i; |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool DoorLockServer::findUnoccupiedUserSlot(chip::EndpointId endpointId, uint16_t & userIndex) |
| { |
| return findUnoccupiedUserSlot(endpointId, 1, userIndex); |
| } |
| |
| bool DoorLockServer::findUnoccupiedUserSlot(chip::EndpointId endpointId, uint16_t startIndex, uint16_t & userIndex) |
| { |
| uint16_t maxNumberOfUsers; |
| VerifyOrReturnError(GetAttribute(endpointId, Attributes::NumberOfTotalUsersSupported::Id, |
| Attributes::NumberOfTotalUsersSupported::Get, maxNumberOfUsers), |
| false); |
| |
| userIndex = 0; |
| for (uint16_t i = startIndex; i <= maxNumberOfUsers; ++i) |
| { |
| EmberAfPluginDoorLockUserInfo user; |
| if (!emberAfPluginDoorLockGetUser(endpointId, i, user)) |
| { |
| ChipLogError(Zcl, "Unable to get user to check if slot is occupied: app error [userIndex=%d]", i); |
| return false; |
| } |
| |
| if (UserStatusEnum::kAvailable == user.userStatus) |
| { |
| userIndex = i; |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool DoorLockServer::findOccupiedCredentialSlot(chip::EndpointId endpointId, CredentialTypeEnum credentialType, uint16_t startIndex, |
| uint16_t & credentialIndex) |
| { |
| uint16_t maxNumberOfCredentials = 0; |
| if (!getMaxNumberOfCredentials(endpointId, credentialType, maxNumberOfCredentials)) |
| { |
| return false; |
| } |
| |
| // Programming PIN index starts with 0, and it is assumed that it is unique. Therefore different bounds checking for that |
| // credential type |
| if (CredentialTypeEnum::kProgrammingPIN == credentialType) |
| { |
| maxNumberOfCredentials--; |
| } |
| |
| for (uint16_t i = startIndex; i <= maxNumberOfCredentials; ++i) |
| { |
| EmberAfPluginDoorLockCredentialInfo info; |
| if (!emberAfPluginDoorLockGetCredential(endpointId, i, credentialType, info)) |
| { |
| ChipLogError(Zcl, "Unable to get credential: app error [endpointId=%d,credentialType=%u,credentialIndex=%d]", |
| endpointId, to_underlying(credentialType), i); |
| return false; |
| } |
| |
| if (DlCredentialStatus::kAvailable != info.status) |
| { |
| credentialIndex = i; |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| bool DoorLockServer::findUnoccupiedCredentialSlot(chip::EndpointId endpointId, CredentialTypeEnum credentialType, |
| uint16_t startIndex, uint16_t & credentialIndex) |
| { |
| uint16_t maxNumberOfCredentials = 0; |
| if (!getMaxNumberOfCredentials(endpointId, credentialType, maxNumberOfCredentials)) |
| { |
| return false; |
| } |
| |
| // Programming PIN index starts with 0, and it is assumed that it is unique. Therefor different bounds checking for that |
| // credential type |
| if (CredentialTypeEnum::kProgrammingPIN == credentialType) |
| { |
| maxNumberOfCredentials--; |
| } |
| |
| for (uint16_t i = startIndex; i <= maxNumberOfCredentials; ++i) |
| { |
| EmberAfPluginDoorLockCredentialInfo info; |
| if (!emberAfPluginDoorLockGetCredential(endpointId, i, credentialType, info)) |
| { |
| ChipLogError(Zcl, "Unable to get credential: app error [endpointId=%d,credentialType=%u,credentialIndex=%d]", |
| endpointId, to_underlying(credentialType), i); |
| return false; |
| } |
| |
| if (DlCredentialStatus::kAvailable == info.status) |
| { |
| credentialIndex = i; |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| bool DoorLockServer::findUserIndexByCredential(chip::EndpointId endpointId, CredentialTypeEnum credentialType, |
| uint16_t credentialIndex, uint16_t & userIndex) |
| { |
| uint16_t maxNumberOfUsers = 0; |
| VerifyOrReturnError(GetAttribute(endpointId, Attributes::NumberOfTotalUsersSupported::Id, |
| Attributes::NumberOfTotalUsersSupported::Get, maxNumberOfUsers), |
| false); |
| |
| for (uint16_t i = 1; i <= maxNumberOfUsers; ++i) |
| { |
| EmberAfPluginDoorLockUserInfo user; |
| if (!emberAfPluginDoorLockGetUser(endpointId, i, user)) |
| { |
| ChipLogError(Zcl, "[GetCredentialStatus] Unable to get user: app error [userIndex=%d]", i); |
| return false; |
| } |
| |
| // Go through occupied users only |
| if (UserStatusEnum::kAvailable == user.userStatus) |
| { |
| continue; |
| } |
| |
| for (size_t j = 0; j < user.credentials.size(); ++j) |
| { |
| if (user.credentials.data()[j].credentialIndex == credentialIndex && |
| user.credentials.data()[j].credentialType == credentialType) |
| { |
| userIndex = i; |
| return true; |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| bool DoorLockServer::findUserIndexByCredential(chip::EndpointId endpointId, CredentialTypeEnum credentialType, |
| chip::ByteSpan credentialData, uint16_t & userIndex, uint16_t & credentialIndex, |
| EmberAfPluginDoorLockUserInfo & userInfo) |
| { |
| uint16_t maxNumberOfUsers = 0; |
| VerifyOrReturnError(GetAttribute(endpointId, Attributes::NumberOfTotalUsersSupported::Id, |
| Attributes::NumberOfTotalUsersSupported::Get, maxNumberOfUsers), |
| false); |
| |
| for (uint16_t i = 1; i <= maxNumberOfUsers; ++i) |
| { |
| EmberAfPluginDoorLockUserInfo user; |
| if (!emberAfPluginDoorLockGetUser(endpointId, i, user)) |
| { |
| ChipLogError(Zcl, "[findUserIndexByCredential] Unable to get user: app error [userIndex=%d]", i); |
| return false; |
| } |
| |
| // Go through occupied users only |
| if (UserStatusEnum::kAvailable == user.userStatus) |
| { |
| continue; |
| } |
| |
| for (const auto & credential : user.credentials) |
| { |
| if (credential.credentialType != credentialType) |
| { |
| continue; |
| } |
| |
| EmberAfPluginDoorLockCredentialInfo credentialInfo; |
| if (!emberAfPluginDoorLockGetCredential(endpointId, credential.credentialIndex, credentialType, credentialInfo)) |
| { |
| ChipLogError(Zcl, |
| "[findUserIndexByCredential] Unable to get credential: app error " |
| "[userIndex=%d,credentialIndex=%d,credentialType=%u]", |
| i, credential.credentialIndex, to_underlying(credentialType)); |
| return false; |
| } |
| |
| if (credentialInfo.status != DlCredentialStatus::kOccupied) |
| { |
| ChipLogError(Zcl, |
| "[findUserIndexByCredential] Users/Credentials database error: credential index attached to user is " |
| "not occupied " |
| "[userIndex=%d,credentialIndex=%d,credentialType=%u]", |
| i, credential.credentialIndex, to_underlying(credentialType)); |
| return false; |
| } |
| |
| if (credentialInfo.credentialData.data_equal(credentialData)) |
| { |
| userIndex = i; |
| credentialIndex = credential.credentialIndex; |
| userInfo = user; |
| return true; |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| EmberAfStatus DoorLockServer::createUser(chip::EndpointId endpointId, chip::FabricIndex creatorFabricIdx, chip::NodeId sourceNodeId, |
| uint16_t userIndex, const Nullable<chip::CharSpan> & userName, |
| const Nullable<uint32_t> & userUniqueId, const Nullable<UserStatusEnum> & userStatus, |
| const Nullable<UserTypeEnum> & userType, |
| const Nullable<CredentialRuleEnum> & credentialRule, |
| const Nullable<CredentialStruct> & credential) |
| { |
| EmberAfPluginDoorLockUserInfo user; |
| if (!emberAfPluginDoorLockGetUser(endpointId, userIndex, user)) |
| { |
| ChipLogError(Zcl, "[createUser] Unable to get the user from app [endpointId=%d,userIndex=%d]", endpointId, userIndex); |
| return EMBER_ZCL_STATUS_FAILURE; |
| } |
| |
| // appclusters, 5.2.4.34: to modify user its status should be set to Available. If it is we should return OCCUPIED. |
| if (UserStatusEnum::kAvailable != user.userStatus) |
| { |
| emberAfDoorLockClusterPrintln("[createUser] Unable to overwrite existing user [endpointId=%d,userIndex=%d]", endpointId, |
| userIndex); |
| return static_cast<EmberAfStatus>(DlStatus::kOccupied); |
| } |
| |
| const auto & newUserName = !userName.IsNull() ? userName.Value() : chip::CharSpan::fromCharString(""); |
| auto newUserUniqueId = userUniqueId.IsNull() ? 0xFFFFFFFF : userUniqueId.Value(); |
| auto newUserStatus = userStatus.IsNull() ? UserStatusEnum::kOccupiedEnabled : userStatus.Value(); |
| auto newUserType = userType.IsNull() ? UserTypeEnum::kUnrestrictedUser : userType.Value(); |
| auto newCredentialRule = credentialRule.IsNull() ? CredentialRuleEnum::kSingle : credentialRule.Value(); |
| const CredentialStruct * newCredentials = nullptr; |
| size_t newTotalCredentials = 0; |
| if (!credential.IsNull()) |
| { |
| newCredentials = &credential.Value(); |
| newTotalCredentials = 1; |
| } |
| |
| if (!emberAfPluginDoorLockSetUser(endpointId, userIndex, creatorFabricIdx, creatorFabricIdx, newUserName, newUserUniqueId, |
| newUserStatus, newUserType, newCredentialRule, newCredentials, newTotalCredentials)) |
| { |
| emberAfDoorLockClusterPrintln("[createUser] Unable to create user: app error " |
| "[endpointId=%d,creatorFabricId=%d,userIndex=%d,userName=\"%.*s\",userUniqueId=0x%" PRIx32 |
| ",userStatus=%u," |
| "userType=%u,credentialRule=%u,totalCredentials=%u]", |
| endpointId, creatorFabricIdx, userIndex, static_cast<int>(newUserName.size()), |
| newUserName.data(), newUserUniqueId, to_underlying(newUserStatus), to_underlying(newUserType), |
| to_underlying(newCredentialRule), static_cast<unsigned int>(newTotalCredentials)); |
| return EMBER_ZCL_STATUS_FAILURE; |
| } |
| |
| emberAfDoorLockClusterPrintln("[createUser] User created " |
| "[endpointId=%d,creatorFabricId=%d,userIndex=%d,userName=\"%.*s\",userUniqueId=0x%" PRIx32 |
| ",userStatus=%u," |
| "userType=%u,credentialRule=%u,totalCredentials=%u]", |
| endpointId, creatorFabricIdx, userIndex, static_cast<int>(newUserName.size()), newUserName.data(), |
| newUserUniqueId, to_underlying(newUserStatus), to_underlying(newUserType), |
| to_underlying(newCredentialRule), static_cast<unsigned int>(newTotalCredentials)); |
| |
| sendRemoteLockUserChange(endpointId, LockDataTypeEnum::kUserIndex, DataOperationTypeEnum::kAdd, sourceNodeId, creatorFabricIdx, |
| userIndex, userIndex); |
| |
| return EMBER_ZCL_STATUS_SUCCESS; |
| } |
| |
| EmberAfStatus DoorLockServer::modifyUser(chip::EndpointId endpointId, chip::FabricIndex modifierFabricIndex, |
| chip::NodeId sourceNodeId, uint16_t userIndex, const Nullable<chip::CharSpan> & userName, |
| const Nullable<uint32_t> & userUniqueId, const Nullable<UserStatusEnum> & userStatus, |
| const Nullable<UserTypeEnum> & userType, |
| const Nullable<CredentialRuleEnum> & credentialRule) |
| { |
| // We should get the user by that index first |
| EmberAfPluginDoorLockUserInfo user; |
| if (!emberAfPluginDoorLockGetUser(endpointId, userIndex, user)) |
| { |
| ChipLogError(Zcl, "[modifyUser] Unable to get the user from app [endpointId=%d,userIndex=%d]", endpointId, userIndex); |
| return EMBER_ZCL_STATUS_FAILURE; |
| } |
| |
| // appclusters, 5.2.4.34: to modify user its status should NOT be set to Available. If it is we should return INVALID_COMMAND. |
| if (UserStatusEnum::kAvailable == user.userStatus) |
| { |
| emberAfDoorLockClusterPrintln("[modifyUser] Unable to modify non-existing user [endpointId=%d,userIndex=%d]", endpointId, |
| userIndex); |
| return EMBER_ZCL_STATUS_INVALID_COMMAND; |
| } |
| |
| // appclusters, 5.2.4.34: UserName SHALL be null if modifying a user record that was not created by the accessing fabric |
| if (user.createdBy != modifierFabricIndex && !userName.IsNull()) |
| { |
| emberAfDoorLockClusterPrintln("[modifyUser] Unable to modify name of user created by different fabric " |
| "[endpointId=%d,userIndex=%d,creatorIdx=%d,modifierIdx=%d]", |
| endpointId, userIndex, user.createdBy, modifierFabricIndex); |
| return EMBER_ZCL_STATUS_INVALID_COMMAND; |
| } |
| |
| // appclusters, 5.2.4.34: UserUniqueID SHALL be null if modifying the user record that was not created by the accessing fabric. |
| if (user.createdBy != modifierFabricIndex && !userUniqueId.IsNull()) |
| { |
| emberAfDoorLockClusterPrintln("[modifyUser] Unable to modify UUID of user created by different fabric " |
| "[endpointId=%d,userIndex=%d,creatorIdx=%d,modifierIdx=%d]", |
| endpointId, userIndex, user.createdBy, modifierFabricIndex); |
| return EMBER_ZCL_STATUS_INVALID_COMMAND; |
| } |
| |
| const auto & newUserName = !userName.IsNull() ? userName.Value() : user.userName; |
| auto newUserUniqueId = userUniqueId.IsNull() ? user.userUniqueId : userUniqueId.Value(); |
| auto newUserStatus = userStatus.IsNull() ? user.userStatus : userStatus.Value(); |
| auto newUserType = userType.IsNull() ? user.userType : userType.Value(); |
| auto newCredentialRule = credentialRule.IsNull() ? user.credentialRule : credentialRule.Value(); |
| |
| if (!emberAfPluginDoorLockSetUser(endpointId, userIndex, user.createdBy, modifierFabricIndex, newUserName, newUserUniqueId, |
| newUserStatus, newUserType, newCredentialRule, user.credentials.data(), |
| user.credentials.size())) |
| { |
| ChipLogError(Zcl, |
| "[modifyUser] Unable to modify the user: app error " |
| "[endpointId=%d,modifierFabric=%d,userIndex=%d,userName=\"%.*s\",userUniqueId=0x%" PRIx32 ",userStatus=%u" |
| ",userType=%u,credentialRule=%u]", |
| endpointId, modifierFabricIndex, userIndex, static_cast<int>(newUserName.size()), newUserName.data(), |
| newUserUniqueId, to_underlying(newUserStatus), to_underlying(newUserType), to_underlying(newCredentialRule)); |
| return EMBER_ZCL_STATUS_FAILURE; |
| } |
| |
| emberAfDoorLockClusterPrintln("[modifyUser] User modified " |
| "[endpointId=%d,modifierFabric=%d,userIndex=%d,userName=\"%.*s\",userUniqueId=0x%" PRIx32 |
| ",userStatus=%u,userType=%u,credentialRule=%u]", |
| endpointId, modifierFabricIndex, userIndex, static_cast<int>(newUserName.size()), |
| newUserName.data(), newUserUniqueId, to_underlying(newUserStatus), to_underlying(newUserType), |
| to_underlying(newCredentialRule)); |
| |
| sendRemoteLockUserChange(endpointId, LockDataTypeEnum::kUserIndex, DataOperationTypeEnum::kModify, sourceNodeId, |
| modifierFabricIndex, userIndex, userIndex); |
| |
| return EMBER_ZCL_STATUS_SUCCESS; |
| } |
| |
| Status DoorLockServer::clearUser(chip::EndpointId endpointId, chip::FabricIndex modifierFabricId, chip::NodeId sourceNodeId, |
| uint16_t userIndex, bool sendUserChangeEvent) |
| { |
| EmberAfPluginDoorLockUserInfo user; |
| if (!emberAfPluginDoorLockGetUser(endpointId, userIndex, user)) |
| { |
| return Status::Failure; |
| } |
| |
| return clearUser(endpointId, modifierFabricId, sourceNodeId, userIndex, user, sendUserChangeEvent); |
| } |
| |
| Status DoorLockServer::clearUser(chip::EndpointId endpointId, chip::FabricIndex modifierFabricId, chip::NodeId sourceNodeId, |
| uint16_t userIndex, const EmberAfPluginDoorLockUserInfo & user, bool sendUserChangeEvent) |
| { |
| // appclusters, 5.2.4.37: all the credentials associated with user should be cleared when clearing the user |
| for (const auto & credential : user.credentials) |
| { |
| emberAfDoorLockClusterPrintln( |
| "[ClearUser] Clearing associated credential [endpointId=%d,userIndex=%d,credentialType=%u,credentialIndex=%d]", |
| endpointId, userIndex, to_underlying(credential.credentialType), credential.credentialIndex); |
| |
| if (!emberAfPluginDoorLockSetCredential(endpointId, credential.credentialIndex, kUndefinedFabricIndex, |
| kUndefinedFabricIndex, DlCredentialStatus::kAvailable, credential.credentialType, |
| chip::ByteSpan())) |
| { |
| ChipLogError(Zcl, |
| "[ClearUser] Unable to remove credentials associated with user - internal error " |
| "[endpointId=%d,userIndex=%d,credentialIndex=%d,credentialType=%u]", |
| endpointId, userIndex, credential.credentialIndex, to_underlying(credential.credentialType)); |
| return Status::Failure; |
| } |
| } |
| |
| // Clear all the user schedules |
| auto status = clearSchedules(endpointId, userIndex); |
| if (DlStatus::kSuccess != status) |
| { |
| ChipLogError(Zcl, "[ClearUser] Unable to delete schedules - internal error [endpointId=%d,userIndex=%d]", endpointId, |
| userIndex); |
| // TODO: Figure out whether this should still clear the user even though |
| // schedule clearing failed? |
| } |
| |
| // Remove the user entry |
| if (!emberAfPluginDoorLockSetUser(endpointId, userIndex, kUndefinedFabricIndex, kUndefinedFabricIndex, chip::CharSpan(""), 0, |
| UserStatusEnum::kAvailable, UserTypeEnum::kUnrestrictedUser, CredentialRuleEnum::kSingle, |
| nullptr, 0)) |
| { |
| return Status::Failure; |
| } |
| |
| if (sendUserChangeEvent) |
| { |
| sendRemoteLockUserChange(endpointId, LockDataTypeEnum::kUserIndex, DataOperationTypeEnum::kClear, sourceNodeId, |
| modifierFabricId, userIndex, userIndex); |
| } |
| return Status::Success; |
| } |
| |
| bool DoorLockServer::clearFabricFromUsers(chip::EndpointId endpointId, chip::FabricIndex fabricIndex) |
| { |
| uint16_t maxNumberOfUsers; |
| VerifyOrReturnError(GetAttribute(endpointId, Attributes::NumberOfTotalUsersSupported::Id, |
| Attributes::NumberOfTotalUsersSupported::Get, maxNumberOfUsers), |
| false); |
| |
| for (uint16_t userIndex = 1; userIndex <= maxNumberOfUsers; ++userIndex) |
| { |
| EmberAfPluginDoorLockUserInfo user; |
| if (!emberAfPluginDoorLockGetUser(endpointId, userIndex, user)) |
| { |
| ChipLogError(Zcl, |
| "[OnFabricRemoved] Unable to get the user - internal error [endpointId=%d,fabricIndex=%d,userIndex=%d]", |
| endpointId, fabricIndex, userIndex); |
| continue; |
| } |
| |
| // Filter out unoccupied slots and users that don't have corresponding fabricIndex in the created/modified fields |
| if (UserStatusEnum::kAvailable == user.userStatus || (fabricIndex != user.createdBy && fabricIndex != user.lastModifiedBy)) |
| { |
| continue; |
| } |
| |
| if (user.createdBy == fabricIndex) |
| { |
| user.createdBy = kUndefinedFabricIndex; |
| } |
| |
| if (user.lastModifiedBy == fabricIndex) |
| { |
| user.lastModifiedBy = kUndefinedFabricIndex; |
| } |
| |
| if (!emberAfPluginDoorLockSetUser(endpointId, userIndex, user.createdBy, user.lastModifiedBy, user.userName, |
| user.userUniqueId, user.userStatus, user.userType, user.credentialRule, |
| user.credentials.data(), user.credentials.size())) |
| { |
| ChipLogError( |
| Zcl, |
| "[OnFabricRemoved] Unable to update the user fabrics - internal error [endpointId=%d,fabricIndex=%d,userIndex=%d]", |
| endpointId, fabricIndex, userIndex); |
| } |
| } |
| return true; |
| } |
| |
| DlStatus DoorLockServer::createNewCredentialAndUser(chip::EndpointId endpointId, chip::FabricIndex creatorFabricIdx, |
| chip::NodeId sourceNodeId, const Nullable<UserStatusEnum> & userStatus, |
| const Nullable<UserTypeEnum> & userType, const CredentialStruct & credential, |
| const chip::ByteSpan & credentialData, uint16_t & createdUserIndex) |
| { |
| uint16_t availableUserIndex = 0; |
| if (!findUnoccupiedUserSlot(endpointId, availableUserIndex)) |
| { |
| emberAfDoorLockClusterPrintln("[SetCredential] Unable to create new user for credential: no available user slots " |
| "[endpointId=%d,credentialIndex=%d]", |
| endpointId, credential.credentialIndex); |
| return DlStatus::kOccupied; |
| } |
| |
| auto status = |
| createUser(endpointId, creatorFabricIdx, sourceNodeId, availableUserIndex, Nullable<CharSpan>(), Nullable<uint32_t>(), |
| userStatus, userType, Nullable<CredentialRuleEnum>(), Nullable<CredentialStruct>(credential)); |
| if (EMBER_ZCL_STATUS_SUCCESS != status) |
| { |
| emberAfDoorLockClusterPrintln("[SetCredential] Unable to create new user for credential: internal error " |
| "[endpointId=%d,credentialIndex=%d,userIndex=%d,status=%d]", |
| endpointId, credential.credentialIndex, availableUserIndex, status); |
| return DlStatus::kFailure; |
| } |
| |
| if (!emberAfPluginDoorLockSetCredential(endpointId, credential.credentialIndex, creatorFabricIdx, creatorFabricIdx, |
| DlCredentialStatus::kOccupied, credential.credentialType, credentialData)) |
| { |
| emberAfDoorLockClusterPrintln("[SetCredential] Unable to set the credential: app error " |
| "[endpointId=%d,credentialIndex=%d,credentialType=%u,dataLength=%u]", |
| endpointId, credential.credentialIndex, to_underlying(credential.credentialType), |
| static_cast<unsigned int>(credentialData.size())); |
| return DlStatus::kFailure; |
| } |
| |
| emberAfDoorLockClusterPrintln("[SetCredential] Credential and user were created " |
| "[endpointId=%d,credentialIndex=%d,credentialType=%u,dataLength=%u,userIndex=%d]", |
| endpointId, credential.credentialIndex, to_underlying(credential.credentialType), |
| static_cast<unsigned int>(credentialData.size()), availableUserIndex); |
| createdUserIndex = availableUserIndex; |
| |
| return DlStatus::kSuccess; |
| } |
| |
| DlStatus DoorLockServer::createNewCredentialAndAddItToUser(chip::EndpointId endpointId, chip::FabricIndex modifierFabricIdx, |
| uint16_t userIndex, const CredentialStruct & credential, |
| const chip::ByteSpan & credentialData) |
| { |
| if (!userIndexValid(endpointId, userIndex)) |
| { |
| emberAfDoorLockClusterPrintln("[SetCredential] Unable to add new credential to user: user out of bounds " |
| "[endpointId=%d,credentialIndex=%d,userIndex=%d]", |
| endpointId, credential.credentialIndex, userIndex); |
| return DlStatus::kInvalidField; |
| } |
| |
| EmberAfPluginDoorLockUserInfo user; |
| if (!emberAfPluginDoorLockGetUser(endpointId, userIndex, user)) |
| { |
| emberAfDoorLockClusterPrintln( |
| "[SetCredential] Unable to check if credential exists: app error [endpointId=%d,credentialIndex=%d,userIndex=%d]", |
| endpointId, credential.credentialIndex, userIndex); |
| |
| return DlStatus::kFailure; |
| } |
| |
| // 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 == UserStatusEnum::kAvailable) |
| { |
| 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; |
| } |
| |
| // Add new credential to the user |
| auto status = addCredentialToUser(endpointId, modifierFabricIdx, userIndex, credential); |
| if (DlStatus::kSuccess != status) |
| { |
| emberAfDoorLockClusterPrintln("[SetCredential] Unable to add credential to a user: internal error " |
| "[endpointId=%d,credentialIndex=%d,userIndex=%d,status=%u]", |
| endpointId, credential.credentialIndex, userIndex, to_underlying(status)); |
| return status; |
| } |
| |
| if (!emberAfPluginDoorLockSetCredential(endpointId, credential.credentialIndex, modifierFabricIdx, modifierFabricIdx, |
| DlCredentialStatus::kOccupied, credential.credentialType, credentialData)) |
| { |
| emberAfDoorLockClusterPrintln("[SetCredential] Unable to set the credential: app error " |
| "[endpointId=%d,credentialIndex=%d,credentialType=%u,dataLength=%u]", |
| endpointId, credential.credentialIndex, to_underlying(credential.credentialType), |
| static_cast<unsigned int>(credentialData.size())); |
| return DlStatus::kFailure; |
| } |
| |
| return DlStatus::kSuccess; |
| } |
| |
| DlStatus DoorLockServer::addCredentialToUser(chip::EndpointId endpointId, chip::FabricIndex modifierFabricIdx, uint16_t userIndex, |
| const CredentialStruct & credential) |
| { |
| // We should get the user by that index first |
| EmberAfPluginDoorLockUserInfo user; |
| if (!emberAfPluginDoorLockGetUser(endpointId, userIndex, user)) |
| { |
| emberAfDoorLockClusterPrintln("[AddCredentialToUser] Unable to get the user from app [endpointId=%d,userIndex=%d]", |
| endpointId, userIndex); |
| return DlStatus::kFailure; |
| } |
| |
| for (size_t i = 0; i < user.credentials.size(); ++i) |
| { |
| // appclusters, 5.2.4.40: CredentialIndex in CredentialStruct provided SHALL be for an available credential slot. |
| // appclusters, 5.6.3.2: This is the index of the specific credential used to authorize |
| // the lock operation in the list of credentials identified by CredentialType |
| if (user.credentials.data()[i].credentialIndex == credential.credentialIndex && |
| user.credentials.data()[i].credentialType == credential.credentialType) |
| { |
| emberAfDoorLockClusterPrintln( |
| "[AddCredentialToUser] Unable to add credential to user: credential with this index is already associated " |
| "with user [endpointId=%d,userIndex=%d,credentialType=%d,credentialIndex=%d]", |
| endpointId, userIndex, to_underlying(credential.credentialType), credential.credentialIndex); |
| return DlStatus::kInvalidField; |
| } |
| } |
| |
| uint8_t maxCredentialsPerUser; |
| if (!GetNumberOfCredentialsSupportedPerUser(endpointId, maxCredentialsPerUser)) |
| { |
| ChipLogError(Zcl, |
| "[AddCredentialToUser] Unable to get the number of available credentials per user: internal error " |
| "[endpointId=%d,userIndex=%d,credentialType=%d,credentialIndex=%d]", |
| endpointId, userIndex, to_underlying(credential.credentialType), credential.credentialIndex); |
| return DlStatus::kFailure; |
| } |
| |
| // appclusters: spec defines up to NumberOfCredentialsSupportedPerUser credentials per user |
| if (user.credentials.size() + 1 > maxCredentialsPerUser) |
| { |
| emberAfDoorLockClusterPrintln("[AddCredentialToUser] Unable to add credentials to user: too many credentials " |
| "[endpointId=%d,userIndex=%d,userTotalCredentials=%u]", |
| endpointId, userIndex, static_cast<unsigned int>(user.credentials.size())); |
| return DlStatus::kResourceExhausted; |
| } |
| |
| chip::Platform::ScopedMemoryBuffer<CredentialStruct> newCredentials; |
| if (!newCredentials.Alloc(user.credentials.size() + 1)) |
| { |
| ChipLogError(Zcl, |
| "[AddCredentialToUser] Unable to allocate the buffer for credentials " |
| "[endpointId=%d,userIndex=%d,userTotalCredentials=%u]", |
| endpointId, userIndex, static_cast<unsigned int>(user.credentials.size())); |
| return DlStatus::kFailure; |
| } |
| |
| memcpy(newCredentials.Get(), user.credentials.data(), sizeof(CredentialStruct) * user.credentials.size()); |
| newCredentials[user.credentials.size()] = credential; |
| |
| if (!emberAfPluginDoorLockSetUser(endpointId, userIndex, user.createdBy, modifierFabricIdx, user.userName, user.userUniqueId, |
| user.userStatus, user.userType, user.credentialRule, newCredentials.Get(), |
| user.credentials.size() + 1)) |
| { |
| emberAfDoorLockClusterPrintln( |
| "[AddCredentialToUser] Unable to add credential to user: credential with this index is already associated " |
| "with user [endpointId=%d,userIndex=%d,credentialType=%d,credentialIndex=%d,userTotalCredentials=%u]", |
| endpointId, userIndex, to_underlying(credential.credentialType), credential.credentialIndex, |
| static_cast<unsigned int>(user.credentials.size())); |
| return DlStatus::kFailure; |
| } |
| |
| emberAfDoorLockClusterPrintln("[AddCredentialToUser] Credential added to user " |
| "[endpointId=%d,userIndex=%d,credentialType=%d,credentialIndex=%d,userTotalCredentials=%u]", |
| endpointId, userIndex, to_underlying(credential.credentialType), credential.credentialIndex, |
| static_cast<unsigned int>(user.credentials.size() + 1)); |
| |
| return DlStatus::kSuccess; |
| } |
| |
| DlStatus DoorLockServer::modifyCredentialForUser(chip::EndpointId endpointId, chip::FabricIndex modifierFabricIdx, |
| uint16_t userIndex, const CredentialStruct & credential) |
| { |
| // We should get the user by that index first |
| EmberAfPluginDoorLockUserInfo user; |
| if (!emberAfPluginDoorLockGetUser(endpointId, userIndex, user)) |
| { |
| emberAfDoorLockClusterPrintln("[ModifyUserCredential] Unable to get the user from app [endpointId=%d,userIndex=%d]", |
| endpointId, userIndex); |
| return DlStatus::kFailure; |
| } |
| |
| for (size_t i = 0; i < user.credentials.size(); ++i) |
| { |
| // appclusters, 5.2.4.40: user should already be associated with given credentialIndex |
| if (user.credentials.data()[i].credentialIndex == credential.credentialIndex) |
| { |
| chip::Platform::ScopedMemoryBuffer<CredentialStruct> newCredentials; |
| if (!newCredentials.Alloc(user.credentials.size())) |
| { |
| ChipLogError(Zcl, |
| "[ModifyUserCredential] Unable to allocate the buffer for credentials " |
| "[endpointId=%d,userIndex=%d,userTotalCredentials=%u,credentialType=%d,credentialIndex=%d]", |
| endpointId, userIndex, static_cast<unsigned int>(user.credentials.size()), |
| to_underlying(credential.credentialType), credential.credentialIndex); |
| return DlStatus::kFailure; |
| } |
| memcpy(newCredentials.Get(), user.credentials.data(), sizeof(CredentialStruct) * user.credentials.size()); |
| newCredentials[i] = credential; |
| |
| emberAfDoorLockClusterPrintln( |
| "[ModifyUserCredential] Unable to add credential to user: credential with this index is already associated " |
| "[endpointId=%d,userIndex=%d,credentialType=%d,credentialIndex=%d]", |
| endpointId, userIndex, to_underlying(credential.credentialType), credential.credentialIndex); |
| |
| if (!emberAfPluginDoorLockSetUser(endpointId, userIndex, user.createdBy, modifierFabricIdx, user.userName, |
| user.userUniqueId, user.userStatus, user.userType, user.credentialRule, |
| newCredentials.Get(), user.credentials.size())) |
| { |
| emberAfDoorLockClusterPrintln( |
| "[ModifyUserCredential] Unable to modify user credential: credential with this index is already associated " |
| "with user [endpointId=%d,userIndex=%d,credentialType=%d,credentialIndex=%d,userTotalCredentials=%u]", |
| endpointId, userIndex, to_underlying(credential.credentialType), credential.credentialIndex, |
| static_cast<unsigned int>(user.credentials.size())); |
| return DlStatus::kFailure; |
| } |
| |
| emberAfDoorLockClusterPrintln( |
| "[ModifyUserCredential] User credential modified " |
| "[endpointId=%d,userIndex=%d,credentialType=%d,credentialIndex=%d,userTotalCredentials=%u]", |
| endpointId, userIndex, to_underlying(credential.credentialType), credential.credentialIndex, |
| static_cast<unsigned int>(user.credentials.size())); |
| |
| return DlStatus::kSuccess; |
| } |
| } |
| |
| // appclusters, 5.2.4.40: if user is not associated with credential index we should return INVALID_COMMAND |
| emberAfDoorLockClusterPrintln( |
| "[ModifyUserCredential] Unable to modify user credential: user is not associated with credential index " |
| "[endpointId=%d,userIndex=%d,credentialIndex=%d]", |
| endpointId, userIndex, credential.credentialIndex); |
| |
| return DlStatus::kInvalidField; |
| } |
| |
| DlStatus DoorLockServer::createCredential(chip::EndpointId endpointId, chip::FabricIndex creatorFabricIdx, |
| chip::NodeId sourceNodeId, uint16_t credentialIndex, CredentialTypeEnum credentialType, |
| const EmberAfPluginDoorLockCredentialInfo & existingCredential, |
| const chip::ByteSpan & credentialData, Nullable<uint16_t> userIndex, |
| const Nullable<UserStatusEnum> & userStatus, Nullable<UserTypeEnum> userType, |
| uint16_t & createdUserIndex) |
| { |
| // appclusters, 5.2.4.41.1: should send the OCCUPIED in the response when the credential is in use |
| if (DlCredentialStatus::kAvailable != existingCredential.status) |
| { |
| emberAfDoorLockClusterPrintln( |
| "[SetCredential] Unable to set the credential: credential slot is occupied [endpointId=%d,credentialIndex=%d]", |
| endpointId, credentialIndex); |
| |
| return DlStatus::kOccupied; |
| } |
| |
| if (!userType.IsNull() && UserTypeEnum::kProgrammingUser == userType.Value()) |
| { |
| emberAfDoorLockClusterPrintln("[SetCredential] Unable to set the credential: user type is invalid " |
| "[endpointId=%d,credentialIndex=%d,userType=%u]", |
| endpointId, credentialIndex, to_underlying(userType.Value())); |
| |
| return DlStatus::kInvalidField; |
| } |
| |
| CredentialStruct credential{ credentialType, credentialIndex }; |
| // appclusters, 5.2.4.40: if userIndex is not provided we should create new user |
| DlStatus status = DlStatus::kSuccess; |
| if (userIndex.IsNull()) |
| { |
| emberAfDoorLockClusterPrintln("[SetCredential] UserIndex is not set, creating new user [endpointId=%d,credentialIndex=%d]", |
| endpointId, credentialIndex); |
| |
| status = createNewCredentialAndUser(endpointId, creatorFabricIdx, sourceNodeId, userStatus, userType, credential, |
| credentialData, createdUserIndex); |
| } |
| else |
| { |
| // appclusters, 5.2.4.40: if user index is NULL, we should try to modify |
| // the existing user. In this case userStatus and userType shall both |
| // be null. |
| if (!userStatus.IsNull() || !userType.IsNull()) |
| { |
| emberAfDoorLockClusterPrintln("[SetCredential] Unable to add credential: invalid arguments " |
| "[endpointId=%d,credentialIndex=%d,credentialType=%u]", |
| endpointId, credentialIndex, to_underlying(credentialType)); |
| return DlStatus::kInvalidField; |
| } |
| status = createNewCredentialAndAddItToUser(endpointId, creatorFabricIdx, userIndex.Value(), credential, credentialData); |
| } |
| |
| if (DlStatus::kSuccess == status) |
| { |
| sendRemoteLockUserChange(endpointId, credentialTypeToLockDataType(credentialType), DataOperationTypeEnum::kAdd, |
| sourceNodeId, creatorFabricIdx, createdUserIndex == 0 ? userIndex.Value() : createdUserIndex, |
| credentialIndex); |
| } |
| |
| return status; |
| } |
| |
| DlStatus DoorLockServer::modifyProgrammingPIN(chip::EndpointId endpointId, chip::FabricIndex modifierFabricIndex, |
| chip::NodeId sourceNodeId, uint16_t credentialIndex, |
| CredentialTypeEnum credentialType, |
| const EmberAfPluginDoorLockCredentialInfo & existingCredential, |
| const chip::ByteSpan & credentialData) |
| { |
| if (CredentialTypeEnum::kProgrammingPIN != credentialType || 0 != credentialIndex) |
| { |
| emberAfDoorLockClusterPrintln( |
| "[SetCredential] Unable to modify programming PIN: invalid argument [endpointId=%d,credentialIndex=%d]", endpointId, |
| credentialIndex); |
| |
| return DlStatus::kInvalidField; |
| } |
| |
| emberAfDoorLockClusterPrintln("[SetCredential] Modifying the programming PIN [endpointId=%d,credentialIndex=%d]", endpointId, |
| credentialIndex); |
| |
| uint16_t relatedUserIndex = 0; |
| if (!findUserIndexByCredential(endpointId, CredentialTypeEnum::kProgrammingPIN, 0, relatedUserIndex)) |
| { |
| ChipLogError(Zcl, "[SetCredential] Unable to modify PIN - related user not found (internal error) [endpointId=%d]", |
| endpointId); |
| return DlStatus::kFailure; |
| } |
| |
| if (!emberAfPluginDoorLockSetCredential(endpointId, credentialIndex, existingCredential.createdBy, modifierFabricIndex, |
| existingCredential.status, existingCredential.credentialType, credentialData)) |
| { |
| emberAfDoorLockClusterPrintln("[SetCredential] Unable to modify the credential: app error " |
| "[endpointId=%d,credentialIndex=%d,credentialType=%u,credentialDataSize=%u]", |
| endpointId, credentialIndex, to_underlying(credentialType), |
| static_cast<unsigned int>(credentialData.size())); |
| return DlStatus::kFailure; |
| } |
| |
| emberAfDoorLockClusterPrintln("[SetCredential] Successfully modified the credential " |
| "[endpointId=%d,credentialIndex=%d,credentialType=%u,credentialDataSize=%u]", |
| endpointId, credentialIndex, to_underlying(credentialType), |
| static_cast<unsigned int>(credentialData.size())); |
| |
| sendRemoteLockUserChange(endpointId, credentialTypeToLockDataType(credentialType), DataOperationTypeEnum::kModify, sourceNodeId, |
| modifierFabricIndex, relatedUserIndex, credentialIndex); |
| |
| return DlStatus::kSuccess; |
| } |
| |
| DlStatus DoorLockServer::modifyCredential(chip::EndpointId endpointId, chip::FabricIndex modifierFabricIndex, |
| chip::NodeId sourceNodeId, uint16_t credentialIndex, CredentialTypeEnum credentialType, |
| const EmberAfPluginDoorLockCredentialInfo & existingCredential, |
| const chip::ByteSpan & credentialData, uint16_t userIndex, |
| const Nullable<UserStatusEnum> & userStatus, Nullable<UserTypeEnum> userType) |
| { |
| |
| // appclusters, 5.2.4.40: when modifying a credential, userStatus and userType shall both be NULL. |
| if (!userStatus.IsNull() || !userType.IsNull()) |
| { |
| emberAfDoorLockClusterPrintln("[SetCredential] Unable to modify the credential: invalid arguments " |
| "[endpointId=%d,credentialIndex=%d,credentialType=%u]", |
| endpointId, credentialIndex, to_underlying(credentialType)); |
| return DlStatus::kInvalidField; |
| } |
| |
| CredentialStruct credential{ credentialType, credentialIndex }; |
| auto status = modifyCredentialForUser(endpointId, modifierFabricIndex, userIndex, credential); |
| |
| if (DlStatus::kSuccess == status) |
| { |
| if (!emberAfPluginDoorLockSetCredential(endpointId, credentialIndex, existingCredential.createdBy, modifierFabricIndex, |
| existingCredential.status, existingCredential.credentialType, credentialData)) |
| { |
| emberAfDoorLockClusterPrintln("[SetCredential] Unable to modify the credential: app error " |
| "[endpointId=%d,credentialIndex=%d,credentialType=%u,credentialDataSize=%u]", |
| endpointId, credentialIndex, to_underlying(credentialType), |
| static_cast<unsigned int>(credentialData.size())); |
| |
| return DlStatus::kFailure; |
| } |
| |
| emberAfDoorLockClusterPrintln("[SetCredential] Successfully modified the credential " |
| "[endpointId=%d,credentialIndex=%d,credentialType=%u,credentialDataSize=%u]", |
| endpointId, credentialIndex, to_underlying(credentialType), |
| static_cast<unsigned int>(credentialData.size())); |
| |
| sendRemoteLockUserChange(endpointId, credentialTypeToLockDataType(credentialType), DataOperationTypeEnum::kModify, |
| sourceNodeId, modifierFabricIndex, userIndex, credentialIndex); |
| } |
| return status; |
| } |
| |
| void DoorLockServer::sendSetCredentialResponse(chip::app::CommandHandler * commandObj, |
| const chip::app::ConcreteCommandPath & commandPath, DlStatus status, |
| uint16_t userIndex, uint16_t nextCredentialIndex) |
| { |
| Commands::SetCredentialResponse::Type command{}; |
| command.status = status; |
| if (0 != userIndex) |
| { |
| command.userIndex.SetNonNull(userIndex); |
| } |
| if (0 != nextCredentialIndex) |
| { |
| command.nextCredentialIndex.SetNonNull(nextCredentialIndex); |
| } |
| commandObj->AddResponse(commandPath, command); |
| } |
| |
| bool DoorLockServer::credentialTypeSupported(chip::EndpointId endpointId, CredentialTypeEnum type) |
| { |
| switch (type) |
| { |
| case CredentialTypeEnum::kProgrammingPIN: |
| case CredentialTypeEnum::kPin: |
| return SupportsPIN(endpointId); |
| case CredentialTypeEnum::kRfid: |
| return SupportsRFID(endpointId); |
| case CredentialTypeEnum::kFingerprint: |
| case CredentialTypeEnum::kFingerVein: |
| return SupportsFingers(endpointId); |
| case CredentialTypeEnum::kFace: |
| return SupportsFace(endpointId); |
| default: |
| return false; |
| } |
| return false; |
| } |
| |
| bool DoorLockServer::weekDayIndexValid(chip::EndpointId endpointId, uint8_t weekDayIndex) |
| { |
| uint8_t weekDaysSupported; |
| if (!GetNumberOfWeekDaySchedulesPerUserSupported(endpointId, weekDaysSupported)) |
| { |
| return false; |
| } |
| |
| // appclusters, 5.2.4.14-17: weekday index changes from 1 to maxNumberOfUsers |
| if (0 == weekDayIndex || weekDayIndex > weekDaysSupported) |
| { |
| return false; |
| } |
| return true; |
| } |
| |
| DlStatus DoorLockServer::clearWeekDaySchedule(chip::EndpointId endpointId, uint16_t userIndex, uint8_t weekDayIndex) |
| { |
| auto status = emberAfPluginDoorLockSetSchedule(endpointId, weekDayIndex, userIndex, DlScheduleStatus::kAvailable, |
| DaysMaskMap(0), 0, 0, 0, 0); |
| if (DlStatus::kSuccess != status && DlStatus::kNotFound != status) |
| { |
| ChipLogError(Zcl, |
| "[ClearWeekDaySchedule] Unable to clear the schedule - internal error " |
| "[endpointId=%d,userIndex=%d,scheduleIndex=%d,status=%u]", |
| endpointId, userIndex, weekDayIndex, to_underlying(status)); |
| return status; |
| } |
| return DlStatus::kSuccess; |
| } |
| |
| DlStatus DoorLockServer::clearWeekDaySchedules(chip::EndpointId endpointId, uint16_t userIndex) |
| { |
| uint8_t weekDaySchedulesPerUser = 0; |
| if (!GetNumberOfWeekDaySchedulesPerUserSupported(endpointId, weekDaySchedulesPerUser)) |
| { |
| return DlStatus::kFailure; |
| } |
| |
| for (uint8_t i = 1; i <= weekDaySchedulesPerUser; ++i) |
| { |
| auto status = clearWeekDaySchedule(endpointId, userIndex, i); |
| if (DlStatus::kSuccess != status) |
| { |
| return status; |
| } |
| } |
| return DlStatus::kSuccess; |
| } |
| |
| DlStatus DoorLockServer::clearSchedules(chip::EndpointId endpointId, uint16_t userIndex) |
| { |
| auto status = clearWeekDaySchedules(endpointId, userIndex); |
| if (DlStatus::kSuccess != status) |
| { |
| ChipLogError(Zcl, |
| "[CleaAllSchedules] Unable to clear week day schedules for user - internal error " |
| "[endpointId=%d,userIndex=%d,status=%u]", |
| endpointId, userIndex, to_underlying(status)); |
| return status; |
| } |
| |
| status = clearYearDaySchedules(endpointId, userIndex); |
| if (DlStatus::kSuccess != status) |
| { |
| ChipLogError(Zcl, |
| "[CleaAllSchedules] Unable to clear year day schedules for user - internal error " |
| "[endpointId=%d,userIndex=%d,status=%u]", |
| endpointId, userIndex, to_underlying(status)); |
| return status; |
| } |
| |
| // No need to clear the holiday schedules here as they are not specific for user. |
| return DlStatus::kSuccess; |
| } |
| |
| void DoorLockServer::sendGetWeekDayScheduleResponse(chip::app::CommandHandler * commandObj, |
| const chip::app::ConcreteCommandPath & commandPath, uint8_t weekdayIndex, |
| uint16_t userIndex, DlStatus status, DaysMaskMap daysMask, uint8_t startHour, |
| uint8_t startMinute, uint8_t endHour, uint8_t endMinute) |
| { |
| VerifyOrDie(nullptr != commandObj); |
| |
| Commands::GetWeekDayScheduleResponse::Type response; |
| response.weekDayIndex = weekdayIndex; |
| response.userIndex = userIndex; |
| response.status = status; |
| if (DlStatus::kSuccess == status) |
| { |
| response.daysMask = Optional<chip::BitMask<DaysMaskMap>>(daysMask); |
| response.startHour = Optional<uint8_t>(startHour); |
| response.startMinute = Optional<uint8_t>(startMinute); |
| response.endHour = Optional<uint8_t>(endHour); |
| response.endMinute = Optional<uint8_t>(endMinute); |
| } |
| |
| commandObj->AddResponse(commandPath, response); |
| } |
| |
| bool DoorLockServer::yearDayIndexValid(chip::EndpointId endpointId, uint8_t yearDayIndex) |
| { |
| uint8_t yearDaysSupported; |
| if (!GetNumberOfYearDaySchedulesPerUserSupported(endpointId, yearDaysSupported)) |
| { |
| return false; |
| } |
| |
| // appclusters, 5.2.4.18-21: year day index changes from 1 to maxNumberOfUsers |
| if (0 == yearDayIndex || yearDayIndex > yearDaysSupported) |
| { |
| return false; |
| } |
| return true; |
| } |
| |
| DlStatus DoorLockServer::clearYearDaySchedule(chip::EndpointId endpointId, uint16_t userIndex, uint8_t yearDayIndex) |
| { |
| auto status = emberAfPluginDoorLockSetSchedule(endpointId, yearDayIndex, userIndex, DlScheduleStatus::kAvailable, 0, 0); |
| if (DlStatus::kSuccess != status && DlStatus::kNotFound != status) |
| { |
| ChipLogError(Zcl, |
| "[ClearYearDaySchedule] Unable to clear the schedule - internal error " |
| "[endpointId=%d,userIndex=%d,scheduleIndex=%d,status=%u]", |
| endpointId, userIndex, yearDayIndex, to_underlying(status)); |
| return status; |
| } |
| return DlStatus::kSuccess; |
| } |
| |
| DlStatus DoorLockServer::clearYearDaySchedules(chip::EndpointId endpointId, uint16_t userIndex) |
| { |
| uint8_t yearDaySchedulesPerUser = 0; |
| if (!GetNumberOfYearDaySchedulesPerUserSupported(endpointId, yearDaySchedulesPerUser)) |
| { |
| return DlStatus::kFailure; |
| } |
| |
| for (uint8_t i = 1; i <= yearDaySchedulesPerUser; ++i) |
| { |
| auto status = clearYearDaySchedule(endpointId, userIndex, i); |
| if (DlStatus::kSuccess != status) |
| { |
| return status; |
| } |
| } |
| return DlStatus::kSuccess; |
| } |
| |
| void DoorLockServer::sendGetYearDayScheduleResponse(chip::app::CommandHandler * commandObj, |
| const chip::app::ConcreteCommandPath & commandPath, uint8_t yearDayIndex, |
| uint16_t userIndex, DlStatus status, uint32_t localStartTime, |
| uint32_t localEndTime) |
| { |
| VerifyOrDie(nullptr != commandObj); |
| |
| Commands::GetYearDayScheduleResponse::Type response; |
| response.yearDayIndex = yearDayIndex; |
| response.userIndex = userIndex; |
| response.status = status; |
| if (DlStatus::kSuccess == status) |
| { |
| response.localStartTime = Optional<uint32_t>(localStartTime); |
| response.localEndTime = Optional<uint32_t>(localEndTime); |
| } |
| |
| commandObj->AddResponse(commandPath, response); |
| } |
| |
| bool DoorLockServer::holidayIndexValid(chip::EndpointId endpointId, uint8_t holidayIndex) |
| { |
| uint8_t holidaysSupported; |
| if (!GetNumberOfHolidaySchedulesSupported(endpointId, holidaysSupported)) |
| { |
| return false; |
| } |
| |
| // appclusters, 5.2.4.22-25: year day index changes from 1 to maxNumberOfHolidaySchedules |
| if (0 == holidayIndex || holidayIndex > holidaysSupported) |
| { |
| return false; |
| } |
| return true; |
| } |
| |
| DlStatus DoorLockServer::clearHolidaySchedule(chip::EndpointId endpointId, uint8_t holidayIndex) |
| { |
| auto status = |
| emberAfPluginDoorLockSetSchedule(endpointId, holidayIndex, DlScheduleStatus::kAvailable, 0, 0, OperatingModeEnum::kNormal); |
| if (DlStatus::kSuccess != status && DlStatus::kNotFound != status) |
| { |
| ChipLogError( |
| Zcl, "[ClearHolidaySchedule] Unable to clear the schedule - internal error [endpointId=%d,scheduleIndex=%d,status=%u]", |
| endpointId, holidayIndex, to_underlying(status)); |
| return status; |
| } |
| return DlStatus::kSuccess; |
| } |
| |
| DlStatus DoorLockServer::clearHolidaySchedules(chip::EndpointId endpointId) |
| { |
| uint8_t totalHolidaySchedules = 0; |
| if (!GetNumberOfHolidaySchedulesSupported(endpointId, totalHolidaySchedules)) |
| { |
| return DlStatus::kFailure; |
| } |
| |
| for (uint8_t i = 1; i <= totalHolidaySchedules; ++i) |
| { |
| auto status = clearHolidaySchedule(endpointId, i); |
| if (DlStatus::kSuccess != status) |
| { |
| return status; |
| } |
| } |
| return DlStatus::kSuccess; |
| } |
| |
| void DoorLockServer::sendHolidayScheduleResponse(chip::app::CommandHandler * commandObj, |
| const chip::app::ConcreteCommandPath & commandPath, uint8_t holidayIndex, |
| DlStatus status, uint32_t localStartTime, uint32_t localEndTime, |
| OperatingModeEnum operatingMode) |
| { |
| VerifyOrDie(nullptr != commandObj); |
| |
| auto response = Commands::GetHolidayScheduleResponse::Type{ holidayIndex, status }; |
| if (DlStatus::kSuccess == status) |
| { |
| response.localStartTime = Optional<uint32_t>(localStartTime); |
| response.localEndTime = Optional<uint32_t>(localEndTime); |
| response.operatingMode = Optional<OperatingModeEnum>(operatingMode); |
| } |
| commandObj->AddResponse(commandPath, response); |
| } |
| |
| Status DoorLockServer::clearCredential(chip::EndpointId endpointId, chip::FabricIndex modifier, chip::NodeId sourceNodeId, |
| CredentialTypeEnum credentialType, uint16_t credentialIndex, bool sendUserChangeEvent) |
| { |
| if (CredentialTypeEnum::kProgrammingPIN == credentialType) |
| { |
| emberAfDoorLockClusterPrintln("[clearCredential] Cannot clear programming PIN credentials " |
| "[endpointId=%d,credentialType=%u,credentialIndex=%d,modifier=%d]", |
| endpointId, to_underlying(credentialType), credentialIndex, modifier); |
| return Status::InvalidCommand; |
| } |
| |
| if (!credentialIndexValid(endpointId, credentialType, credentialIndex)) |
| { |
| emberAfDoorLockClusterPrintln("[clearCredential] Cannot clear credential - index out of bounds " |
| "[endpointId=%d,credentialType=%u,credentialIndex=%d,modifier=%d]", |
| endpointId, to_underlying(credentialType), credentialIndex, modifier); |
| return Status::InvalidCommand; |
| } |
| |
| // 1. Fetch the credential from storage, so we know what we're deleting |
| EmberAfPluginDoorLockCredentialInfo credential; |
| if (!emberAfPluginDoorLockGetCredential(endpointId, credentialIndex, credentialType, credential)) |
| { |
| ChipLogError(Zcl, |
| "[clearCredential] Unable to clear credential - couldn't read credential from database " |
| "[endpointId=%d,credentialType=%u,credentialIndex=%d,modifier=%d]", |
| endpointId, to_underlying(credentialType), credentialIndex, modifier); |
| return Status::Failure; |
| } |
| |
| if (DlCredentialStatus::kAvailable == credential.status) |
| { |
| emberAfDoorLockClusterPrintln("[clearCredential] Ignored attempt to clear unoccupied credential slot " |
| "[endpointId=%d,credentialType=%u,credentialIndex=%d,modifier=%d]", |
| endpointId, to_underlying(credentialType), credentialIndex, modifier); |
| return Status::Success; |
| } |
| |
| if (credentialType != credential.credentialType) |
| { |
| emberAfDoorLockClusterPrintln("[clearCredential] Ignored attempt to clear credential of different type " |
| "[endpointId=%d,credentialType=%u,credentialIndex=%d,modifier=%d,actualCredentialType=%u]", |
| endpointId, to_underlying(credentialType), credentialIndex, modifier, |
| to_underlying(credential.credentialType)); |
| return Status::Success; |
| } |
| |
| // 2. Get the associated user and if it is the only attached credential -- delete the user. This operation will |
| // also remove the associated credential. |
| uint16_t relatedUserIndex = 0; |
| if (!findUserIndexByCredential(endpointId, credentialType, credentialIndex, relatedUserIndex)) |
| { |
| ChipLogError(Zcl, |
| "[clearCredential] Unable to clear related credential user - couldn't find index of related user " |
| "[endpointId=%d,credentialType=%u,credentialIndex=%d,modifier=%d]", |
| endpointId, to_underlying(credentialType), credentialIndex, modifier); |
| return Status::Failure; |
| } |
| EmberAfPluginDoorLockUserInfo relatedUser; |
| if (!emberAfPluginDoorLockGetUser(endpointId, relatedUserIndex, relatedUser)) |
| { |
| ChipLogError(Zcl, |
| "[clearCredential] Unable to clear credential for related user - couldn't get user from database " |
| "[endpointId=%d,credentialType=%u,credentialIndex=%d,modifier=%d,userIndex=%d]", |
| endpointId, to_underlying(credentialType), credentialIndex, modifier, relatedUserIndex); |
| |
| return Status::Failure; |
| } |
| if (1 == relatedUser.credentials.size()) |
| { |
| emberAfDoorLockClusterPrintln("[clearCredential] Clearing related user - no credentials left " |
| "[endpointId=%d,credentialType=%u,credentialIndex=%d,modifier=%d,userIndex=%d]", |
| endpointId, to_underlying(credentialType), credentialIndex, modifier, relatedUserIndex); |
| auto clearStatus = clearUser(endpointId, modifier, sourceNodeId, relatedUserIndex, relatedUser, true); |
| if (Status::Success != clearStatus) |
| { |
| ChipLogError(Zcl, |
| "[clearCredential] Unable to clear related credential user - internal error " |
| "[endpointId=%d,credentialType=%u,credentialIndex=%d,modifier=%d,userIndex=%d,status=%d]", |
| endpointId, to_underlying(credentialType), credentialIndex, modifier, relatedUserIndex, |
| to_underlying(clearStatus)); |
| |
| return Status::Failure; |
| } |
| emberAfDoorLockClusterPrintln("[clearCredential] Successfully clear credential and related user " |
| "[endpointId=%d,credentialType=%u,credentialIndex=%d,modifier=%d,userIndex=%d]", |
| endpointId, to_underlying(credentialType), credentialIndex, modifier, relatedUserIndex); |
| return Status::Success; |
| } |
| |
| // 3. If the user wasn't deleted, delete the credential and adjust the list of credentials for related user in the storage |
| if (!emberAfPluginDoorLockSetCredential(endpointId, credentialIndex, kUndefinedFabricIndex, kUndefinedFabricIndex, |
| DlCredentialStatus::kAvailable, credentialType, chip::ByteSpan())) |
| { |
| ChipLogError(Zcl, |
| "[clearCredential] Unable to clear credential - couldn't write new credential to database " |
| "[endpointId=%d,credentialType=%u,credentialIndex=%d,modifier=%d]", |
| endpointId, to_underlying(credentialType), credentialIndex, modifier); |
| return Status::Failure; |
| } |
| |
| uint8_t maxCredentialsPerUser; |
| if (!GetNumberOfCredentialsSupportedPerUser(endpointId, maxCredentialsPerUser)) |
| { |
| ChipLogError(Zcl, |
| "[clearCredential] Unable to get the number of available credentials per user: internal error " |
| "[endpointId=%d,credentialType=%d,credentialIndex=%d]", |
| endpointId, to_underlying(credentialType), credentialIndex); |
| return Status::Failure; |
| } |
| |
| // Should never happen, only possible if the implementation of application is incorrect |
| if (relatedUser.credentials.size() > maxCredentialsPerUser) |
| { |
| ChipLogError(Zcl, |
| "[clearCredential] Unable to clear credential for related user - user has too many credentials associated" |
| "[endpointId=%d,credentialType=%u,credentialIndex=%d,modifier=%d,userIndex=%d,credentialsCount=%u]", |
| endpointId, to_underlying(credentialType), credentialIndex, modifier, relatedUserIndex, |
| static_cast<unsigned int>(relatedUser.credentials.size())); |
| |
| return Status::Failure; |
| } |
| |
| chip::Platform::ScopedMemoryBuffer<CredentialStruct> newCredentials; |
| if (!newCredentials.Alloc(relatedUser.credentials.size())) |
| { |
| ChipLogError(Zcl, |
| "[clearCredential] Unable to allocate the buffer for credentials " |
| "[endpointId=%d,credentialType=%u,credentialIndex=%d,modifier=%d,userIndex=%d,credentialsCount=%u]", |
| endpointId, to_underlying(credentialType), credentialIndex, modifier, relatedUserIndex, |
| static_cast<unsigned int>(relatedUser.credentials.size())); |
| return Status::Failure; |
| } |
| |
| size_t newCredentialsCount = 0; |
| for (const auto & c : relatedUser.credentials) |
| { |
| if (c.credentialType == credentialType && c.credentialIndex == credentialIndex) |
| { |
| continue; |
| } |
| newCredentials[newCredentialsCount++] = c; |
| } |
| |
| if (!emberAfPluginDoorLockSetUser(endpointId, relatedUserIndex, relatedUser.createdBy, modifier, relatedUser.userName, |
| relatedUser.userUniqueId, relatedUser.userStatus, relatedUser.userType, |
| relatedUser.credentialRule, newCredentials.Get(), newCredentialsCount)) |
| { |
| ChipLogError(Zcl, |
| "[clearCredential] Unable to clear credential for related user - unable to update database " |
| "[endpointId=%d,credentialType=%u" |
| ",credentialIndex=%d,modifier=%d,userIndex=%d,newCredentialsCount=%u]", |
| endpointId, to_underlying(credentialType), credentialIndex, modifier, relatedUserIndex, |
| static_cast<unsigned int>(newCredentialsCount)); |
| return Status::Failure; |
| } |
| |
| emberAfDoorLockClusterPrintln( |
| "[clearCredential] Successfully clear credential and related user " |
| "[endpointId=%d,credentialType=%u,credentialIndex=%d,modifier=%d,userIndex=%d,newCredentialsCount=%u]", |
| endpointId, to_underlying(credentialType), credentialIndex, modifier, relatedUserIndex, |
| static_cast<unsigned int>(newCredentialsCount)); |
| |
| if (sendUserChangeEvent) |
| { |
| sendRemoteLockUserChange(endpointId, credentialTypeToLockDataType(credentialType), DataOperationTypeEnum::kClear, |
| sourceNodeId, modifier, relatedUserIndex, credentialIndex); |
| } |
| |
| return Status::Success; |
| } |
| |
| Status DoorLockServer::clearCredentials(chip::EndpointId endpointId, chip::FabricIndex modifier, chip::NodeId sourceNodeId) |
| { |
| if (SupportsPIN(endpointId)) |
| { |
| auto status = clearCredentials(endpointId, modifier, sourceNodeId, CredentialTypeEnum::kPin); |
| if (Status::Success != status) |
| { |
| ChipLogError(Zcl, "[clearCredentials] Unable to clear all PIN credentials [endpointId=%d,status=%d]", endpointId, |
| to_underlying(status)); |
| return status; |
| } |
| |
| emberAfDoorLockClusterPrintln("[clearCredentials] All PIN credentials were cleared [endpointId=%d]", endpointId); |
| } |
| |
| if (SupportsRFID(endpointId)) |
| { |
| auto status = clearCredentials(endpointId, modifier, sourceNodeId, CredentialTypeEnum::kRfid); |
| if (Status::Success != status) |
| { |
| ChipLogError(Zcl, "[clearCredentials] Unable to clear all RFID credentials [endpointId=%d,status=%d]", endpointId, |
| to_underlying(status)); |
| return status; |
| } |
| emberAfDoorLockClusterPrintln("[clearCredentials] All RFID credentials were cleared [endpointId=%d]", endpointId); |
| } |
| |
| if (SupportsFingers(endpointId)) |
| { |
| auto status = clearCredentials(endpointId, modifier, sourceNodeId, CredentialTypeEnum::kFingerprint); |
| if (Status::Success != status) |
| { |
| ChipLogError(Zcl, "[clearCredentials] Unable to clear all Fingerprint credentials [endpointId=%d,status=%d]", |
| endpointId, to_underlying(status)); |
| return status; |
| } |
| |
| status = clearCredentials(endpointId, modifier, sourceNodeId, CredentialTypeEnum::kFingerVein); |
| if (Status::Success != status) |
| { |
| ChipLogError(Zcl, "[clearCredentials] Unable to clear all Finger Vein credentials [endpointId=%d,status=%d]", |
| endpointId, to_underlying(status)); |
| return status; |
| } |
| |
| emberAfDoorLockClusterPrintln("[clearCredentials] All Finger credentials were cleared [endpointId=%d]", endpointId); |
| } |
| |
| if (SupportsFace(endpointId)) |
| { |
| auto status = clearCredentials(endpointId, modifier, sourceNodeId, CredentialTypeEnum::kFace); |
| if (Status::Success != status) |
| { |
| ChipLogError(Zcl, "[clearCredentials] Unable to clear all face credentials [endpointId=%d,status=%d]", endpointId, |
| to_underlying(status)); |
| return status; |
| } |
| emberAfDoorLockClusterPrintln("[clearCredentials] All face credentials were cleared [endpointId=%d]", endpointId); |
| } |
| |
| return Status::Success; |
| } |
| |
| Status DoorLockServer::clearCredentials(chip::EndpointId endpointId, chip::FabricIndex modifier, chip::NodeId sourceNodeId, |
| CredentialTypeEnum credentialType) |
| { |
| uint16_t maxNumberOfCredentials = 0; |
| if (!getMaxNumberOfCredentials(endpointId, credentialType, maxNumberOfCredentials)) |
| { |
| ChipLogError(Zcl, |
| "[clearCredentials] Unable to get max number of credentials to clear - can't get max number of credentials " |
| "[endpointId=%d,credentialType=%u]", |
| endpointId, to_underlying(credentialType)); |
| return Status::Failure; |
| } |
| |
| Status status = Status::Success; |
| bool clearedCredential = false; |
| for (uint16_t i = 1; i < maxNumberOfCredentials; ++i) |
| { |
| Status clearStatus = clearCredential(endpointId, modifier, sourceNodeId, credentialType, i, false); |
| if (Status::Success != clearStatus) |
| { |
| ChipLogError(Zcl, |
| "[clearCredentials] Unable to clear the credential - internal error " |
| "[endpointId=%d,credentialType=%u,credentialIndex=%d,status=%d]", |
| endpointId, to_underlying(credentialType), i, to_underlying(status)); |
| if (status == Status::Success) |
| { |
| status = clearStatus; |
| } |
| } |
| else |
| { |
| clearedCredential = true; |
| } |
| } |
| |
| // Generate the event if we cleared any credentials, even if we then had errors |
| // clearing other ones, so we don't have credentials silently disappearing. |
| if (clearedCredential) |
| { |
| sendRemoteLockUserChange(endpointId, credentialTypeToLockDataType(credentialType), DataOperationTypeEnum::kClear, |
| sourceNodeId, modifier, 0xFFFE, 0xFFFE); |
| } |
| |
| return status; |
| } |
| |
| bool DoorLockServer::clearFabricFromCredentials(chip::EndpointId endpointId, CredentialTypeEnum credentialType, |
| chip::FabricIndex fabricToRemove) |
| { |
| uint16_t maxNumberOfCredentials = 0; |
| if (!getMaxNumberOfCredentials(endpointId, credentialType, maxNumberOfCredentials)) |
| { |
| ChipLogError( |
| Zcl, |
| "[clearFabricFromCredentials] Unable to get max number of credentials to clear - can't get max number of credentials " |
| "[endpointId=%d,credentialType=%u]", |
| endpointId, to_underlying(credentialType)); |
| 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; |
| maxNumberOfCredentials--; |
| } |
| |
| for (uint16_t credentialIndex = startIndex; credentialIndex <= maxNumberOfCredentials; ++credentialIndex) |
| { |
| EmberAfPluginDoorLockCredentialInfo credential; |
| if (!emberAfPluginDoorLockGetCredential(endpointId, credentialIndex, credentialType, credential)) |
| { |
| ChipLogError( |
| Zcl, |
| "[clearFabricFromCredentials] Unable to clear fabric from credential - couldn't read credential from database " |
| "[endpointId=%d,credentialType=%u,credentialIndex=%d,fabricIdToRemove=%d]", |
| endpointId, to_underlying(credentialType), credentialIndex, fabricToRemove); |
| |
| // Go on and try to clear all the remaining credentials |
| continue; |
| } |
| |
| if (DlCredentialStatus::kAvailable == credential.status || |
| (credential.createdBy != fabricToRemove && credential.lastModifiedBy != fabricToRemove)) |
| { |
| continue; |
| } |
| |
| if (credential.createdBy == fabricToRemove) |
| { |
| credential.createdBy = kUndefinedFabricIndex; |
| } |
| |
| if (credential.lastModifiedBy == fabricToRemove) |
| { |
| credential.lastModifiedBy = kUndefinedFabricIndex; |
| } |
| |
| if (!emberAfPluginDoorLockSetCredential(endpointId, credentialIndex, credential.createdBy, credential.lastModifiedBy, |
| credential.status, credential.credentialType, credential.credentialData)) |
| { |
| ChipLogError(Zcl, |
| "[clearFabricFromCredentials] Unable to clear fabric from credential - internal error " |
| "[endpointId=%d,credentialType=%u,credentialIndex=%d,fabricIdToRemove=%d]", |
| endpointId, to_underlying(credentialType), credentialIndex, fabricToRemove); |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| bool DoorLockServer::clearFabricFromCredentials(chip::EndpointId endpointId, chip::FabricIndex fabricToRemove) |
| { |
| if (SupportsRFID(endpointId)) |
| { |
| clearFabricFromCredentials(endpointId, CredentialTypeEnum::kRfid, fabricToRemove); |
| } |
| |
| if (SupportsPIN(endpointId)) |
| { |
| clearFabricFromCredentials(endpointId, CredentialTypeEnum::kPin, fabricToRemove); |
| } |
| |
| if (SupportsFingers(endpointId)) |
| { |
| clearFabricFromCredentials(endpointId, CredentialTypeEnum::kFingerprint, fabricToRemove); |
| clearFabricFromCredentials(endpointId, CredentialTypeEnum::kFingerVein, fabricToRemove); |
| } |
| |
| if (SupportsFace(endpointId)) |
| { |
| clearFabricFromCredentials(endpointId, CredentialTypeEnum::kFace, fabricToRemove); |
| } |
| |
| clearFabricFromCredentials(endpointId, CredentialTypeEnum::kProgrammingPIN, fabricToRemove); |
| |
| return true; |
| } |
| |
| bool DoorLockServer::sendRemoteLockUserChange(chip::EndpointId endpointId, LockDataTypeEnum dataType, |
| DataOperationTypeEnum operation, chip::NodeId nodeId, chip::FabricIndex fabricIndex, |
| uint16_t userIndex, uint16_t dataIndex) |
| { |
| Events::LockUserChange::Type event; |
| event.lockDataType = dataType; |
| event.dataOperationType = operation; |
| event.operationSource = OperationSourceEnum::kRemote; |
| if (0 != userIndex) |
| { |
| event.userIndex.SetNonNull(userIndex); |
| } |
| event.fabricIndex.SetNonNull(fabricIndex); |
| event.sourceNode.SetNonNull(nodeId); |
| if (0 != dataIndex) |
| { |
| event.dataIndex.SetNonNull(dataIndex); |
| } |
| |
| EventNumber eventNumber; |
| CHIP_ERROR error = app::LogEvent(event, endpointId, eventNumber); |
| if (CHIP_NO_ERROR != error) |
| { |
| ChipLogError(Zcl, "[RemoteLockUserChange] Unable to send lock user change event: %s [endpointId=%d]", error.AsString(), |
| endpointId); |
| return false; |
| } |
| emberAfDoorLockClusterPrintln("[RemoteLockUserChange] Sent lock user change event " |
| "[endpointId=%d,eventNumber=%" PRIu64 ",dataType=%u,operation=%u,nodeId=%" PRIu64 |
| ",fabricIndex=%d]", |
| endpointId, eventNumber, to_underlying(dataType), to_underlying(operation), nodeId, fabricIndex); |
| return true; |
| } |
| |
| LockDataTypeEnum DoorLockServer::credentialTypeToLockDataType(CredentialTypeEnum credentialType) |
| { |
| switch (credentialType) |
| { |
| case CredentialTypeEnum::kProgrammingPIN: |
| return LockDataTypeEnum::kProgrammingCode; |
| case CredentialTypeEnum::kPin: |
| return LockDataTypeEnum::kPin; |
| case CredentialTypeEnum::kRfid: |
| return LockDataTypeEnum::kRfid; |
| case CredentialTypeEnum::kFingerprint: |
| return LockDataTypeEnum::kFingerprint; |
| case CredentialTypeEnum::kFingerVein: |
| return LockDataTypeEnum::kFingerVein; |
| case CredentialTypeEnum::kFace: |
| return LockDataTypeEnum::kFace; |
| case CredentialTypeEnum::kUnknownEnumValue: |
| return LockDataTypeEnum::kUnspecified; |
| } |
| |
| return LockDataTypeEnum::kUnspecified; |
| } |
| |
| bool DoorLockServer::isUserScheduleRestricted(chip::EndpointId endpointId, const EmberAfPluginDoorLockUserInfo & user) |
| { |
| return false; |
| } |
| |
| void DoorLockServer::setHolidayScheduleCommandHandler(chip::app::CommandHandler * commandObj, |
| const chip::app::ConcreteCommandPath & commandPath, uint8_t holidayIndex, |
| uint32_t localStartTime, uint32_t localEndTime, |
| OperatingModeEnum operatingMode) |
| { |
| VerifyOrDie(nullptr != commandObj); |
| |
| auto endpointId = commandPath.mEndpointId; |
| if (!SupportsHolidaySchedules(endpointId)) |
| { |
| emberAfDoorLockClusterPrintln("[SetHolidaySchedule] Ignore command (not supported) [endpointId=%d]", endpointId); |
| commandObj->AddStatus(commandPath, Status::InvalidCommand); |
| return; |
| } |
| |
| emberAfDoorLockClusterPrintln("[SetHolidaySchedule] incoming command [endpointId=%d]", endpointId); |
| |
| if (!holidayIndexValid(endpointId, holidayIndex)) |
| { |
| emberAfDoorLockClusterPrintln( |
| "[SetHolidaySchedule] Unable to add schedule - index out of range [endpointId=%d,scheduleIndex=%d]", endpointId, |
| holidayIndex); |
| commandObj->AddStatus(commandPath, Status::InvalidCommand); |
| return; |
| } |
| |
| if (localEndTime <= localStartTime) |
| { |
| emberAfDoorLockClusterPrintln("[SetHolidaySchedule] Unable to add schedule - schedule ends earlier than starts" |
| "[endpointId=%d,scheduleIndex=%d,localStarTime=%" PRIu32 ",localEndTime=%" PRIu32 "]", |
| endpointId, holidayIndex, localStartTime, localEndTime); |
| commandObj->AddStatus(commandPath, Status::InvalidCommand); |
| return; |
| } |
| |
| if (operatingMode == OperatingModeEnum::kUnknownEnumValue) |
| { |
| emberAfDoorLockClusterPrintln("[SetHolidaySchedule] Unable to add schedule - operating mode is unknown" |
| "[endpointId=%d,scheduleIndex=%d,localStarTime=%" PRIu32 ",localEndTime=%" PRIu32 |
| ", operatingMode=%d]", |
| endpointId, holidayIndex, localStartTime, localEndTime, to_underlying(operatingMode)); |
| commandObj->AddStatus(commandPath, Status::InvalidCommand); |
| return; |
| } |
| |
| auto status = emberAfPluginDoorLockSetSchedule(endpointId, holidayIndex, DlScheduleStatus::kOccupied, localStartTime, |
| localEndTime, operatingMode); |
| if (DlStatus::kSuccess != status) |
| { |
| ChipLogError(Zcl, "[SetHolidaySchedule] Unable to add schedule - internal error [endpointId=%d,scheduleIndex=%d,status=%u]", |
| endpointId, holidayIndex, to_underlying(status)); |
| commandObj->AddStatus(commandPath, Status::Failure); |
| return; |
| } |
| |
| emberAfDoorLockClusterPrintln("[SetHolidaySchedule] Successfully created new schedule " |
| "[endpointId=%d,scheduleIndex=%d,localStartTime=%" PRIu32 ",endTime=%" PRIu32 |
| ",operatingMode=%d]", |
| endpointId, holidayIndex, localStartTime, localEndTime, to_underlying(operatingMode)); |
| |
| commandObj->AddStatus(commandPath, Status::Success); |
| } |
| |
| void DoorLockServer::getHolidayScheduleCommandHandler(chip::app::CommandHandler * commandObj, |
| const ConcreteCommandPath & commandPath, uint8_t holidayIndex) |
| { |
| auto endpointId = commandPath.mEndpointId; |
| if (!SupportsHolidaySchedules(endpointId)) |
| { |
| emberAfDoorLockClusterPrintln("[GetHolidaySchedule] Ignore command (not supported) [endpointId=%d,scheduleIndex=%d]", |
| endpointId, holidayIndex); |
| commandObj->AddStatus(commandPath, Status::InvalidCommand); |
| return; |
| } |
| emberAfDoorLockClusterPrintln("[GetHolidaySchedule] incoming command [endpointId=%d,scheduleIndex=%d]", endpointId, |
| holidayIndex); |
| |
| if (!holidayIndexValid(endpointId, holidayIndex)) |
| { |
| emberAfDoorLockClusterPrintln( |
| "[GetYearDaySchedule] Unable to get schedule - index out of range [endpointId=%d,scheduleIndex=%d]", endpointId, |
| holidayIndex); |
| sendHolidayScheduleResponse(commandObj, commandPath, holidayIndex, DlStatus::kInvalidField); |
| return; |
| } |
| |
| EmberAfPluginDoorLockHolidaySchedule scheduleInfo{}; |
| auto status = emberAfPluginDoorLockGetSchedule(endpointId, holidayIndex, scheduleInfo); |
| if (DlStatus::kSuccess != status) |
| { |
| sendHolidayScheduleResponse(commandObj, commandPath, holidayIndex, status); |
| return; |
| } |
| sendHolidayScheduleResponse(commandObj, commandPath, holidayIndex, DlStatus::kSuccess, scheduleInfo.localStartTime, |
| scheduleInfo.localEndTime, scheduleInfo.operatingMode); |
| } |
| |
| void DoorLockServer::clearHolidayScheduleCommandHandler(chip::app::CommandHandler * commandObj, |
| const chip::app::ConcreteCommandPath & commandPath, uint8_t holidayIndex) |
| { |
| auto endpointId = commandPath.mEndpointId; |
| if (!SupportsHolidaySchedules(endpointId)) |
| { |
| emberAfDoorLockClusterPrintln("[ClearHolidaySchedule] Ignore command (not supported) [endpointId=%d]", endpointId); |
| commandObj->AddStatus(commandPath, Status::InvalidCommand); |
| return; |
| } |
| emberAfDoorLockClusterPrintln("[ClearHolidaySchedule] incoming command [endpointId=%d,scheduleIndex=%d]", endpointId, |
| holidayIndex); |
| |
| if (!holidayIndexValid(endpointId, holidayIndex) && 0xFE != holidayIndex) |
| { |
| emberAfDoorLockClusterPrintln("[ClearHolidaySchedule] Holiday index is out of range [endpointId=%d,scheduleIndex=%d]", |
| endpointId, holidayIndex); |
| commandObj->AddStatus(commandPath, Status::InvalidCommand); |
| return; |
| } |
| |
| DlStatus clearStatus = DlStatus::kSuccess; |
| if (0xFE == holidayIndex) |
| { |
| emberAfDoorLockClusterPrintln( |
| "[ClearHolidaySchedule] Clearing all holiday schedules for a single user [endpointId=%d,scheduleIndex=%d]", endpointId, |
| holidayIndex); |
| clearStatus = clearHolidaySchedules(endpointId); |
| } |
| else |
| { |
| emberAfDoorLockClusterPrintln("[ClearHolidaySchedule] Clearing a single schedule [endpointId=%d,scheduleIndex=%d]", |
| endpointId, holidayIndex); |
| clearStatus = clearHolidaySchedule(endpointId, holidayIndex); |
| } |
| |
| if (DlStatus::kSuccess != clearStatus) |
| { |
| emberAfDoorLockClusterPrintln( |
| "[ClearHolidaySchedule] Unable to clear the user schedules - app error [endpointId=%d,scheduleIndex=%d,status=%u]", |
| endpointId, holidayIndex, to_underlying(clearStatus)); |
| commandObj->AddStatus(commandPath, Status::Failure); |
| return; |
| } |
| commandObj->AddStatus(commandPath, Status::Success); |
| } |
| |
| bool DoorLockServer::RemoteOperationEnabled(chip::EndpointId endpointId) const |
| { |
| OperatingModeEnum mode; |
| |
| return GetAttribute(endpointId, Attributes::OperatingMode::Id, Attributes::OperatingMode::Get, mode) && |
| mode != OperatingModeEnum::kPrivacy && mode != OperatingModeEnum::kNoRemoteLockUnlock; |
| } |
| |
| CHIP_ERROR DoorLockServer::sendClusterResponse(chip::app::CommandHandler * commandObj, |
| const chip::app::ConcreteCommandPath & commandPath, EmberAfStatus status) |
| { |
| VerifyOrDie(nullptr != commandObj); |
| |
| auto err = CHIP_NO_ERROR; |
| auto statusAsInteger = to_underlying(status); |
| if (statusAsInteger == to_underlying(DlStatus::kOccupied) || statusAsInteger == to_underlying(DlStatus::kDuplicate)) |
| { |
| err = commandObj->AddClusterSpecificFailure(commandPath, static_cast<chip::ClusterStatus>(status)); |
| } |
| else |
| { |
| err = commandObj->AddStatus(commandPath, ToInteractionModelStatus(status)); |
| } |
| |
| return err; |
| } |
| |
| EmberAfDoorLockEndpointContext * DoorLockServer::getContext(chip::EndpointId endpointId) |
| { |
| auto index = emberAfFindClusterServerEndpointIndex(endpointId, ::Id); |
| if (index != 0xFFFF) |
| { |
| return &mEndpointCtx[index]; |
| } |
| return nullptr; |
| } |
| |
| bool DoorLockServer::HandleRemoteLockOperation(chip::app::CommandHandler * commandObj, |
| const chip::app::ConcreteCommandPath & commandPath, LockOperationTypeEnum opType, |
| RemoteLockOpHandler opHandler, const Optional<ByteSpan> & pinCode) |
| { |
| VerifyOrDie(LockOperationTypeEnum::kLock == opType || LockOperationTypeEnum::kUnlock == opType); |
| VerifyOrDie(nullptr != opHandler); |
| |
| EndpointId endpoint = commandPath.mEndpointId; |
| OperationErrorEnum reason = OperationErrorEnum::kUnspecified; |
| Nullable<uint16_t> pinUserIdx; // Will get set to non-null if we find a user for the PIN. |
| Optional<uint16_t> pinCredIdx; // Will get set to a value if the PIN is one we know about. |
| bool success = false; |
| bool sendEvent = true; |
| |
| auto currentTime = chip::System::SystemClock().GetMonotonicTimestamp(); |
| |
| EmberAfDoorLockEndpointContext * endpointContext; |
| |
| VerifyOrExit(RemoteOperationEnabled(endpoint), reason = OperationErrorEnum::kUnspecified); |
| |
| // appclusters.pdf 5.3.4.1: |
| // When the PINCode field is provided an invalid PIN will count towards the WrongCodeEntryLimit and the |
| // UserCodeTemporaryDisableTime will be triggered if the WrongCodeEntryLimit is exceeded. The lock SHALL ignore any attempts |
| // to lock/unlock the door until the UserCodeTemporaryDisableTime expires. |
| endpointContext = getContext(endpoint); |
| VerifyOrExit(nullptr != endpointContext, ChipLogError(Zcl, "Failed to get endpoint index for cluster [endpoint=%d]", endpoint)); |
| if (endpointContext->lockoutEndTimestamp >= currentTime) |
| { |
| emberAfDoorLockClusterPrintln( |
| "Rejecting remote lock operation -- lockout is in action [endpoint=%d,lockoutEnd=%u,currentTime=%u]", endpoint, |
| static_cast<unsigned>(endpointContext->lockoutEndTimestamp.count()), static_cast<unsigned>(currentTime.count())); |
| sendEvent = false; |
| goto exit; |
| } |
| |
| if (pinCode.HasValue()) |
| { |
| // appclusters.pdf 5.3.4.1: |
| // If the PINCode field is provided, the door lock SHALL verify PINCode before granting access regardless of the value |
| // of RequirePINForRemoteOperation attribute. |
| VerifyOrExit(SupportsPIN(endpoint) && SupportsUSR(endpoint), |
| emberAfDoorLockClusterPrintln( |
| "PIN code is supplied while USR/PIN features are disabled. Exiting [endpoint=%d, lock_op=%d]", endpoint, |
| chip::to_underlying(opType))); |
| |
| // Look up the user index and credential index -- it should be used in the Lock Operation event |
| EmberAfPluginDoorLockUserInfo user; |
| uint16_t userIdx; |
| uint16_t credIdx; |
| if (findUserIndexByCredential(endpoint, CredentialTypeEnum::kPin, pinCode.Value(), userIdx, credIdx, user)) |
| { |
| pinUserIdx.SetNonNull(userIdx); |
| pinCredIdx.Emplace(credIdx); |
| } |
| else |
| { |
| emberAfDoorLockClusterPrintln("Rejecting lock operation: unknown PIN provided [endpoint=%d, lock_op=%d]", endpoint, |
| to_underlying(opType)); |
| reason = OperationErrorEnum::kInvalidCredential; |
| goto exit; |
| } |
| |
| // If the user status is OccupiedDisabled we should deny the access and send out the appropriate event |
| VerifyOrExit(user.userStatus != UserStatusEnum::kOccupiedDisabled, { |
| reason = OperationErrorEnum::kDisabledUserDenied; |
| emberAfDoorLockClusterPrintln( |
| "Unable to perform remote lock operation: user is disabled [endpoint=%d, lock_op=%d, userIndex=%d]", endpoint, |
| to_underlying(opType), userIdx); |
| }); |
| } |
| else |
| { |
| bool requirePin = false; |
| |
| // appclusters.pdf 5.3.4.1: |
| // If the RequirePINForRemoteOperation attribute is True then PINCode field SHALL be provided and the door lock SHALL |
| // NOT grant access if it is not provided. This attribute exists when COTA and PIN features are both enabled. Otherwise |
| // we assume PIN to be OK. |
| if (SupportsCredentialsOTA(endpoint) && SupportsPIN(endpoint)) |
| { |
| auto status = Attributes::RequirePINforRemoteOperation::Get(endpoint, &requirePin); |
| VerifyOrExit( |
| EMBER_ZCL_STATUS_UNSUPPORTED_ATTRIBUTE == status || EMBER_ZCL_STATUS_SUCCESS == status, |
| ChipLogError(Zcl, "Failed to read Require PIN For Remote Operation attribute, status=0x%x", to_underlying(status))); |
| } |
| // If the PIN is required but not provided we should exit |
| VerifyOrExit(!requirePin, { |
| reason = OperationErrorEnum::kInvalidCredential; |
| emberAfDoorLockClusterPrintln("Checking credentials failed: PIN is not provided when it is required"); |
| }); |
| } |
| |
| // credentials check succeeded, try to lock/unlock door |
| success = opHandler(endpoint, pinCode, reason); |
| // The app should trigger the lock state change as it may take a while before the lock actually locks/unlocks |
| exit: |
| if (!success && reason == OperationErrorEnum::kInvalidCredential) |
| { |
| HandleWrongCodeEntry(endpoint); |
| } |
| |
| // Reset the wrong code retry attempts if a valid credential is presented during lock/unlock |
| if (success && pinCode.HasValue()) |
| { |
| ResetWrongCodeEntryAttempts(endpoint); |
| } |
| |
| // Send command response |
| commandObj->AddStatus(commandPath, success ? Status::Success : Status::Failure); |
| |
| // Most of the time we want to send the lock operation event but sometimes (when the lockout is active) we don't want it. |
| if (!sendEvent) |
| { |
| return success; |
| } |
| |
| // Send LockOperation/LockOperationError event. The credential index in |
| // foundCred will be filled in if we actually have a value to fill in. |
| Nullable<List<const LockOpCredentials>> credentials{}; |
| LockOpCredentials foundCred[] = { { CredentialTypeEnum::kPin, UINT16_MAX } }; |
| |
| // appclusters.pdf 5.3.5.3, 5.3.5.4: |
| // The list of credentials used in performing the lock operation. This SHALL be null if no credentials were involved. |
| if (pinCode.HasValue() && pinCredIdx.HasValue()) |
| { |
| foundCred[0].credentialIndex = pinCredIdx.Value(); |
| credentials.SetNonNull(foundCred); |
| } |
| |
| SendLockOperationEvent(endpoint, opType, OperationSourceEnum::kRemote, reason, pinUserIdx, |
| Nullable<chip::FabricIndex>(getFabricIndex(commandObj)), Nullable<chip::NodeId>(getNodeId(commandObj)), |
| credentials, success); |
| return success; |
| } |
| |
| void DoorLockServer::SendLockOperationEvent(chip::EndpointId endpointId, LockOperationTypeEnum opType, OperationSourceEnum opSource, |
| OperationErrorEnum opErr, const Nullable<uint16_t> & userId, |
| const Nullable<chip::FabricIndex> & fabricIdx, const Nullable<chip::NodeId> & nodeId, |
| const Nullable<List<const LockOpCredentials>> & credentials, bool opSuccess) |
| { |
| |
| // TODO: if [USR] feature is not supported then credentials should be omitted (Optional.HasValue()==false)? |
| // Spec just says that it should be NULL if no PIN were provided. |
| if (opSuccess) |
| { |
| Events::LockOperation::Type event{ opType, opSource, userId, fabricIdx, nodeId, MakeOptional(credentials) }; |
| SendEvent(endpointId, event); |
| } |
| else |
| { |
| Events::LockOperationError::Type event{ opType, opSource, opErr, userId, fabricIdx, nodeId, MakeOptional(credentials) }; |
| SendEvent(endpointId, event); |
| } |
| } |
| |
| void DoorLockServer::ScheduleAutoRelock(chip::EndpointId endpointId, uint32_t timeoutSec) |
| { |
| uint32_t timeoutMs = |
| (DOOR_LOCK_MAX_LOCK_TIMEOUT_SEC >= timeoutSec) ? timeoutSec * MILLISECOND_TICKS_PER_SECOND : DOOR_LOCK_MAX_LOCK_TIMEOUT_SEC; |
| |
| CHIP_ERROR err = |
| DeviceLayer::SystemLayer().StartTimer(chip::System::Clock::Milliseconds32(timeoutMs), DoorLockOnAutoRelockCallback, |
| reinterpret_cast<void *>(static_cast<uintptr_t>(endpointId))); |
| |
| if (err != CHIP_NO_ERROR) |
| { |
| ChipLogError(Zcl, "Failed to schedule autorelock: timeout=%" PRIu32 ", status=%" CHIP_ERROR_FORMAT, timeoutSec, |
| err.Format()); |
| } |
| } |
| |
| template <typename T> |
| void DoorLockServer::SendEvent(chip::EndpointId endpointId, T & event) |
| { |
| EventNumber eventNumber; |
| auto err = chip::app::LogEvent(event, endpointId, eventNumber); |
| |
| if (CHIP_NO_ERROR != err) |
| { |
| ChipLogError(Zcl, "Failed to log event: err=0x%" PRIx32 ", event_id=0x%" PRIx32, err.AsInteger(), event.GetEventId()); |
| } |
| } |
| |
| template <typename T> |
| bool DoorLockServer::GetAttribute(chip::EndpointId endpointId, chip::AttributeId attributeId, |
| EmberAfStatus (*getFn)(chip::EndpointId endpointId, T * value), T & value) const |
| { |
| EmberAfStatus status = getFn(endpointId, &value); |
| bool success = (EMBER_ZCL_STATUS_SUCCESS == status); |
| |
| if (!success) |
| { |
| ChipLogError(Zcl, "Failed to read DoorLock attribute: attribute=0x%" PRIx32 ", status=0x%x", attributeId, |
| to_underlying(status)); |
| } |
| return success; |
| } |
| |
| template <typename T> |
| bool DoorLockServer::SetAttribute(chip::EndpointId endpointId, chip::AttributeId attributeId, |
| EmberAfStatus (*setFn)(chip::EndpointId endpointId, T value), T value) |
| { |
| EmberAfStatus status = setFn(endpointId, value); |
| bool success = (EMBER_ZCL_STATUS_SUCCESS == status); |
| |
| if (!success) |
| { |
| ChipLogError(Zcl, "Failed to write DoorLock attribute: attribute=0x%" PRIx32 ", status=0x%x", attributeId, |
| to_underlying(status)); |
| } |
| return success; |
| } |
| |
| // ============================================================================= |
| // Cluster commands callbacks |
| // ============================================================================= |
| |
| bool emberAfDoorLockClusterLockDoorCallback(chip::app::CommandHandler * commandObj, |
| const chip::app::ConcreteCommandPath & commandPath, |
| const chip::app::Clusters::DoorLock::Commands::LockDoor::DecodableType & commandData) |
| { |
| emberAfDoorLockClusterPrintln("Received command: LockDoor"); |
| DoorLockServer::Instance().HandleRemoteLockOperation(commandObj, commandPath, LockOperationTypeEnum::kLock, |
| emberAfPluginDoorLockOnDoorLockCommand, commandData.PINCode); |
| return true; |
| } |
| |
| bool emberAfDoorLockClusterUnlockDoorCallback( |
| chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, |
| const chip::app::Clusters::DoorLock::Commands::UnlockDoor::DecodableType & commandData) |
| { |
| emberAfDoorLockClusterPrintln("Received command: UnlockDoor"); |
| |
| if (DoorLockServer::Instance().HandleRemoteLockOperation(commandObj, commandPath, LockOperationTypeEnum::kUnlock, |
| emberAfPluginDoorLockOnDoorUnlockCommand, commandData.PINCode)) |
| { |
| // appclusters.pdf 5.3.3.25: |
| // The number of seconds to wait after unlocking a lock before it automatically locks again. 0=disabled. If set, unlock |
| // operations from any source will be timed. For one time unlock with timeout use the specific command. |
| uint32_t autoRelockTime = 0; |
| |
| VerifyOrReturnError(DoorLockServer::Instance().GetAutoRelockTime(commandPath.mEndpointId, autoRelockTime), true); |
| VerifyOrReturnError(0 != autoRelockTime, true); |
| DoorLockServer::Instance().ScheduleAutoRelock(commandPath.mEndpointId, autoRelockTime); |
| } |
| |
| return true; |
| } |
| |
| bool emberAfDoorLockClusterUnlockWithTimeoutCallback( |
| chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, |
| const chip::app::Clusters::DoorLock::Commands::UnlockWithTimeout::DecodableType & commandData) |
| { |
| emberAfDoorLockClusterPrintln("Received command: UnlockWithTimeout"); |
| |
| if (DoorLockServer::Instance().HandleRemoteLockOperation(commandObj, commandPath, LockOperationTypeEnum::kUnlock, |
| emberAfPluginDoorLockOnDoorUnlockCommand, commandData.PINCode)) |
| { |
| // appclusters.pdf 5.3.4.3: |
| // This command causes the lock device to unlock the door with a timeout parameter. After the time in seconds specified in |
| // the timeout field, the lock device will relock itself automatically. |
| // field: Timeout, type: uint16_t |
| auto timeout = static_cast<uint32_t>(commandData.timeout); |
| |
| VerifyOrReturnError(0 != timeout, true); |
| DoorLockServer::Instance().ScheduleAutoRelock(commandPath.mEndpointId, timeout); |
| } |
| |
| return true; |
| } |
| |
| bool emberAfDoorLockClusterSetUserCallback(chip::app::CommandHandler * commandObj, |
| const chip::app::ConcreteCommandPath & commandPath, |
| const chip::app::Clusters::DoorLock::Commands::SetUser::DecodableType & commandData) |
| { |
| DoorLockServer::Instance().setUserCommandHandler(commandObj, commandPath, commandData); |
| return true; |
| } |
| |
| bool emberAfDoorLockClusterGetUserCallback(chip::app::CommandHandler * commandObj, |
| const chip::app::ConcreteCommandPath & commandPath, |
| const chip::app::Clusters::DoorLock::Commands::GetUser::DecodableType & commandData) |
| { |
| DoorLockServer::Instance().getUserCommandHandler(commandObj, commandPath, commandData.userIndex); |
| return true; |
| } |
| |
| bool emberAfDoorLockClusterClearUserCallback(chip::app::CommandHandler * commandObj, |
| const chip::app::ConcreteCommandPath & commandPath, |
| const chip::app::Clusters::DoorLock::Commands::ClearUser::DecodableType & commandData) |
| { |
| DoorLockServer::Instance().clearUserCommandHandler(commandObj, commandPath, commandData.userIndex); |
| return true; |
| } |
| |
| bool emberAfDoorLockClusterSetCredentialCallback( |
| chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, |
| const chip::app::Clusters::DoorLock::Commands::SetCredential::DecodableType & commandData) |
| { |
| DoorLockServer::Instance().setCredentialCommandHandler(commandObj, commandPath, commandData); |
| return true; |
| } |
| |
| bool emberAfDoorLockClusterGetCredentialStatusCallback( |
| chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, |
| const chip::app::Clusters::DoorLock::Commands::GetCredentialStatus::DecodableType & commandData) |
| { |
| DoorLockServer::Instance().getCredentialStatusCommandHandler(commandObj, commandPath, commandData.credential.credentialType, |
| commandData.credential.credentialIndex); |
| return true; |
| } |
| |
| bool emberAfDoorLockClusterClearCredentialCallback( |
| chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, |
| const chip::app::Clusters::DoorLock::Commands::ClearCredential::DecodableType & commandData) |
| { |
| DoorLockServer::Instance().clearCredentialCommandHandler(commandObj, commandPath, commandData); |
| return true; |
| } |
| |
| bool emberAfDoorLockClusterSetWeekDayScheduleCallback( |
| chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, |
| const chip::app::Clusters::DoorLock::Commands::SetWeekDaySchedule::DecodableType & commandData) |
| { |
| DoorLockServer::Instance().setWeekDayScheduleCommandHandler( |
| commandObj, commandPath, commandData.weekDayIndex, commandData.userIndex, commandData.daysMask, commandData.startHour, |
| commandData.startMinute, commandData.endHour, commandData.endMinute); |
| return true; |
| } |
| |
| bool emberAfDoorLockClusterGetWeekDayScheduleCallback( |
| chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, |
| const chip::app::Clusters::DoorLock::Commands::GetWeekDaySchedule::DecodableType & commandData) |
| { |
| DoorLockServer::Instance().getWeekDayScheduleCommandHandler(commandObj, commandPath, commandData.weekDayIndex, |
| commandData.userIndex); |
| return true; |
| } |
| |
| bool emberAfDoorLockClusterClearWeekDayScheduleCallback( |
| chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, |
| const chip::app::Clusters::DoorLock::Commands::ClearWeekDaySchedule::DecodableType & commandData) |
| { |
| DoorLockServer::Instance().clearWeekDayScheduleCommandHandler(commandObj, commandPath, commandData.weekDayIndex, |
| commandData.userIndex); |
| return true; |
| } |
| |
| bool emberAfDoorLockClusterSetYearDayScheduleCallback( |
| chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, |
| const chip::app::Clusters::DoorLock::Commands::SetYearDaySchedule::DecodableType & commandData) |
| { |
| DoorLockServer::Instance().setYearDayScheduleCommandHandler(commandObj, commandPath, commandData.yearDayIndex, |
| commandData.userIndex, commandData.localStartTime, |
| commandData.localEndTime); |
| return true; |
| } |
| |
| bool emberAfDoorLockClusterGetYearDayScheduleCallback( |
| chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, |
| const chip::app::Clusters::DoorLock::Commands::GetYearDaySchedule::DecodableType & commandData) |
| { |
| DoorLockServer::Instance().getYearDayScheduleCommandHandler(commandObj, commandPath, commandData.yearDayIndex, |
| commandData.userIndex); |
| return true; |
| } |
| |
| bool emberAfDoorLockClusterClearYearDayScheduleCallback( |
| chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, |
| const chip::app::Clusters::DoorLock::Commands::ClearYearDaySchedule::DecodableType & commandData) |
| { |
| DoorLockServer::Instance().clearYearDayScheduleCommandHandler(commandObj, commandPath, commandData.yearDayIndex, |
| commandData.userIndex); |
| return true; |
| } |
| |
| bool emberAfDoorLockClusterSetHolidayScheduleCallback( |
| chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, |
| const chip::app::Clusters::DoorLock::Commands::SetHolidaySchedule::DecodableType & commandData) |
| { |
| DoorLockServer::Instance().setHolidayScheduleCommandHandler(commandObj, commandPath, commandData.holidayIndex, |
| commandData.localStartTime, commandData.localEndTime, |
| commandData.operatingMode); |
| return true; |
| } |
| |
| bool emberAfDoorLockClusterGetHolidayScheduleCallback( |
| chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, |
| const chip::app::Clusters::DoorLock::Commands::GetHolidaySchedule::DecodableType & commandData) |
| { |
| DoorLockServer::Instance().getHolidayScheduleCommandHandler(commandObj, commandPath, commandData.holidayIndex); |
| return true; |
| } |
| |
| bool emberAfDoorLockClusterClearHolidayScheduleCallback( |
| chip::app::CommandHandler * commandObj, const chip::app::ConcreteCommandPath & commandPath, |
| const chip::app::Clusters::DoorLock::Commands::ClearHolidaySchedule::DecodableType & commandData) |
| { |
| DoorLockServer::Instance().clearHolidayScheduleCommandHandler(commandObj, commandPath, commandData.holidayIndex); |
| return true; |
| } |
| |
| // ============================================================================= |
| // SDK callbacks |
| // ============================================================================= |
| |
| chip::Protocols::InteractionModel::Status |
| MatterDoorLockClusterServerPreAttributeChangedCallback(const chip::app::ConcreteAttributePath & attributePath, |
| EmberAfAttributeType attributeType, uint16_t size, uint8_t * value) |
| { |
| chip::Protocols::InteractionModel::Status res; |
| |
| switch (attributePath.mAttributeId) |
| { |
| case chip::app::Clusters::DoorLock::Attributes::Language::Id: |
| if (value[0] <= 3) |
| { |
| auto lang = chip::CharSpan(reinterpret_cast<const char *>(&value[1]), static_cast<size_t>(value[0])); |
| res = emberAfPluginDoorLockOnLanguageChange(attributePath.mEndpointId, lang); |
| } |
| else |
| { |
| res = chip::Protocols::InteractionModel::Status::InvalidValue; |
| } |
| break; |
| |
| case chip::app::Clusters::DoorLock::Attributes::AutoRelockTime::Id: |
| if (sizeof(uint32_t) == size) |
| { |
| uint32_t newRelockTime = *(reinterpret_cast<uint32_t *>(value)); |
| res = emberAfPluginDoorLockOnAutoRelockTimeChange(attributePath.mEndpointId, newRelockTime); |
| } |
| else |
| { |
| res = chip::Protocols::InteractionModel::Status::InvalidValue; |
| } |
| break; |
| |
| case chip::app::Clusters::DoorLock::Attributes::SoundVolume::Id: |
| if (sizeof(uint8_t) == size) |
| { |
| res = emberAfPluginDoorLockOnSoundVolumeChange(attributePath.mEndpointId, *value); |
| } |
| else |
| { |
| res = chip::Protocols::InteractionModel::Status::InvalidValue; |
| } |
| break; |
| |
| case chip::app::Clusters::DoorLock::Attributes::OperatingMode::Id: |
| if (sizeof(uint8_t) == size) |
| { |
| res = emberAfPluginDoorLockOnOperatingModeChange(attributePath.mEndpointId, *value); |
| } |
| else |
| { |
| res = chip::Protocols::InteractionModel::Status::InvalidValue; |
| } |
| break; |
| |
| case chip::app::Clusters::DoorLock::Attributes::EnableOneTouchLocking::Id: |
| if (sizeof(bool) == size) |
| { |
| bool enable = *reinterpret_cast<bool *>(value); |
| res = emberAfPluginDoorLockOnEnableOneTouchLockingChange(attributePath.mEndpointId, enable); |
| } |
| else |
| { |
| res = chip::Protocols::InteractionModel::Status::InvalidValue; |
| } |
| break; |
| |
| case chip::app::Clusters::DoorLock::Attributes::EnablePrivacyModeButton::Id: |
| if (sizeof(bool) == size) |
| { |
| bool enable = *reinterpret_cast<bool *>(value); |
| res = emberAfPluginDoorLockOnEnablePrivacyModeButtonChange(attributePath.mEndpointId, enable); |
| } |
| else |
| { |
| res = chip::Protocols::InteractionModel::Status::InvalidValue; |
| } |
| break; |
| |
| case chip::app::Clusters::DoorLock::Attributes::WrongCodeEntryLimit::Id: |
| if (sizeof(uint8_t) == size) |
| { |
| res = emberAfPluginDoorLockOnWrongCodeEntryLimitChange(attributePath.mEndpointId, *value); |
| } |
| else |
| { |
| res = chip::Protocols::InteractionModel::Status::InvalidValue; |
| } |
| break; |
| |
| case chip::app::Clusters::DoorLock::Attributes::UserCodeTemporaryDisableTime::Id: |
| if (sizeof(uint8_t) == size) |
| { |
| res = emberAfPluginDoorLockOnUserCodeTemporaryDisableTimeChange(attributePath.mEndpointId, *value); |
| } |
| else |
| { |
| res = chip::Protocols::InteractionModel::Status::InvalidValue; |
| } |
| break; |
| |
| default: |
| res = emberAfPluginDoorLockOnUnhandledAttributeChange(attributePath.mEndpointId, attributePath, attributeType, size, value); |
| break; |
| } |
| |
| return res; |
| } |
| |
| void emberAfPluginDoorLockServerLockoutEventHandler() {} |
| |
| void emberAfPluginDoorLockServerRelockEventHandler() {} |
| |
| void MatterDoorLockPluginServerInitCallback() |
| { |
| emberAfDoorLockClusterPrintln("Door Lock server initialized"); |
| Server::GetInstance().GetFabricTable().AddFabricDelegate(&gFabricDelegate); |
| } |
| |
| void MatterDoorLockClusterServerAttributeChangedCallback(const app::ConcreteAttributePath & attributePath) {} |
| |
| void MatterDoorLockClusterServerShutdownCallback(EndpointId endpoint) |
| { |
| emberAfDoorLockClusterPrintln("Shuting door lock server cluster on endpoint %d", endpoint); |
| |
| DeviceLayer::SystemLayer().CancelTimer(DoorLockServer::DoorLockOnAutoRelockCallback, |
| reinterpret_cast<void *>(static_cast<uintptr_t>(endpoint))); |
| } |
| |
| // ============================================================================= |
| // Timer callbacks |
| // ============================================================================= |
| |
| void DoorLockServer::DoorLockOnAutoRelockCallback(System::Layer *, void * callbackContext) |
| { |
| auto endpointId = static_cast<EndpointId>(reinterpret_cast<uintptr_t>(callbackContext)); |
| |
| Nullable<DlLockState> lockState; |
| if (Attributes::LockState::Get(endpointId, lockState) != EMBER_ZCL_STATUS_SUCCESS || lockState.IsNull() || |
| lockState.Value() != DlLockState::kLocked) |
| { |
| emberAfDoorLockClusterPrintln("Door Auto relock timer expired. %s", "Locking..."); |
| |
| DoorLockServer::Instance().SetLockState(endpointId, DlLockState::kLocked, OperationSourceEnum::kAuto); |
| emberAfPluginDoorLockOnAutoRelock(endpointId); |
| } |
| else |
| { |
| emberAfDoorLockClusterPrintln("Door Auto relock timer expired. %s", "Already locked."); |
| } |
| } |