blob: a3c75c4818ff9c2dd615d163ce8345c33c1177f0 [file] [log] [blame]
/**
*
* 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-event.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;
static constexpr uint8_t DOOR_LOCK_SCHEDULE_MAX_HOUR = 23;
static constexpr uint8_t DOOR_LOCK_SCHEDULE_MAX_MINUTE = 59;
// emberEventControlSetDelayMS() uses uint32_t for timeout in milliseconds but doesn't accept
// values more than MAX(UINT32) / 2. This is internal value. Thus, lets limit our relock timeout
// in seconds with the appropriate maximum to ensure that delay setting won't fail.
static constexpr uint32_t DOOR_LOCK_MAX_LOCK_TIMEOUT_SEC = MAX_INT32U_VALUE / (2 * 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, DlOperationSource opSource)
{
bool success = SetLockState(endpointId, newLockState);
// Remote operations are handled separately as they use more data unavailable here
VerifyOrReturnError(DlOperationSource::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) ? DlLockOperationType::kLock : DlLockOperationType::kUnlock;
SendLockOperationEvent(endpointId, opType, opSource, DlOperationError::kUnspecified, Nullable<uint16_t>(),
Nullable<chip::FabricIndex>(), Nullable<chip::NodeId>(), nullptr, 0, success);
// Schedule auto-relocking
if (success && DlLockOperationType::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, DlDoorState 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::TrackWrongCodeEntry(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;
}
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);
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, DlAlarmCode 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<DlUserStatus> & userStatus)
{
return userStatus.IsNull() || (userStatus.Value() == DlUserStatus::kOccupiedEnabled) ||
(userStatus.Value() == DlUserStatus::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.IsNull() && (userType.Value() < DlUserType::kUnrestrictedUser || userType.Value() > DlUserType::kRemoteOnlyUser))
{
emberAfDoorLockClusterPrintln(
"[SetUser] Unable to set the user: user type is out of range [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 DlDataOperationType::kAdd:
status = createUser(commandPath.mEndpointId, fabricIdx, sourceNodeId, userIndex, userName, userUniqueId, userStatus,
userType, credentialRule);
break;
case DlDataOperationType::kModify:
status = modifyUser(commandPath.mEndpointId, fabricIdx, sourceNodeId, userIndex, userName, userUniqueId, userStatus,
userType, credentialRule);
break;
case DlDataOperationType::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);
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_UNSUPPORTED_COMMAND);
return;
}
uint16_t maxNumberOfUsers = 0;
if (!userIndexValid(commandPath.mEndpointId, userIndex, maxNumberOfUsers))
{
emberAfDoorLockClusterPrintln("[GetUser] User index out of bounds [userIndex=%d,numberOfTotalUsersSupported=%d]", userIndex,
maxNumberOfUsers);
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_INVALID_COMMAND);
return;
}
CHIP_ERROR err = CHIP_NO_ERROR;
EmberAfPluginDoorLockUserInfo user;
VerifyOrExit(emberAfPluginDoorLockGetUser(commandPath.mEndpointId, userIndex, user), err = CHIP_ERROR_INTERNAL);
{
chip::app::ConcreteCommandPath path = { emberAfCurrentEndpoint(), ::Id, Commands::GetUserResponse::Id };
chip::TLV::TLVWriter * writer;
SuccessOrExit(err = commandObj->PrepareCommand(path));
VerifyOrExit((writer = commandObj->GetCommandDataIBTLVWriter()) != nullptr, err = CHIP_ERROR_INCORRECT_STATE);
SuccessOrExit(
err = writer->Put(chip::TLV::ContextTag(to_underlying(Commands::GetUserResponse::Fields::kUserIndex)), userIndex));
using ResponseFields = Commands::GetUserResponse::Fields;
// appclusters, 5.2.4.36: we should not add user-specific field if the user status is set to Available
if (DlUserStatus::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);
SuccessOrExit(err = writer->PutString(TLV::ContextTag(to_underlying(ResponseFields::kUserName)), user.userName));
if (0xFFFFFFFFU != user.userUniqueId)
{
SuccessOrExit(err = writer->Put(TLV::ContextTag(to_underlying(ResponseFields::kUserUniqueId)), user.userUniqueId));
}
SuccessOrExit(err = writer->Put(TLV::ContextTag(to_underlying(ResponseFields::kUserStatus)), user.userStatus));
SuccessOrExit(err = writer->Put(TLV::ContextTag(to_underlying(ResponseFields::kUserType)), user.userType));
SuccessOrExit(err = writer->Put(TLV::ContextTag(to_underlying(ResponseFields::kCredentialRule)), user.credentialRule));
if (!user.credentials.empty())
{
TLV::TLVType credentialsContainer;
SuccessOrExit(err = writer->StartContainer(TLV::ContextTag(to_underlying(ResponseFields::kCredentials)),
TLV::kTLVType_Array, credentialsContainer));
for (size_t i = 0; i < user.credentials.size(); ++i)
{
Structs::DlCredential::Type credential;
credential.credentialIndex = user.credentials.data()[i].CredentialIndex;
credential.credentialType = static_cast<DlCredentialType>(user.credentials.data()[i].CredentialType);
SuccessOrExit(err = credential.Encode(*writer, TLV::AnonymousTag()));
}
SuccessOrExit(err = writer->EndContainer(credentialsContainer));
}
// Append fabric IDs only if the user was created/modified by matter
if (user.creationSource == DlAssetSource::kMatterIM)
{
SuccessOrExit(err =
writer->Put(TLV::ContextTag(to_underlying(ResponseFields::kCreatorFabricIndex)), user.createdBy));
}
if (user.modificationSource == DlAssetSource::kMatterIM)
{
SuccessOrExit(err = writer->Put(TLV::ContextTag(to_underlying(ResponseFields::kLastModifiedFabricIndex)),
user.lastModifiedBy));
}
}
else
{
emberAfDoorLockClusterPrintln("[GetUser] User not found [userIndex=%d]", userIndex);
}
// appclusters, 5.2.4.36.1: We need to add next occupied user after userIndex if any.
uint16_t nextAvailableUserIndex = 0;
if (findOccupiedUserSlot(commandPath.mEndpointId, static_cast<uint16_t>(userIndex + 1), nextAvailableUserIndex))
{
SuccessOrExit(err =
writer->Put(TLV::ContextTag(to_underlying(ResponseFields::kNextUserIndex)), nextAvailableUserIndex));
}
SuccessOrExit(err = commandObj->FinishCommand());
}
exit:
if (CHIP_NO_ERROR != err)
{
ChipLogError(Zcl, "[GetUser] Command processing failed [endpointId=%d,userIndex=%d,err=\"%s\"]", commandPath.mEndpointId,
userIndex, err.AsString());
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_FAILURE);
}
}
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);
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_UNSUPPORTED_COMMAND);
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);
emberAfSendImmediateDefaultResponse(EMBER_ZCL_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);
emberAfSendImmediateDefaultResponse(EMBER_ZCL_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);
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_INVALID_COMMAND);
return;
}
if (0xFFFE != userIndex)
{
auto status = clearUser(commandPath.mEndpointId, fabricIdx, sourceNodeId, userIndex, true);
if (EMBER_ZCL_STATUS_SUCCESS != status)
{
ChipLogError(Zcl, "[ClearUser] App reported failure when resetting the user [userIndex=%d,status=0x%x]", userIndex,
status);
}
emberAfSendImmediateDefaultResponse(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 (EMBER_ZCL_STATUS_SUCCESS != status)
{
ChipLogError(Zcl, "[ClearUser] App reported failure when resetting the user [userIndex=%d,status=0x%x]", i, status);
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_FAILURE);
return;
}
}
emberAfDoorLockClusterPrintln("[ClearUser] Removed all users from storage [users=%d]", maxNumberOfUsers);
sendRemoteLockUserChange(commandPath.mEndpointId, DlLockDataType::kUserIndex, DlDataOperationType::kClear, sourceNodeId,
fabricIdx, 0xFFFE, 0xFFFE);
emberAfSendImmediateDefaultResponse(EMBER_ZCL_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);
emberAfSendImmediateDefaultResponse(EMBER_ZCL_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);
emberAfSendImmediateDefaultResponse(EMBER_ZCL_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));
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_UNSUPPORTED_COMMAND);
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; DlCredentialType::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.IsNull() && (userType.Value() < DlUserType::kUnrestrictedUser || userType.Value() > DlUserType::kRemoteOnlyUser))
{
emberAfDoorLockClusterPrintln("[SetCredential] Unable to set the credential: user type is out of range "
"[endpointId=%d,credentialIndex=%d,userType=%u]",
commandPath.mEndpointId, credentialIndex, to_underlying(userType.Value()));
sendSetCredentialResponse(commandObj, commandPath, DlStatus::kInvalidField, 0, nextAvailableCredentialSlot);
return;
}
switch (operationType)
{
case DlDataOperationType::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 DlDataOperationType::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())
{
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 DlDataOperationType::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,
DlCredentialType 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));
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_UNSUPPORTED_COMMAND);
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);
emberAfSendImmediateDefaultResponse(EMBER_ZCL_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);
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_FAILURE);
return;
}
}
sendGetCredentialResponse(commandObj, commandPath, credentialType, credentialIndex, userIndexWithCredential, &credentialInfo,
credentialExists);
}
void DoorLockServer::sendGetCredentialResponse(chip::app::CommandHandler * commandObj,
const chip::app::ConcreteCommandPath & commandPath, DlCredentialType 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)
{
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_FAILURE);
return;
}
auto sourceNodeId = getNodeId(commandObj);
if (chip::kUndefinedNodeId == sourceNodeId)
{
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_FAILURE);
return;
}
const auto & credential = commandData.credential;
if (credential.IsNull())
{
emberAfDoorLockClusterPrintln("[ClearCredential] Clearing all credentials [endpointId=%d]", commandPath.mEndpointId);
emberAfSendImmediateDefaultResponse(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)
{
emberAfSendImmediateDefaultResponse(clearCredentials(commandPath.mEndpointId, modifier, sourceNodeId, credentialType));
return;
}
emberAfSendImmediateDefaultResponse(
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<DlDaysMaskMap> & 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);
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_INVALID_COMMAND);
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);
emberAfSendImmediateDefaultResponse(EMBER_ZCL_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);
emberAfSendImmediateDefaultResponse(EMBER_ZCL_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);
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_INVALID_COMMAND);
return;
}
if (!userExists(endpointId, userIndex))
{
emberAfDoorLockClusterPrintln("[SetWeekDaySchedule] Unable to add schedule - user does not exist "
"[endpointId=%d,weekDayIndex=%d,userIndex=%d]",
endpointId, weekDayIndex, userIndex);
emberAfSendImmediateDefaultResponse(EMBER_ZCL_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());
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_INVALID_COMMAND);
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);
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_INVALID_COMMAND);
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);
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_INVALID_COMMAND);
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));
emberAfSendImmediateDefaultResponse(EMBER_ZCL_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, DlLockDataType::kWeekDaySchedule, DlDataOperationType::kAdd, sourceNodeId, fabricIdx,
userIndex, static_cast<uint16_t>(weekDayIndex));
emberAfSendImmediateDefaultResponse(EMBER_ZCL_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);
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_INVALID_COMMAND);
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);
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_INVALID_COMMAND);
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);
emberAfSendImmediateDefaultResponse(EMBER_ZCL_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);
emberAfSendImmediateDefaultResponse(EMBER_ZCL_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);
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_INVALID_COMMAND);
return;
}
if (!userExists(endpointId, userIndex))
{
emberAfDoorLockClusterPrintln("[ClearWeekDaySchedule] User does not exist [endpointId=%d,weekDayIndex=%d,userIndex=%d]",
endpointId, weekDayIndex, userIndex);
emberAfSendImmediateDefaultResponse(EMBER_ZCL_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));
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_FAILURE);
return;
}
sendRemoteLockUserChange(endpointId, DlLockDataType::kWeekDaySchedule, DlDataOperationType::kClear, sourceNodeId, fabricIdx,
userIndex, static_cast<uint16_t>(weekDayIndex));
emberAfSendImmediateDefaultResponse(EMBER_ZCL_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);
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_INVALID_COMMAND);
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);
emberAfSendImmediateDefaultResponse(EMBER_ZCL_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);
emberAfSendImmediateDefaultResponse(EMBER_ZCL_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);
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_INVALID_COMMAND);
return;
}
if (!userExists(endpointId, userIndex))
{
emberAfDoorLockClusterPrintln("[SetYearDaySchedule] Unable to add schedule - user does not exist "
"[endpointId=%d,yearDayIndex=%d,userIndex=%d]",
endpointId, yearDayIndex, userIndex);
emberAfSendImmediateDefaultResponse(EMBER_ZCL_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);
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_INVALID_COMMAND);
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));
emberAfSendImmediateDefaultResponse(EMBER_ZCL_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, DlLockDataType::kYearDaySchedule, DlDataOperationType::kAdd, sourceNodeId, fabricIdx,
userIndex, static_cast<uint16_t>(yearDayIndex));
emberAfSendImmediateDefaultResponse(EMBER_ZCL_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);
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_INVALID_COMMAND);
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);
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_INVALID_COMMAND);
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);
emberAfSendImmediateDefaultResponse(EMBER_ZCL_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);
emberAfSendImmediateDefaultResponse(EMBER_ZCL_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);
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_INVALID_COMMAND);
return;
}
if (!userExists(endpointId, userIndex))
{
emberAfDoorLockClusterPrintln("[ClearYearDaySchedule] User does not exist [endpointId=%d,yearDayIndex=%d,userIndex=%d]",
endpointId, yearDayIndex, userIndex);
emberAfSendImmediateDefaultResponse(EMBER_ZCL_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));
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_FAILURE);
return;
}
sendRemoteLockUserChange(endpointId, DlLockDataType::kYearDaySchedule, DlDataOperationType::kClear, sourceNodeId, fabricIdx,
userIndex, static_cast<uint16_t>(yearDayIndex));
emberAfSendImmediateDefaultResponse(EMBER_ZCL_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 DlUserStatus::kAvailable != user.userStatus;
}
bool DoorLockServer::credentialIndexValid(chip::EndpointId endpointId, DlCredentialType type, uint16_t credentialIndex)
{
uint16_t maxCredentials = 0;
return credentialIndexValid(endpointId, type, credentialIndex, maxCredentials);
}
bool DoorLockServer::credentialIndexValid(chip::EndpointId endpointId, DlCredentialType 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 (DlCredentialType::kProgrammingPIN == type)
{
return (0 == credentialIndex);
}
if (0 == credentialIndex || credentialIndex > maxNumberOfCredentials)
{
return false;
}
return true;
}
DlStatus DoorLockServer::credentialLengthWithinRange(chip::EndpointId endpointId, DlCredentialType type,
const chip::ByteSpan & credentialData)
{
bool statusMin = true, statusMax = true;
uint8_t minLen, maxLen;
switch (type)
{
case DlCredentialType::kProgrammingPIN:
case DlCredentialType::kPin:
statusMin = GetAttribute(endpointId, Attributes::MinPINCodeLength::Id, Attributes::MinPINCodeLength::Get, minLen);
statusMax = GetAttribute(endpointId, Attributes::MaxPINCodeLength::Id, Attributes::MaxPINCodeLength::Get, maxLen);
break;
case DlCredentialType::kRfid:
statusMin = GetAttribute(endpointId, Attributes::MinRFIDCodeLength::Id, Attributes::MinRFIDCodeLength::Get, minLen);
statusMax = GetAttribute(endpointId, Attributes::MaxRFIDCodeLength::Id, Attributes::MaxRFIDCodeLength::Get, 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, DlCredentialType credentialType,
uint16_t & maxNumberOfCredentials)
{
maxNumberOfCredentials = 0;
bool status = false;
switch (credentialType)
{
case DlCredentialType::kProgrammingPIN:
maxNumberOfCredentials = 1;
return true;
case DlCredentialType::kPin:
status = GetNumberOfPINCredentialsSupported(endpointId, maxNumberOfCredentials);
break;
case DlCredentialType::kRfid:
status = GetNumberOfRFIDCredentialsSupported(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 (DlUserStatus::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 (DlUserStatus::kAvailable == user.userStatus)
{
userIndex = i;
return true;
}
}
return false;
}
bool DoorLockServer::findOccupiedCredentialSlot(chip::EndpointId endpointId, DlCredentialType 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 (DlCredentialType::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, DlCredentialType 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 (DlCredentialType::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, DlCredentialType 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);
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_FAILURE);
return false;
}
// Go through occupied users only
if (DlUserStatus::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 == to_underlying(credentialType))
{
userIndex = i;
return true;
}
}
}
return false;
}
bool DoorLockServer::findUserIndexByCredential(chip::EndpointId endpointId, DlCredentialType 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);
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_FAILURE);
return false;
}
// Go through occupied users only
if (DlUserStatus::kAvailable == user.userStatus)
{
continue;
}
for (const auto & credential : user.credentials)
{
if (credential.CredentialType != to_underlying(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));
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_FAILURE);
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));
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_FAILURE);
return false;
}
if (credentialInfo.credentialData.data_equal(credentialData))
{
userIndex = i;
credentialIndex = i;
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<DlUserStatus> & userStatus,
const Nullable<DlUserType> & userType, const Nullable<DlCredentialRule> & credentialRule,
const Nullable<DlCredential> & 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 (DlUserStatus::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() ? DlUserStatus::kOccupiedEnabled : userStatus.Value();
auto newUserType = userType.IsNull() ? DlUserType::kUnrestrictedUser : userType.Value();
auto newCredentialRule = credentialRule.IsNull() ? DlCredentialRule::kSingle : credentialRule.Value();
const DlCredential * 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, DlLockDataType::kUserIndex, DlDataOperationType::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<DlUserStatus> & userStatus,
const Nullable<DlUserType> & userType, const Nullable<DlCredentialRule> & 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 (DlUserStatus::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, DlLockDataType::kUserIndex, DlDataOperationType::kModify, sourceNodeId,
modifierFabricIndex, userIndex, userIndex);
return EMBER_ZCL_STATUS_SUCCESS;
}
EmberAfStatus DoorLockServer::clearUser(chip::EndpointId endpointId, chip::FabricIndex modifierFabricId, chip::NodeId sourceNodeId,
uint16_t userIndex, bool sendUserChangeEvent)
{
EmberAfPluginDoorLockUserInfo user;
if (!emberAfPluginDoorLockGetUser(endpointId, userIndex, user))
{
return EMBER_ZCL_STATUS_FAILURE;
}
return clearUser(endpointId, modifierFabricId, sourceNodeId, userIndex, user, sendUserChangeEvent);
}
EmberAfStatus 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, credential.CredentialType, credential.CredentialIndex);
if (!emberAfPluginDoorLockSetCredential(endpointId, credential.CredentialIndex, kUndefinedFabricIndex,
kUndefinedFabricIndex, DlCredentialStatus::kAvailable,
static_cast<DlCredentialType>(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, credential.CredentialType);
return EMBER_ZCL_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);
}
// Remove the user entry
if (!emberAfPluginDoorLockSetUser(endpointId, userIndex, kUndefinedFabricIndex, kUndefinedFabricIndex, chip::CharSpan(""), 0,
DlUserStatus::kAvailable, DlUserType::kUnrestrictedUser, DlCredentialRule::kSingle, nullptr,
0))
{
return EMBER_ZCL_STATUS_FAILURE;
}
if (sendUserChangeEvent)
{
sendRemoteLockUserChange(endpointId, DlLockDataType::kUserIndex, DlDataOperationType::kClear, sourceNodeId,
modifierFabricId, userIndex, userIndex);
}
return EMBER_ZCL_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 (DlUserStatus::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<DlUserStatus> & userStatus,
const Nullable<DlUserType> & userType, const DlCredential & 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<DlCredentialRule>(), Nullable<DlCredential>(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, static_cast<DlCredentialType>(credential.CredentialType),
credentialData))
{
emberAfDoorLockClusterPrintln("[SetCredential] Unable to set the credential: app error "
"[endpointId=%d,credentialIndex=%d,credentialType=%u,dataLength=%u]",
endpointId, credential.CredentialIndex, 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, 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 DlCredential & 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 == DlUserStatus::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, static_cast<DlCredentialType>(credential.CredentialType),
credentialData))
{
emberAfDoorLockClusterPrintln("[SetCredential] Unable to set the credential: app error "
"[endpointId=%d,credentialIndex=%d,credentialType=%u,dataLength=%u]",
endpointId, credential.CredentialIndex, 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 DlCredential & 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: user should not be already associated with given credentialIndex
if (user.credentials.data()[i].CredentialIndex == credential.CredentialIndex)
{
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, 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, 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<DlCredential> 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(DlCredential) * 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, 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, 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 DlCredential & 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<DlCredential> 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()), credential.CredentialType,
credential.CredentialIndex);
return DlStatus::kFailure;
}
memcpy(newCredentials.Get(), user.credentials.data(), sizeof(DlCredential) * 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, 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, 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, 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, DlCredentialType credentialType,
const EmberAfPluginDoorLockCredentialInfo & existingCredential,
const chip::ByteSpan & credentialData, Nullable<uint16_t> userIndex,
const Nullable<DlUserStatus> & userStatus, Nullable<DlUserType> 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() && DlUserType::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;
}
DlCredential credential{ to_underlying(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
status = createNewCredentialAndAddItToUser(endpointId, creatorFabricIdx, userIndex.Value(), credential, credentialData);
}
if (DlStatus::kSuccess == status)
{
sendRemoteLockUserChange(endpointId, credentialTypeToLockDataType(credentialType), DlDataOperationType::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, DlCredentialType credentialType,
const EmberAfPluginDoorLockCredentialInfo & existingCredential,
const chip::ByteSpan & credentialData)
{
if (DlCredentialType::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, DlCredentialType::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), DlDataOperationType::kModify, sourceNodeId,
modifierFabricIndex, relatedUserIndex, credentialIndex);
return DlStatus::kSuccess;
}
DlStatus DoorLockServer::modifyCredential(chip::EndpointId endpointId, chip::FabricIndex modifierFabricIndex,
chip::NodeId sourceNodeId, uint16_t credentialIndex, DlCredentialType credentialType,
const EmberAfPluginDoorLockCredentialInfo & existingCredential,
const chip::ByteSpan & credentialData, uint16_t userIndex,
const Nullable<DlUserStatus> & userStatus, Nullable<DlUserType> userType)
{
// appclusters, 5.2.4.40: when modifying a credential, userStatus and userType shall both be NULL.
if (!userStatus.IsNull() || (!userType.IsNull() && DlUserType::kProgrammingUser != userType.Value()))
{
emberAfDoorLockClusterPrintln("[SetCredential] Unable to modify the credential: invalid arguments "
"[endpointId=%d,credentialIndex=%d,credentialType=%u]",
endpointId, credentialIndex, to_underlying(credentialType));
return DlStatus::kInvalidField;
}
DlCredential credential{ to_underlying(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), DlDataOperationType::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, DlCredentialType type)
{
switch (type)
{
case DlCredentialType::kProgrammingPIN:
case DlCredentialType::kPin:
return SupportsPIN(endpointId);
case DlCredentialType::kRfid:
return SupportsRFID(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,
DlDaysMaskMap(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, DlDaysMaskMap 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<DlDaysMaskMap>>(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, DlOperatingMode::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,
DlOperatingMode 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<DlOperatingMode>(operatingMode);
}
commandObj->AddResponse(commandPath, response);
}
EmberAfStatus DoorLockServer::clearCredential(chip::EndpointId endpointId, chip::FabricIndex modifier, chip::NodeId sourceNodeId,
DlCredentialType credentialType, uint16_t credentialIndex, bool sendUserChangeEvent)
{
if (DlCredentialType::kProgrammingPIN == credentialType)
{
emberAfDoorLockClusterPrintln("[clearCredential] Cannot clear programming PIN credentials "
"[endpointId=%d,credentialType=%u,credentialIndex=%d,modifier=%d]",
endpointId, to_underlying(credentialType), credentialIndex, modifier);
return EMBER_ZCL_STATUS_INVALID_COMMAND;
}
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 EMBER_ZCL_STATUS_INVALID_COMMAND;
}
// 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 EMBER_ZCL_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 EMBER_ZCL_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 EMBER_ZCL_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 EMBER_ZCL_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 EMBER_ZCL_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 (EMBER_ZCL_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, clearStatus);
return EMBER_ZCL_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 EMBER_ZCL_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 EMBER_ZCL_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 EMBER_ZCL_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 EMBER_ZCL_STATUS_FAILURE;
}
chip::Platform::ScopedMemoryBuffer<DlCredential> 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 EMBER_ZCL_STATUS_FAILURE;
}
size_t newCredentialsCount = 0;
for (const auto & c : relatedUser.credentials)
{
if (static_cast<DlCredentialType>(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 EMBER_ZCL_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), DlDataOperationType::kClear,
sourceNodeId, modifier, relatedUserIndex, credentialIndex);
}
return EMBER_ZCL_STATUS_SUCCESS;
}
EmberAfStatus DoorLockServer::clearCredentials(chip::EndpointId endpointId, chip::FabricIndex modifier, chip::NodeId sourceNodeId)
{
if (SupportsPIN(endpointId))
{
auto status = clearCredentials(endpointId, modifier, sourceNodeId, DlCredentialType::kPin);
if (EMBER_ZCL_STATUS_SUCCESS != status)
{
ChipLogError(Zcl, "[clearCredentials] Unable to clear all PIN credentials [endpointId=%d,status=%d]", endpointId,
status);
return status;
}
emberAfDoorLockClusterPrintln("[clearCredentials] All PIN credentials were cleared [endpointId=%d]", endpointId);
}
if (SupportsRFID(endpointId))
{
auto status = clearCredentials(endpointId, modifier, sourceNodeId, DlCredentialType::kRfid);
if (EMBER_ZCL_STATUS_SUCCESS != status)
{
ChipLogError(Zcl, "[clearCredentials] Unable to clear all RFID credentials [endpointId=%d,status=%d]", endpointId,
status);
return status;
}
emberAfDoorLockClusterPrintln("[clearCredentials] All RFID credentials were cleared [endpointId=%d]", endpointId);
}
if (SupportsFingers(endpointId))
{
auto status = clearCredentials(endpointId, modifier, sourceNodeId, DlCredentialType::kFingerprint);
if (EMBER_ZCL_STATUS_SUCCESS != status)
{
ChipLogError(Zcl, "[clearCredentials] Unable to clear all Fingerprint credentials [endpointId=%d,status=%d]",
endpointId, status);
return status;
}
status = clearCredentials(endpointId, modifier, sourceNodeId, DlCredentialType::kFingerVein);
if (EMBER_ZCL_STATUS_SUCCESS != status)
{
ChipLogError(Zcl, "[clearCredentials] Unable to clear all Finger Vein credentials [endpointId=%d,status=%d]",
endpointId, status);
return status;
}
emberAfDoorLockClusterPrintln("[clearCredentials] All Finger credentials were cleared [endpointId=%d]", endpointId);
}
if (SupportsFace(endpointId))
{
auto status = clearCredentials(endpointId, modifier, sourceNodeId, DlCredentialType::kFace);
if (EMBER_ZCL_STATUS_SUCCESS != status)
{
ChipLogError(Zcl, "[clearCredentials] Unable to clear all face credentials [endpointId=%d,status=%d]", endpointId,
status);
return status;
}
emberAfDoorLockClusterPrintln("[clearCredentials] All face credentials were cleared [endpointId=%d]", endpointId);
}
return EMBER_ZCL_STATUS_SUCCESS;
}
EmberAfStatus DoorLockServer::clearCredentials(chip::EndpointId endpointId, chip::FabricIndex modifier, chip::NodeId sourceNodeId,
DlCredentialType 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 EMBER_ZCL_STATUS_FAILURE;
}
for (uint16_t i = 1; i < maxNumberOfCredentials; ++i)
{
auto status = clearCredential(endpointId, modifier, sourceNodeId, credentialType, i, false);
if (EMBER_ZCL_STATUS_SUCCESS != status)
{
ChipLogError(Zcl,
"[clearCredentials] Unable to clear the credential - internal error "
"[endpointId=%d,credentialType=%u,credentialIndex=%d,status=%d]",
endpointId, to_underlying(credentialType), i, status);
return status;
}
}
sendRemoteLockUserChange(endpointId, credentialTypeToLockDataType(credentialType), DlDataOperationType::kClear, sourceNodeId,
modifier, 0xFFFE, 0xFFFE);
return EMBER_ZCL_STATUS_SUCCESS;
}
bool DoorLockServer::clearFabricFromCredentials(chip::EndpointId endpointId, DlCredentialType 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 (DlCredentialType::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, DlCredentialType::kRfid, fabricToRemove);
}
if (SupportsPIN(endpointId))
{
clearFabricFromCredentials(endpointId, DlCredentialType::kPin, fabricToRemove);
}
if (SupportsFingers(endpointId))
{
clearFabricFromCredentials(endpointId, DlCredentialType::kFingerprint, fabricToRemove);
clearFabricFromCredentials(endpointId, DlCredentialType::kFingerVein, fabricToRemove);
}
if (SupportsFace(endpointId))
{
clearFabricFromCredentials(endpointId, DlCredentialType::kFace, fabricToRemove);
}
clearFabricFromCredentials(endpointId, DlCredentialType::kProgrammingPIN, fabricToRemove);
return true;
}
bool DoorLockServer::sendRemoteLockUserChange(chip::EndpointId endpointId, DlLockDataType dataType, DlDataOperationType 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 = DlOperationSource::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;
}
DlLockDataType DoorLockServer::credentialTypeToLockDataType(DlCredentialType credentialType)
{
switch (credentialType)
{
case DlCredentialType::kProgrammingPIN:
return DlLockDataType::kProgrammingCode;
case DlCredentialType::kPin:
return DlLockDataType::kPin;
case DlCredentialType::kRfid:
return DlLockDataType::kRfid;
case DlCredentialType::kFingerprint:
return DlLockDataType::kFingerprint;
case DlCredentialType::kFingerVein:
return DlLockDataType::kFingerprint;
case DlCredentialType::kFace:
// So far there's no distinct data type for face credentials
return DlLockDataType::kUnspecified;
case DlCredentialType::kUnknownEnumValue:
return DlLockDataType::kUnspecified;
}
return DlLockDataType::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, DlOperatingMode operatingMode)
{
VerifyOrDie(nullptr != commandObj);
auto endpointId = commandPath.mEndpointId;
if (!SupportsHolidaySchedules(endpointId))
{
emberAfDoorLockClusterPrintln("[SetHolidaySchedule] Ignore command (not supported) [endpointId=%d]", endpointId);
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_INVALID_COMMAND);
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);
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_INVALID_COMMAND);
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);
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_INVALID_COMMAND);
return;
}
if (operatingMode > DlOperatingMode::kPassage || operatingMode < DlOperatingMode::kNormal)
{
emberAfDoorLockClusterPrintln("[SetHolidaySchedule] Unable to add schedule - operating mode is out of range"
"[endpointId=%d,scheduleIndex=%d,localStarTime=%" PRIu32 ",localEndTime=%" PRIu32
", operatingMode=%d]",
endpointId, holidayIndex, localStartTime, localEndTime, to_underlying(operatingMode));
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_INVALID_COMMAND);
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));
emberAfSendImmediateDefaultResponse(EMBER_ZCL_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));
emberAfSendImmediateDefaultResponse(EMBER_ZCL_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);
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_INVALID_COMMAND);
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);
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_INVALID_COMMAND);
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);
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_INVALID_COMMAND);
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));
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_FAILURE);
return;
}
emberAfSendImmediateDefaultResponse(EMBER_ZCL_STATUS_SUCCESS);
}
bool DoorLockServer::RemoteOperationEnabled(chip::EndpointId endpointId) const
{
DlOperatingMode mode;
return GetAttribute(endpointId, Attributes::OperatingMode::Id, Attributes::OperatingMode::Get, mode) &&
mode != DlOperatingMode::kPrivacy && mode != DlOperatingMode::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, DlLockOperationType opType,
RemoteLockOpHandler opHandler, const Optional<ByteSpan> & pinCode)
{
VerifyOrDie(DlLockOperationType::kLock == opType || DlLockOperationType::kUnlock == opType);
VerifyOrDie(nullptr != opHandler);
EndpointId endpoint = commandPath.mEndpointId;
DlOperationError reason = DlOperationError::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 = DlOperationError::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, DlCredentialType::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 = DlOperationError::kInvalidCredential;
goto exit;
}
// If the user status is OccupiedDisabled we should deny the access and send out the appropriate event
VerifyOrExit(user.userStatus != DlUserStatus::kOccupiedDisabled, {
reason = DlOperationError::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 = DlOperationError::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 == DlOperationError::kInvalidCredential)
{
TrackWrongCodeEntry(endpoint);
}
// Send command response
emberAfSendImmediateDefaultResponse(success ? EMBER_ZCL_STATUS_SUCCESS : EMBER_ZCL_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.
LockOpCredentials foundCred[] = { { DlCredentialType::kPin, UINT16_MAX } };
LockOpCredentials * credList = nullptr;
size_t credListSize = 0;
// 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();
credList = foundCred;
credListSize = 1;
}
SendLockOperationEvent(endpoint, opType, DlOperationSource::kRemote, reason, pinUserIdx,
Nullable<chip::FabricIndex>(getFabricIndex(commandObj)), Nullable<chip::NodeId>(getNodeId(commandObj)),
credList, credListSize, success);
return success;
}
void DoorLockServer::SendLockOperationEvent(chip::EndpointId endpointId, DlLockOperationType opType, DlOperationSource opSource,
DlOperationError opErr, const Nullable<uint16_t> & userId,
const Nullable<chip::FabricIndex> & fabricIdx, const Nullable<chip::NodeId> & nodeId,
LockOpCredentials * credList, size_t credListSize, bool opSuccess)
{
Nullable<List<const Structs::DlCredential::Type>> credentials{};
// 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 (nullptr == credList || 0 == credListSize)
{
credentials.SetNull();
}
else
{
credentials.SetNonNull(List<const Structs::DlCredential::Type>(credList, credListSize));
}
// 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)
{
emberEventControlSetInactive(&AutolockEvent);
AutolockEvent.endpoint = endpointId;
AutolockEvent.callback = DoorLockOnAutoRelockCallback;
uint32_t timeoutMs =
(DOOR_LOCK_MAX_LOCK_TIMEOUT_SEC >= timeoutSec) ? timeoutSec * MILLISECOND_TICKS_PER_SECOND : DOOR_LOCK_MAX_LOCK_TIMEOUT_SEC;
auto err = emberEventControlSetDelayMS(&AutolockEvent, timeoutMs);
if (EMBER_SUCCESS != err)
{
ChipLogError(Zcl, "Failed to schedule autorelock: timeout=%" PRIu32 ", status=0x%x", timeoutSec, err);
}
}
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, DlLockOperationType::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, DlLockOperationType::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, DlLockOperationType::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) {}
// =============================================================================
// Timer callbacks
// =============================================================================
void DoorLockServer::DoorLockOnAutoRelockCallback(chip::EndpointId endpointId)
{
emberAfDoorLockClusterPrintln("Door Auto relock timer expired. Locking...");
emberEventControlSetInactive(&DoorLockServer::Instance().AutolockEvent);
DoorLockServer::Instance().SetLockState(endpointId, DlLockState::kLocked, DlOperationSource::kAuto);
emberAfPluginDoorLockOnAutoRelock(endpointId);
}