From 52979e8f23ca38a0126f1cbd4e984a09912cc1fe Mon Sep 17 00:00:00 2001 From: Arkadiusz Balys Date: Mon, 20 May 2024 14:29:06 +0200 Subject: [PATCH] samples: matter: Introduce Timed Schedule Access in Lock - Regenerated Matter Lock Zap to include Timed Schedule Access feature - Added Schedules for WeekDay, YearDay and Holiday to the credentials manager according to Matter Specification. - Implemented callbacks for the schedule feature required by door-lock-server. - Moved credentials manager users, schedules and credentials implementations to separate files due to a high number of lines in the credentials_manager.cpp file. - Added a guide to Matter Lock sample on how to use this feature. - Updated release notes. Signed-off-by: Arkadiusz Balys --- .../releases/release-notes-changelog.rst | 1 + samples/matter/lock/Kconfig | 32 ++ samples/matter/lock/README.rst | 147 ++++++ samples/matter/lock/src/bolt_lock_manager.cpp | 43 ++ samples/matter/lock/src/bolt_lock_manager.h | 15 + .../credentials/credentials_data_types.cpp | 158 +++++++ .../src/credentials/credentials_data_types.h | 92 +++- .../src/credentials/credentials_manager.cpp | 437 ++---------------- .../src/credentials/credentials_manager.h | 53 ++- .../credentials_manager_credentials.cpp | 282 +++++++++++ .../credentials_manager_schedules.cpp | 386 ++++++++++++++++ .../credentials/credentials_manager_users.cpp | 182 ++++++++ .../src/credentials/credentials_storage.cpp | 33 +- .../src/credentials/credentials_storage.h | 41 +- samples/matter/lock/src/lock.zap | 166 ++++++- .../zap-generated/IMClusterCommandHandler.cpp | 81 ++++ .../matter/lock/src/zap-generated/access.h | 27 ++ .../lock/src/zap-generated/endpoint_config.h | 34 +- samples/matter/lock/src/zcl_callbacks.cpp | 55 +++ 19 files changed, 1825 insertions(+), 440 deletions(-) create mode 100644 samples/matter/lock/src/credentials/credentials_manager_credentials.cpp create mode 100644 samples/matter/lock/src/credentials/credentials_manager_schedules.cpp create mode 100644 samples/matter/lock/src/credentials/credentials_manager_users.cpp diff --git a/doc/nrf/releases_and_maturity/releases/release-notes-changelog.rst b/doc/nrf/releases_and_maturity/releases/release-notes-changelog.rst index f50bcde979d0..632083ed60fb 100644 --- a/doc/nrf/releases_and_maturity/releases/release-notes-changelog.rst +++ b/doc/nrf/releases_and_maturity/releases/release-notes-changelog.rst @@ -587,6 +587,7 @@ Matter samples * Added support for emulation of the nRF7001 Wi-Fi companion IC on the nRF7002 DK. * Added a door lock credentials manager module. The module is used to implement support for refined handling and persistent storage of PIN codes. + * Added the ::ref::`matter_lock_scheduled_timed_access` feature. Multicore samples ----------------- diff --git a/samples/matter/lock/Kconfig b/samples/matter/lock/Kconfig index b731b079a74e..7f724f23b70d 100644 --- a/samples/matter/lock/Kconfig +++ b/samples/matter/lock/Kconfig @@ -22,6 +22,29 @@ config LOCK_MAX_CREDENTIAL_LENGTH int "Maximum length of the single credential supported by the lock" default 10 +config LOCK_SCHEDULES + bool "Support for WeekDay, YearDay and Holiday schedules in lock" + help + This option adds support for an optional Timed Schedule Access Lock feature. + You can use Week Day, Year Day and Holiday schedules to restrict access for a + specific user for a specific amount of time. For example, use it to + allow access to a user only for three days in week. + +config LOCK_MAX_WEEKDAY_SCHEDULES_PER_USER + int "Maximum number of WeekDay schedules per one user supported by the lock" + depends on LOCK_SCHEDULES + default 5 + +config LOCK_MAX_YEARDAY_SCHEDULES_PER_USER + int "Maximum number of YearDay schedules per one user supported by the lock" + depends on LOCK_SCHEDULES + default 5 + +config LOCK_MAX_HOLIDAY_SCHEDULES + int "Maximum number of Holiday schedules supported by the lock" + depends on LOCK_SCHEDULES + default 5 + config LOCK_ENABLE_DEBUG bool "Enable debug features to print users and credentials" depends on SHELL @@ -32,6 +55,15 @@ config LOCK_ENABLE_DEBUG if LOCK_ENABLE_DEBUG +config LOCK_PRINT_STORAGE_STATUS + bool "Print storage status after each store call" + help + Debug feature to print the debug-level log that contains information of the entry storing + to persistent storage and how many bytes left to store the new entry. It can be used to verify + whether credentials, schedules or users entry are written properly to persistent storage. + This option should be disabled in production firmware + version. + # This should be deterined based on the maximum possible command length that depends on the LOCK_MAX_CREDENTIAL_LENGTH config SHELL_CMD_BUFF_SIZE default 300 diff --git a/samples/matter/lock/README.rst b/samples/matter/lock/README.rst index 71ce02b37c07..865c294d4d1b 100644 --- a/samples/matter/lock/README.rst +++ b/samples/matter/lock/README.rst @@ -481,6 +481,48 @@ Upgrading the device firmware To upgrade the device firmware, complete the steps listed for the selected method in the :doc:`matter:nrfconnect_examples_software_update` tutorial of the Matter documentation. +.. _matter_lock_scheduled_timed_access: + +Schedule timed-access +===================== + +Schedule timed-access feature is an optional Matter lock feature that can be applicable to all available lock users. +You can use the timed schedule access feature to allow guests users of the home to access the lock at the specific scheduled times. +To use this feature you need create at least one user on the lock device, and assign credentials. +To learn how to do it, see :ref:`matter_lock_sample_remote_access_with_pin`. + +You can use the following types of the schedule timed-access: + + - ``Week-day`` to restrict access to a specified time window on certain days of the week for a specific user. + This schedule is repeated each week. + When the schedule is cleared the unrestricted access is granted to the user. + + - ``Year-day`` to restrict access to a specified data end time window. + This schedule is granted only once not repeated anymore. + When the schedule is cleared the unrestricted access is granted to the user. + + - ``Holiday`` to setup a holiday operating mode in the lock device. + You can choose one of the operating mode option from the following list: + + - ``Normal`` - the lock operates normally. + - ``Vacation`` - Only remote operations are enabled. + - ``Privacy`` - Possible only if lock is in the locked state. + All external interactions with the lock are disabled. + Manual unlocking changes the mode to ``Normal``. + - ``NoRemoteLockUnlock`` - Disables all remote operations. + - ``Passage`` - Allow using the lock without providing a PIN. + This option can be used for example for employees during working hours. + +To enable the Schedule timed-access feature set the :kconfig:option:`CONFIG_LOCK_SCHEDULES` kconfig option to ``y``. + +Use the following kconfig options to modify maximum number of the specific schedule type: + + - :kconfig:option:`CONFIG_LOCK_MAX_WEEKDAY_SCHEDULES_PER_USER` to define the maximum number of week-day schedules per one user. + - :kconfig:option:`CONFIG_LOCK_MAX_YEARDAY_SCHEDULES_PER_USER` to define the maximum number of year-day schedules per one user. + - :kconfig:option:`CONFIG_LOCK_MAX_HOLIDAY_SCHEDULES` to define the maximum number of holiday schedules. + +All schedule timed-access entries are saved to non-volatile memory and loaded automatically after device reboot. + .. _matter_lock_sample_remote_access_with_pin: Testing remote access with PIN code credential @@ -536,6 +578,111 @@ After building the sample and programming it to your development kit, complete t .. note:: Accessing the door lock remotely without a valid PIN code credential will fail. +.. _matter_lock_sample_schedule_testing: + +Testing schedule timed-access +============================= + +.. note:: + You can test :ref:`matter_lock_scheduled_timed_access` using any Matter compatible controller. + The following steps use the CHIP Tool controller as an example. + +After building the sample with the feature enabled and programming it to your development kit, complete the following steps to test schedule timed-access: + +1. |connect_kit| +#. |connect_terminal_ANSI| +#. Wait until the device boots. +#. Commission an accessory with node ID equal to 10 to the Matter network by following the steps described in the `Commissioning the device`_ section. +#. Add the example ``Home`` door lock user: + + .. code-block:: console + + ./chip-tool doorlock set-user 0 2 Home 123 1 0 0 10 1 --timedInteractionTimeoutMs 5000 + + This command creates a ``Home`` user with a unique ID of ``123`` and an index of ``2``. + The new user's status is set to ``1``, and both its type and credential rule to ``0``. + The user is assigned to the door lock cluster residing on endpoint ``1`` of the node with ID ``10``. + +#. Set the example ``week-day`` schedule using the following command: + + ./chip-tool doorlock set-week-day-schedule WeekDayIndex UserIndex DaysMask StartHour StartMinute EndHour EndMinute destination-id endpoint-id + + Where: + + * ``WeekDayIndex`` is an index of the new schedule, starting from ``1``. + The maximum value is defined by the :kconfig:option:`CONFIG_LOCK_MAX_WEEKDAY_SCHEDULES_PER_USER` kconfig option. + * ``UserIndex`` is the user index defined in the previous step, starting from ``1``. + * ``DaysMask`` is a bitmap of numbers of the week days starting from ``0`` as a Sunday and finishing at ``6`` as a Saturday. + For example, to assign this schedule to Tuesday, Thursday and Saturday you need to provide ``84`` because it is equivalent to the ``01010100`` bitmap. + * ``StartHour`` is the starting hour for the week day schedule. + * ``StartMinute`` is the starting minute for the week day schedule. + * ``EndHour`` is the ending hour for the week day schedule. + * ``EndMinute`` is the ending hour for the week day schedule. + * ``node-id`` is the device node ID. + * ``endpoint-id`` is the Matter door lock endpoint, in this sample assigned to ``1``. + + For example, use the following command to set a ``week-day`` schedule for Tuesday, Thursday and Saturday to start at 7:30 AM and finish at 10:30 AM dedicated for user with ID ``1``: + + .. code-block:: console + + ./chip-tool doorlock set-week-day-schedule 1 1 84 7 30 10 30 1 1 + + +#. Set the example ``year-day`` schedule using the following command: + + ./chip-tool doorlock set-year-day-schedule YearDayIndex UserIndex LocalStartTime LocalEndTime node-id endpoint-id + + Where: + + * ``YearDayIndex`` is an index of the new schedule, starting from ``1``. + The maximum value is defined by the :kconfig:option:`CONFIG_LOCK_MAX_YEARDAY_SCHEDULES_PER_USER` kconfig option. + * ``UserIndex`` is the user index defined in the previous step, starting from ``1``. + * ``LocalStartTime`` is the starting time in Epoch Time. + * ``LocalEndTime`` is the ending time in Epoch Time. + * ``node-id`` is the device node ID. + * ``endpoint-id`` is the Matter door lock endpoint, in this sample assigned to ``1``. + + Both ``LocalStartTime`` and ``LocalEndTime`` are in seconds with the local time offset based on the local timezone and DST offset on the day represented by the value. + + For example: + + .. code-block:: console + + ./chip-tool doorlock set-week-day-schedule 1 1 60 120 1 1 + +#. Set the example ``holiday`` schedule using the following command: + + ./chip-tool doorlock set-holiday-schedule HolidayIndex LocalStartTime LocalEndTime OperatingMode destination-id endpoint-id + + Where: + + * ``HolidayIndex`` is an index of the new schedule, starting from ``1``. + * ``LocalStartTime`` is the starting time in Epoch Time. + * ``LocalEndTime`` is the ending time in Epoch Time. + * ``OperatingMode`` is the operating mode described in the :ref:`matter_lock_scheduled_timed_access` section of this guide. + * ``node-id`` is the device node ID. + * ``endpoint-id`` is the Matter door lock endpoint, in this sample assigned to ``1``. + + For example, to setup a ``holiday-schedule`` with type ``Vacation`` use the following command: + + .. code-block:: console + + ./chip-tool doorlock set-holiday-schedule 1 60 120 1 1 1 + +#. Read saved schedules using the following commands providing the arguments in a similar way as above: + + .. code-block:: console + + ./chip-tool doorlock get-week-day-schedule WeekDayIndex UserIndex destination-id endpoint-id + + .. code-block:: console + + ./chip-tool doorlock get-year-day-schedule YearDayIndex UserIndex destination-id endpoint-id + + .. code-block:: console + + ./chip-tool doorlock get-holiday-schedule HolidayIndex destination-id endpoint-id + .. _matter_lock_sample_switching_thread_wifi: Testing switching between Thread and Wi-Fi diff --git a/samples/matter/lock/src/bolt_lock_manager.cpp b/samples/matter/lock/src/bolt_lock_manager.cpp index cb1ce552b3f5..30780d7f15de 100644 --- a/samples/matter/lock/src/bolt_lock_manager.cpp +++ b/samples/matter/lock/src/bolt_lock_manager.cpp @@ -54,6 +54,49 @@ bool BoltLockManager::SetCredential(uint16_t credentialIndex, FabricIndex creato credentialType, secret); } +#ifdef CONFIG_LOCK_SCHEDULES + +DlStatus BoltLockManager::GetWeekDaySchedule(uint8_t weekdayIndex, uint16_t userIndex, + EmberAfPluginDoorLockWeekDaySchedule &schedule) +{ + return PinManager::Instance().GetWeekDaySchedule(weekdayIndex, userIndex, schedule); +} + +DlStatus BoltLockManager::SetWeekDaySchedule(uint8_t weekdayIndex, uint16_t userIndex, DlScheduleStatus status, + DaysMaskMap daysMask, uint8_t startHour, uint8_t startMinute, + uint8_t endHour, uint8_t endMinute) +{ + return PinManager::Instance().SetWeekDaySchedule(weekdayIndex, userIndex, status, daysMask, startHour, + startMinute, endHour, endMinute); +} + +DlStatus BoltLockManager::GetYearDaySchedule(uint8_t yearDayIndex, uint16_t userIndex, + EmberAfPluginDoorLockYearDaySchedule &schedule) +{ + return PinManager::Instance().GetYearDaySchedule(yearDayIndex, userIndex, schedule); +} + +DlStatus BoltLockManager::SetYearDaySchedule(uint8_t yeardayIndex, uint16_t userIndex, DlScheduleStatus status, + uint32_t localStartTime, uint32_t localEndTime) +{ + return PinManager::Instance().SetYearDaySchedule(yeardayIndex, userIndex, status, localStartTime, localEndTime); +} + +DlStatus BoltLockManager::GetHolidaySchedule(uint8_t holidayIndex, EmberAfPluginDoorLockHolidaySchedule &schedule) +{ + return PinManager::Instance().GetHolidaySchedule(holidayIndex, schedule); +} + +DlStatus BoltLockManager::SetHolidaySchedule(uint8_t holidayIndex, DlScheduleStatus status, uint32_t localStartTime, + uint32_t localEndTime, OperatingModeEnum operatingMode) +{ + return PinManager::Instance().SetHolidaySchedule(holidayIndex, status, localStartTime, localEndTime, + operatingMode); +} + +#endif /* CONFIG_LOCK_SCHEDULES */ + + bool BoltLockManager::ValidatePIN(const Optional &pinCode, OperationErrorEnum &err) { return PinManager::Instance().ValidatePIN(pinCode, err); diff --git a/samples/matter/lock/src/bolt_lock_manager.h b/samples/matter/lock/src/bolt_lock_manager.h index f5851715b63c..23a728acacad 100644 --- a/samples/matter/lock/src/bolt_lock_manager.h +++ b/samples/matter/lock/src/bolt_lock_manager.h @@ -59,6 +59,21 @@ class BoltLockManager { DlCredentialStatus credentialStatus, CredentialTypeEnum credentialType, const chip::ByteSpan &secret); +#ifdef CONFIG_LOCK_SCHEDULES + DlStatus GetWeekDaySchedule(uint8_t weekdayIndex, uint16_t userIndex, + EmberAfPluginDoorLockWeekDaySchedule &schedule); + DlStatus SetWeekDaySchedule(uint8_t weekdayIndex, uint16_t userIndex, DlScheduleStatus status, + DaysMaskMap daysMask, uint8_t startHour, uint8_t startMinute, uint8_t endHour, + uint8_t endMinute); + DlStatus GetYearDaySchedule(uint8_t yearDayIndex, uint16_t userIndex, + EmberAfPluginDoorLockYearDaySchedule &schedule); + DlStatus SetYearDaySchedule(uint8_t yearDayIndex, uint16_t userIndex, DlScheduleStatus status, + uint32_t localStartTime, uint32_t localEndTime); + DlStatus GetHolidaySchedule(uint8_t holidayIndex, EmberAfPluginDoorLockHolidaySchedule &schedule); + DlStatus SetHolidaySchedule(uint8_t holidayIndex, DlScheduleStatus status, uint32_t localStartTime, + uint32_t localEndTime, OperatingModeEnum operatingMode); +#endif /* CONFIG_LOCK_SCHEDULES */ + bool ValidatePIN(const Optional &pinCode, OperationErrorEnum &err); void Lock(OperationSource source); diff --git a/samples/matter/lock/src/credentials/credentials_data_types.cpp b/samples/matter/lock/src/credentials/credentials_data_types.cpp index 74e836dec3a6..6bc19ed81d9c 100644 --- a/samples/matter/lock/src/credentials/credentials_data_types.cpp +++ b/samples/matter/lock/src/credentials/credentials_data_types.cpp @@ -176,4 +176,162 @@ CHIP_ERROR User::Deserialize(const void *buff, size_t buffSize) return CHIP_NO_ERROR; } +template <> +template <> +CHIP_ERROR Schedule::FillFromPlugin(EmberAfPluginDoorLockWeekDaySchedule &plugin) +{ + static_assert(sizeof(EmberAfPluginDoorLockWeekDaySchedule) == sizeof(ScheduleParams::WeekDay)); + + mScheduleParams.mWeekDay.mFields.mDaysMask = static_cast(plugin.daysMask); + mScheduleParams.mWeekDay.mFields.mStartHour = plugin.startHour; + mScheduleParams.mWeekDay.mFields.mStartMinute = plugin.startMinute; + mScheduleParams.mWeekDay.mFields.mEndHour = plugin.endHour; + mScheduleParams.mWeekDay.mFields.mEndMinute = plugin.endMinute; + + return CHIP_NO_ERROR; +} + +template <> +template <> +CHIP_ERROR Schedule::FillFromPlugin(EmberAfPluginDoorLockYearDaySchedule &plugin) +{ + static_assert(sizeof(EmberAfPluginDoorLockYearDaySchedule) == sizeof(ScheduleParams::YearDay)); + + mScheduleParams.mYearDay.mFields.mLocalStartTime = plugin.localStartTime; + mScheduleParams.mYearDay.mFields.mLocalEndTime = plugin.localEndTime; + + return CHIP_NO_ERROR; +} + +template <> +template <> +CHIP_ERROR Schedule::FillFromPlugin(EmberAfPluginDoorLockHolidaySchedule &plugin) +{ + static_assert(sizeof(EmberAfPluginDoorLockHolidaySchedule) == sizeof(ScheduleParams::Holiday)); + + mScheduleParams.mHoliday.mFields.mLocalStartTime = plugin.localStartTime; + mScheduleParams.mHoliday.mFields.mLocalEndTime = plugin.localEndTime; + mScheduleParams.mHoliday.mFields.mOperatingMode = static_cast(plugin.operatingMode); + + return CHIP_NO_ERROR; +} + +template <> +template <> +CHIP_ERROR Schedule::ConvertToPlugin(EmberAfPluginDoorLockWeekDaySchedule &plugin) const +{ + static_assert(sizeof(EmberAfPluginDoorLockWeekDaySchedule) == sizeof(ScheduleParams::WeekDay)); + + plugin.daysMask = static_cast(mScheduleParams.mWeekDay.mFields.mDaysMask); + plugin.startHour = mScheduleParams.mWeekDay.mFields.mStartHour; + plugin.startMinute = mScheduleParams.mWeekDay.mFields.mStartMinute; + plugin.endHour = mScheduleParams.mWeekDay.mFields.mEndHour; + plugin.endMinute = mScheduleParams.mWeekDay.mFields.mEndMinute; + + return CHIP_NO_ERROR; +} + +template <> +template <> +CHIP_ERROR Schedule::ConvertToPlugin(EmberAfPluginDoorLockYearDaySchedule &plugin) const +{ + static_assert(sizeof(EmberAfPluginDoorLockYearDaySchedule) == sizeof(ScheduleParams::YearDay)); + + plugin.localStartTime = mScheduleParams.mYearDay.mFields.mLocalStartTime; + plugin.localEndTime = mScheduleParams.mYearDay.mFields.mLocalEndTime; + + return CHIP_NO_ERROR; +} + +template <> +template <> +CHIP_ERROR Schedule::ConvertToPlugin(EmberAfPluginDoorLockHolidaySchedule &plugin) const +{ + static_assert(sizeof(EmberAfPluginDoorLockHolidaySchedule) == sizeof(ScheduleParams::Holiday)); + + plugin.localStartTime = mScheduleParams.mHoliday.mFields.mLocalStartTime; + plugin.localEndTime = mScheduleParams.mHoliday.mFields.mLocalEndTime; + plugin.operatingMode = static_cast(mScheduleParams.mHoliday.mFields.mOperatingMode); + + return CHIP_NO_ERROR; +} + +template <> size_t Schedule::Serialize(void *buff, size_t buffSize) +{ + if (!buff || buffSize < sizeof(ScheduleParams::WeekDay)) { + return 0; + } + + memcpy(buff, mScheduleParams.mWeekDay.mRaw, sizeof(mScheduleParams.mWeekDay.mRaw)); + + return sizeof(mScheduleParams.mWeekDay.mRaw); +} + +template <> size_t Schedule::Serialize(void *buff, size_t buffSize) +{ + if (!buff || buffSize < sizeof(ScheduleParams::YearDay)) { + return 0; + } + + memcpy(buff, mScheduleParams.mYearDay.mRaw, sizeof(mScheduleParams.mYearDay.mRaw)); + + return sizeof(mScheduleParams.mYearDay.mRaw); +} + +template <> size_t Schedule::Serialize(void *buff, size_t buffSize) +{ + if (!buff || buffSize < sizeof(ScheduleParams::Holiday)) { + return 0; + } + + memcpy(buff, mScheduleParams.mHoliday.mRaw, sizeof(mScheduleParams.mHoliday.mRaw)); + + return sizeof(mScheduleParams.mHoliday.mRaw); +} + +template <> CHIP_ERROR Schedule::Deserialize(const void *buff, size_t buffSize) +{ + if (!buff) { + return CHIP_ERROR_INVALID_ARGUMENT; + } + + if (buffSize > sizeof(ScheduleParams::WeekDay)) { + return CHIP_ERROR_BUFFER_TOO_SMALL; + } + + memcpy(mScheduleParams.mWeekDay.mRaw, buff, sizeof(mScheduleParams.mWeekDay.mRaw)); + + return CHIP_NO_ERROR; +} + +template <> CHIP_ERROR Schedule::Deserialize(const void *buff, size_t buffSize) +{ + if (!buff) { + return CHIP_ERROR_INVALID_ARGUMENT; + } + + if (buffSize > sizeof(ScheduleParams::YearDay)) { + return CHIP_ERROR_BUFFER_TOO_SMALL; + } + + memcpy(mScheduleParams.mYearDay.mRaw, buff, sizeof(mScheduleParams.mYearDay.mRaw)); + + return CHIP_NO_ERROR; +} + +template <> CHIP_ERROR Schedule::Deserialize(const void *buff, size_t buffSize) +{ + if (!buff) { + return CHIP_ERROR_INVALID_ARGUMENT; + } + + if (buffSize > sizeof(ScheduleParams::Holiday)) { + return CHIP_ERROR_BUFFER_TOO_SMALL; + } + + memcpy(mScheduleParams.mHoliday.mRaw, buff, sizeof(mScheduleParams.mHoliday.mRaw)); + + return CHIP_NO_ERROR; +} + } /* namespace DoorLockData */ diff --git a/samples/matter/lock/src/credentials/credentials_data_types.h b/samples/matter/lock/src/credentials/credentials_data_types.h index 7d9b17c9411d..eaecf09228f5 100644 --- a/samples/matter/lock/src/credentials/credentials_data_types.h +++ b/samples/matter/lock/src/credentials/credentials_data_types.h @@ -8,6 +8,7 @@ #include #include +#include namespace DoorLockData { @@ -118,6 +119,89 @@ struct Credential { CHIP_ERROR Deserialize(const void *buff, size_t buffSize); }; +enum class ScheduleType : uint8_t { WeekDay, YearDay, Holiday }; + +template struct Schedule { + union ScheduleParams { + union WeekDay { + struct __attribute__((__packed__)) Fields { + uint8_t mDaysMask; + uint8_t mStartHour; + uint8_t mStartMinute; + uint8_t mEndHour; + uint8_t mEndMinute; + } mFields; + uint8_t mRaw[sizeof(mFields)]; + } mWeekDay; + static_assert(sizeof(WeekDay) == 5); + union YearDay { + struct __attribute__((__packed__)) Fields { + uint32_t mLocalStartTime; + uint32_t mLocalEndTime; + } mFields; + uint8_t mRaw[sizeof(mFields)]; + } mYearDay; + static_assert(sizeof(YearDay) == 8); + union Holiday { + struct __attribute__((__packed__)) Fields { + uint32_t mLocalStartTime; + uint32_t mLocalEndTime; + uint32_t mOperatingMode; + } mFields; + uint8_t mRaw[sizeof(mFields)]; + } mHoliday; + static_assert(sizeof(Holiday) == 12); + }; + + ScheduleParams mScheduleParams; + + /** + * @brief fill User entry with data from one of the possible plugin structs: + * EmberAfPluginDoorLockWeekDaySchedule for the WeekDay schedule type. + * EmberAfPluginDoorLockYearDaySchedule for the YearDay schedule type. + * EmberAfPluginDoorLockHolidaySchedule for the Holiday schedule type. + * + * @param plugin a reference to the plugin. + * @return CHIP_NO_ERROR if whole struct has been filled properly. + */ + template CHIP_ERROR FillFromPlugin(Plugin &plugin); + + /** + * @brief Convert whole object to one of the possible plugin structs: + * EmberAfPluginDoorLockWeekDaySchedule for the WeekDay schedule type. + * EmberAfPluginDoorLockYearDaySchedule for the YearDay schedule type. + * EmberAfPluginDoorLockHolidaySchedule for the Holiday schedule type. + * + * @param plugin a reference to the plugin. + * @return CHIP_NO_ERROR if conversion has been finished successfully. + */ + template CHIP_ERROR ConvertToPlugin(Plugin &plugin) const; + + /** + * @brief Serialize all fields into data buffer. + * + * `buffSize` must be set to at least the RequiredBufferSize size. + * + * @param buff buffer to store serialized data. + * @param buffSize size of input buffer. + * @return size_t output serialized data length, 0 value means error. + */ + size_t Serialize(void *buff, size_t buffSize); + + /** + * @brief Deserialize all fields from given buffer. + * + * `buffSize` must be set to at least the RequiredBufferSize size. + * + * @param buff buffer containing serialized data (by invoking serialized method). + * @param buffSize size of output buffer. + * @return CHIP_ERROR_BUFFER_TOO_SMALL if provided buffSize is too small. + * @return CHIP_ERROR_INVALID_ARGUMENT if arguments are wrong. + * @return CHIP_NO_ERROR if deserialization has been finished successfully. + */ + CHIP_ERROR Deserialize(const void *buff, size_t buffSize); +}; + struct User { union Info { struct __attribute__((__packed__)) Fields { @@ -138,7 +222,6 @@ struct User { size_t mSize; CredentialStruct mData[CONFIG_LOCK_MAX_NUM_CREDENTIALS_PER_USER]; }; - struct Name { size_t mSize; char mValue[DOOR_LOCK_USER_NAME_BUFFER_SIZE]; @@ -385,14 +468,15 @@ template class Credentials { bool cumulativeError{ true }; for (auto &credType : mCredentials) { - /* Array indexes are used as credential indexes (that start from 1 in the door lock server). */ + /* Array indexes are used as credential indexes (that start from 1 in the door lock + * server). */ uint16_t credIdx{ 0 }; for (auto &cred : credType) { if (operation) { bool currOpStatus = operation(cred, credIdx); if (!currOpStatus) { - /* It's user responsibility to handle the operation properly, so all we - * can do is to continue */ + /* It's user responsibility to handle the operation properly, so + * all we can do is to continue */ continue; } cumulativeError = cumulativeError && currOpStatus; diff --git a/samples/matter/lock/src/credentials/credentials_manager.cpp b/samples/matter/lock/src/credentials/credentials_manager.cpp index b751d815267e..899be19dfdb7 100644 --- a/samples/matter/lock/src/credentials/credentials_manager.cpp +++ b/samples/matter/lock/src/credentials/credentials_manager.cpp @@ -26,214 +26,11 @@ void CredentialsManager::Init(SetCredentialCallback setCredential mClearCredentialCallback = clearCredentialClbk; mValidateCredentialCallback = validateCredentialClbk; CredentialsStorage::Instance().Init(); - LoadFromPersistentStorage(); -} - -template -bool CredentialsManager::GetUserInfo(uint16_t userIndex, EmberAfPluginDoorLockUserInfo &user) -{ - /* userIndex is guaranteed by the caller to be between 1 and CONFIG_LOCK_MAX_NUM_USERS */ - VerifyOrReturnError(userIndex > 0 && userIndex <= CONFIG_LOCK_MAX_NUM_USERS, false); - if (CHIP_NO_ERROR != mUsers[userIndex - 1].ConvertToPlugin(user)) { - return false; - } - - return true; -} - -template -bool CredentialsManager::SetUser(uint16_t userIndex, FabricIndex creator, FabricIndex modifier, - const CharSpan &userName, uint32_t uniqueId, UserStatusEnum userStatus, - UserTypeEnum userType, CredentialRuleEnum credentialRule, - const CredentialStruct *credentials, size_t totalCredentials) -{ - /* userIndex is guaranteed by the caller to be between 1 and CONFIG_LOCK_MAX_NUM_USERS */ - VerifyOrReturnError(userIndex > 0 && userIndex <= CONFIG_LOCK_MAX_NUM_USERS, false); - auto &user = mUsers[userIndex - 1]; - - VerifyOrReturnError(userName.size() <= DOOR_LOCK_MAX_USER_NAME_SIZE, false); - VerifyOrReturnError(totalCredentials <= CONFIG_LOCK_MAX_NUM_CREDENTIALS_PER_USER, false); - - user.mName.mSize = userName.size(); - memcpy(user.mName.mValue, userName.data(), userName.size()); - - for (size_t i = 0; i < totalCredentials; ++i) { - auto *currentCredentials = credentials + i; - memcpy(&user.mOccupiedCredentials.mData[i], currentCredentials, sizeof(CredentialStruct)); - } - user.mOccupiedCredentials.mSize = totalCredentials * sizeof(CredentialStruct); - - user.mInfo.mFields.mUserUniqueId = uniqueId; - user.mInfo.mFields.mUserStatus = static_cast(userStatus); - user.mInfo.mFields.mUserType = static_cast(userType); - user.mInfo.mFields.mCredentialRule = static_cast(credentialRule); - user.mInfo.mFields.mCreationSource = static_cast(DlAssetSource::kMatterIM); - user.mInfo.mFields.mCreatedBy = static_cast(creator); - user.mInfo.mFields.mModificationSource = static_cast(DlAssetSource::kMatterIM); - user.mInfo.mFields.mLastModifiedBy = static_cast(modifier); - - uint8_t userSerialized[DoorLockData::User::RequiredBufferSize()] = { 0 }; - size_t serializedSize = user.Serialize(userSerialized, sizeof(userSerialized)); - - uint8_t userIndexesSerialized[UsersIndexes::RequiredBufferSize()] = { 0 }; - size_t serializedIndexesSize{ 0 }; - - /* Process the user indexes only if the user itself was properly serialized. */ - if (0 < serializedSize) { - if (CHIP_ERROR_BUFFER_TOO_SMALL == mUsersIndexes.AddIndex(userIndex)) { - return false; - } - serializedIndexesSize = mUsersIndexes.Serialize(userIndexesSerialized, sizeof(userIndexesSerialized)); - } - - /* Store to persistent storage */ - if (0 < serializedSize && 0 < serializedIndexesSize) { - if (!CredentialsStorage::Instance().Store(CredentialsStorage::Type::User, userSerialized, - serializedSize, userIndex)) { - LOG_ERR("Cannot store given User Idx: %d", userIndex); - } else { - if (!CredentialsStorage::Instance().Store(CredentialsStorage::Type::UsersIndexes, - userIndexesSerialized, serializedIndexesSize, 0)) { - LOG_ERR("Cannot store users counter. The persistent database will be corrupted."); - } - } - } else { - LOG_ERR("Cannot serialize given User Idx: %d", userIndex); - } - - LOG_INF("Setting lock user %u: %s", static_cast(userIndex), - userStatus == UserStatusEnum::kAvailable ? "available" : "occupied"); - - return true; -} - -template -bool CredentialsManager::GetCredential(uint16_t credentialIndex, CredentialTypeEnum credentialType, - EmberAfPluginDoorLockCredentialInfo &credential) -{ - VerifyOrReturnError(credentialIndex > 0 && credentialIndex <= CONFIG_LOCK_MAX_NUM_CREDENTIALS_PER_TYPE, false); - VerifyOrReturnError(CHIP_NO_ERROR == mCredentials.GetCredentials(credentialType, credential, credentialIndex), - false); - - return true; -} - -template -bool CredentialsManager::SetCredential(uint16_t credentialIndex, FabricIndex creator, - FabricIndex modifier, DlCredentialStatus credentialStatus, - CredentialTypeEnum credentialType, const ByteSpan &secret) -{ - /* Programming PIN is a special case - it is unique and its index assumed to be 0, currently we do not - * support it */ - VerifyOrReturnError(credentialType != CredentialTypeEnum::kProgrammingPIN, false); - VerifyOrReturnError(credentialIndex > 0 && credentialIndex <= CONFIG_LOCK_MAX_NUM_CREDENTIALS_PER_TYPE, false); - VerifyOrReturnError(secret.size() <= DoorLockData::kMaxCredentialLength, false); - - bool success{ false }; - auto &credentials = mCredentials.GetCredentialsTypes(credentialType, success); - VerifyOrReturnError(success, false); - auto &credential = credentials[credentialIndex - 1]; - return DoSetCredential(credential, credentialIndex, creator, modifier, credentialStatus, credentialType, - secret); -} - -template -bool CredentialsManager::DoSetCredential(DoorLockData::Credential &credential, uint16_t credentialIndex, - FabricIndex creator, FabricIndex modifier, - DlCredentialStatus credentialStatus, - CredentialTypeEnum credentialType, const ByteSpan &secret) -{ - uint32_t uniqueUserId; - credential.mInfo.mFields.mStatus = static_cast(credentialStatus); - credential.mInfo.mFields.mCredentialType = static_cast(credentialType); - credential.mInfo.mFields.mCreationSource = static_cast(DlAssetSource::kMatterIM); - credential.mInfo.mFields.mCreatedBy = static_cast(creator); - credential.mInfo.mFields.mModificationSource = static_cast(DlAssetSource::kMatterIM); - credential.mInfo.mFields.mLastModifiedBy = static_cast(modifier); - - if (!secret.empty()) { - credential.mSecret.mDataLength = secret.size(); - memcpy(credential.mSecret.mData, secret.data(), secret.size()); - } else if (CHIP_NO_ERROR == Instance().GetCredentialUserId(credentialIndex, credentialType, uniqueUserId)) { - /* Credential already exists, and the new secret is empty, so the credential is being cleared. - * Inform the higher layer about clearing the credential and provide current credential data. - */ - if (Instance().mClearCredentialCallback && DlCredentialStatus::kAvailable == credentialStatus) { - ByteSpan existingSecretToClear = { credential.mSecret.mData, credential.mSecret.mDataLength }; - Instance().mClearCredentialCallback(credentialIndex, credentialType, uniqueUserId, - existingSecretToClear); - } - memset(credential.mSecret.mData, 0, credential.mSecret.mDataLength); - credential.mSecret.mDataLength = 0; - } - - uint8_t credentialSerialized[DoorLockData::Credential::RequiredBufferSize()] = { 0 }; - size_t serializedSize = credential.Serialize(credentialSerialized, sizeof(credentialSerialized)); - - uint8_t credentialIndexesSerialized[CredentialsIndexes::CredentialList::RequiredBufferSize()] = { 0 }; - size_t credIndexesSerializedSize{ 0 }; - - /* Process the credential indexes only if the credential itself was properly serialized. */ - if (0 < serializedSize) { - auto &credIndexes = - Instance().mCredentialsIndexes.Get(static_cast(credentialType)); - - if (CHIP_ERROR_BUFFER_TOO_SMALL == credIndexes.AddIndex(credentialIndex)) { - return false; - } - - credIndexesSerializedSize = - credIndexes.Serialize(credentialIndexesSerialized, sizeof(credentialIndexesSerialized)); - } - - /* Store to persistent storage */ - if (0 < serializedSize && 0 < credIndexesSerializedSize) { - if (!CredentialsStorage::Instance().Store( - CredentialsStorage::Type::Credential, credentialSerialized, serializedSize, - static_cast(credentialType), credentialIndex)) { - LOG_ERR("Cannot store given credentials Type: %d Idx: %d", - static_cast(credentialType), credentialIndex); - } else { - if (!CredentialsStorage::Instance().Store( - CredentialsStorage::Type::CredentialsIndexes, credentialIndexesSerialized, - credIndexesSerializedSize, static_cast(credentialType))) { - LOG_ERR("Cannot store credentials counter. The persistent database will be corrupted."); - } - } - } else { - LOG_ERR("Cannot serialize given credentials Type: %d Idx: %d", static_cast(credentialType), - credentialIndex); - } - - LOG_INF("Setting lock credential %u: %s", static_cast(credentialIndex), - credential.mInfo.mFields.mStatus == static_cast(DlCredentialStatus::kAvailable) ? "available" : - "occupied"); - - if (CHIP_NO_ERROR == Instance().GetCredentialUserId(credentialIndex, credentialType, uniqueUserId)) { - if (Instance().mSetCredentialCallback && credential.mSecret.mDataLength > 0) { - Instance().mSetCredentialCallback(credentialIndex, credentialType, uniqueUserId); - } - } - - return true; -} - -template -CHIP_ERROR CredentialsManager::GetCredentialUserId(uint16_t credentialIndex, - CredentialTypeEnum credentialType, uint32_t &userId) -{ - for (size_t idxUsr = 0; idxUsr < CONFIG_LOCK_MAX_NUM_USERS; ++idxUsr) { - auto &user = Instance().mUsers[idxUsr]; - for (size_t idxCred = 0; idxCred < CONFIG_LOCK_MAX_NUM_CREDENTIALS_PER_USER; ++idxCred) { - auto &credentialStruct = user.mOccupiedCredentials.mData[idxCred]; - if (credentialStruct.credentialIndex == credentialIndex && - credentialStruct.credentialType == credentialType) { - userId = user.mInfo.mFields.mUserUniqueId; - return CHIP_NO_ERROR; - } - } - } - return CHIP_ERROR_NOT_FOUND; + LoadUsersFromPersistentStorage(); + LoadCredentialsFromPersistentStorage(); +#ifdef CONFIG_LOCK_SCHEDULES + LoadSchedulesFromPersistentStorage(); +#endif /* CONFIG_LOCK_SCHEDULES */ } template @@ -276,113 +73,6 @@ bool CredentialsManager::ValidatePIN(const Optional &pi return false; } -#ifdef CONFIG_LOCK_LEAVE_FABRIC_CLEAR_CREDENTIAL -template bool CredentialsManager::ClearAllCredentialsFromFabric() -{ - return mCredentials.ForEach([](DoorLockData::Credential &credential, uint8_t credIdx) { - /* At this point the door-lock-server already invalidated both mCreatedBy and mLastModifiedBy - of all credentials assigned to the fabric which is currently being removed */ - if (credential.mInfo.mFields.mCreatedBy == kUndefinedFabricIndex || - credential.mInfo.mFields.mLastModifiedBy == kUndefinedFabricIndex) { - return Instance().ClearCredential(credential, credIdx); - } - return true; - }); -} - -template -bool CredentialsManager::ClearCredential(DoorLockData::Credential &credential, uint8_t credIdx) -{ - credIdx++; /* DoSetCredential expects indexes starting from 1 */ - return DoSetCredential(credential, credIdx, kUndefinedFabricIndex, kUndefinedFabricIndex, - DlCredentialStatus::kAvailable, - static_cast(credential.mInfo.mFields.mCredentialType), - chip::ByteSpan()); -} -#endif - -template void CredentialsManager::LoadFromPersistentStorage() -{ - /* Load all Users from persistent storage */ - uint8_t userIndexesSerialized[UsersIndexes::RequiredBufferSize()] = { 0 }; - uint8_t userData[DoorLockData::User::RequiredBufferSize()] = { 0 }; - bool usersFound{ false }; - size_t outSize{ 0 }; - - if (!CredentialsStorage::Instance().Load(CredentialsStorage::Type::UsersIndexes, userIndexesSerialized, - sizeof(userIndexesSerialized), outSize, 0)) { - LOG_INF("No users indexes stored"); - } else { - if (CHIP_NO_ERROR != mUsersIndexes.Deserialize(userIndexesSerialized, outSize)) { - } else { - usersFound = true; - } - } - - if (usersFound) { - for (size_t idx = 0; idx < mUsersIndexes.mList.mLength; idx++) { - /* Read the actual index from the indexList */ - uint16_t userIndex = mUsersIndexes.mList.mIndexes[idx]; - outSize = 0; - if (!CredentialsStorage::Instance().Load(CredentialsStorage::Type::User, userData, - sizeof(userData), outSize, userIndex)) { - } else { - if (CHIP_NO_ERROR != mUsers[userIndex - 1].Deserialize(userData, outSize)) { - LOG_ERR("Cannot deserialize User index: %d", userIndex); - } - } - } - } - - uint8_t credentialData[DoorLockData::Credential::RequiredBufferSize()] = { 0 }; - uint8_t credentialIndexesSerialized[CredentialsIndexes::CredentialList::RequiredBufferSize()] = { 0 }; - - for (uint8_t type = CredentialTypeIndex::Pin; type <= CredentialTypeIndex::Max; ++type) { - bool success{ false }; - auto &credentials = mCredentials.GetCredentialsTypes(static_cast(type), success); - - if (!success) { - continue; - } - outSize = 0; - if (!CredentialsStorage::Instance().Load(CredentialsStorage::Type::CredentialsIndexes, - credentialIndexesSerialized, - sizeof(credentialIndexesSerialized), outSize, type)) { - LOG_INF("No stored indexes for credential of type: %d", type); - continue; - } - - auto &credIndexes = mCredentialsIndexes.Get(static_cast(type)); - if (CHIP_NO_ERROR != credIndexes.Deserialize(credentialIndexesSerialized, outSize)) { - LOG_ERR("Cannot deserialize indexes for credentials of type: %d", type); - continue; - } - - for (size_t idx = 0; idx < credIndexes.mList.mLength; idx++) { - /* Read the actual index from the indexList */ - uint16_t credentialIndex = credIndexes.mList.mIndexes[idx]; - outSize = 0; - if (!CredentialsStorage::Instance().Load(CredentialsStorage::Type::Credential, credentialData, - sizeof(credentialData), outSize, type, - credentialIndex)) { - LOG_ERR("Cannot load credentials of type %d for index: %d", static_cast(type), - credentialIndex); - } - - if (CHIP_NO_ERROR != credentials[credentialIndex - 1].Deserialize(credentialData, outSize)) { - LOG_ERR("Cannot deserialize credentials of type %d for index: %d", - static_cast(type), credentialIndex); - } - } - } - outSize = 0; - if (!CredentialsStorage::Instance().Load(CredentialsStorage::Type::RequirePIN, &mRequirePINForRemoteOperation, - sizeof(mRequirePINForRemoteOperation), outSize) || - outSize != sizeof(mRequirePINForRemoteOperation)) { - LOG_DBG("Cannot load RequirePINforRemoteOperation"); - } -} - template void CredentialsManager::InitializeUsers() { for (size_t userIndex = 0; userIndex < CONFIG_LOCK_MAX_NUM_USERS; ++userIndex) { @@ -400,10 +90,35 @@ template void CredentialsManager: } } +#ifdef CONFIG_LOCK_SCHEDULES +template void CredentialsManager::InitializeSchedules() +{ + for (size_t userIndex = 0; userIndex < CONFIG_LOCK_MAX_NUM_USERS; ++userIndex) { + for (size_t weekDayIndex = 0; weekDayIndex < CONFIG_LOCK_MAX_WEEKDAY_SCHEDULES_PER_USER; + ++weekDayIndex) { + auto &schedule = mWeekDaySchedule[userIndex][weekDayIndex]; + memset(schedule.mScheduleParams.mWeekDay.mRaw, 0, sizeof(schedule.mScheduleParams.mWeekDay)); + } + for (size_t yearDayIndex = 0; yearDayIndex < CONFIG_LOCK_MAX_YEARDAY_SCHEDULES_PER_USER; + ++yearDayIndex) { + auto &schedule = mYearDaySchedule[userIndex][yearDayIndex]; + memset(schedule.mScheduleParams.mYearDay.mRaw, 0, sizeof(schedule.mScheduleParams.mYearDay)); + } + } + for (size_t holidayIndex = 0; holidayIndex < CONFIG_LOCK_MAX_WEEKDAY_SCHEDULES_PER_USER; ++holidayIndex) { + auto &schedule = mHolidaySchedule[holidayIndex]; + memset(schedule.mScheduleParams.mHoliday.mRaw, 0, sizeof(schedule.mScheduleParams.mHoliday)); + } +} +#endif /* CONFIG_LOCK_SCHEDULES */ + template void CredentialsManager::InitializeAllCredentials() { mCredentials.Initialize(); InitializeUsers(); +#ifdef CONFIG_LOCK_SCHEDULES + InitializeSchedules(); +#endif /* CONFIG_LOCK_SCHEDULES */ } template void CredentialsManager::SetRequirePIN(bool require) @@ -418,98 +133,6 @@ template void CredentialsManager: } } -#ifdef CONFIG_LOCK_ENABLE_DEBUG - -template -const char *CredentialsManager::GetCredentialName(CredentialTypeEnum type) -{ - switch (type) { - case CredentialTypeEnum::kPin: - return "PIN"; - case CredentialTypeEnum::kRfid: - return "RFID"; - case CredentialTypeEnum::kFingerprint: - return "Fingerprint"; - case CredentialTypeEnum::kFingerVein: - return "Finger vein"; - case CredentialTypeEnum::kFace: - return "Face"; - default: - return "None"; - } -} - -template -void CredentialsManager::PrintCredential(CredentialTypeEnum type, uint16_t index) -{ - bool success{ false }; - auto &credentialList = mCredentials.GetCredentialsTypes(type, success); - - if (!success) { - LOG_INF("%s credential type is not supported. Nothing to print.", GetCredentialName(type)); - return; - } - - /* User is going to provide an index which is started from 1 instead of 0 */ - auto arrayIdx = index - 1; - - LOG_INF("\t -- Index: %d", index); - LOG_INF("\t -- Type: %s", GetCredentialName(type)); - LOG_INF("\t\t -- Status: %s", - static_cast(DlCredentialStatus::kAvailable) == credentialList[arrayIdx].mInfo.mFields.mStatus ? - "available" : - "occupied"); - LOG_INF("\t\t -- Creation source: %d", credentialList[arrayIdx].mInfo.mFields.mCreationSource); - LOG_INF("\t\t -- Created by: %d", credentialList[arrayIdx].mInfo.mFields.mCreatedBy); - LOG_INF("\t\t -- Modification source: %d", credentialList[arrayIdx].mInfo.mFields.mModificationSource); - LOG_INF("\t\t -- Modified by: %d", credentialList[arrayIdx].mInfo.mFields.mLastModifiedBy); - LOG_HEXDUMP_INF(credentialList[arrayIdx].mSecret.mData, credentialList[arrayIdx].mSecret.mDataLength, - "\t\t -- Credential data:"); -} - -template void CredentialsManager::PrintUser(uint16_t userIndex) -{ - auto *user = &mUsers[userIndex - 1]; - - if (user) { - LOG_INF("\t -- User %d: %s", userIndex, user->mName.mValue); - LOG_INF("\t\t -- ID: %d", user->mInfo.mFields.mUserUniqueId); - LOG_INF("\t\t -- Type: %d", user->mInfo.mFields.mUserType); - LOG_INF("\t\t -- Status: %s", - static_cast(user->mInfo.mFields.mUserStatus) == UserStatusEnum::kAvailable ? - "available" : - "occupied"); - LOG_INF("\t\t -- Credential Rule: %d", user->mInfo.mFields.mCredentialRule); - LOG_INF("\t\t -- Creation Source: %d", user->mInfo.mFields.mCreationSource); - LOG_INF("\t\t -- Created By: %d", user->mInfo.mFields.mCreatedBy); - LOG_INF("\t\t -- Modification Source: %d", user->mInfo.mFields.mModificationSource); - LOG_INF("\t\t -- Modified By: %d", user->mInfo.mFields.mLastModifiedBy); - LOG_INF("\t\t -- Associated Credentials %d:", - user->mOccupiedCredentials.mSize / sizeof(CredentialStruct)); - for (uint8_t i = 0; i < user->mOccupiedCredentials.mSize / sizeof(CredentialStruct); i++) { - switch (user->mOccupiedCredentials.mData[i].credentialType) { - case CredentialTypeEnum::kPin: - LOG_INF("\t\t\t -- PIN | index: %d", - user->mOccupiedCredentials.mData[i].credentialIndex); - break; - case CredentialTypeEnum::kRfid: - LOG_INF("\t\t\t -- RFID | index: %d", - user->mOccupiedCredentials.mData[i].credentialIndex); - break; - case CredentialTypeEnum::kFingerprint: - case CredentialTypeEnum::kFingerVein: - LOG_INF("\t\t\t -- Fingerprint | index: %d", - user->mOccupiedCredentials.mData[i].credentialIndex); - break; - default: - break; - } - } - } -} - -#endif - /* Explicitly instantiate supported template variants to avoid linker errors. */ template class CredentialsManager; template class CredentialsManager; diff --git a/samples/matter/lock/src/credentials/credentials_manager.h b/samples/matter/lock/src/credentials/credentials_manager.h index dcdd97f1fdab..fa60eea4e780 100644 --- a/samples/matter/lock/src/credentials/credentials_manager.h +++ b/samples/matter/lock/src/credentials/credentials_manager.h @@ -134,6 +134,19 @@ template class CredentialsManager DlCredentialStatus credentialStatus, CredentialTypeEnum credentialType, const chip::ByteSpan &secret); + DlStatus GetWeekDaySchedule(uint8_t weekdayIndex, uint16_t userIndex, + EmberAfPluginDoorLockWeekDaySchedule &schedule); + DlStatus SetWeekDaySchedule(uint8_t weekdayIndex, uint16_t userIndex, DlScheduleStatus status, + DaysMaskMap daysMask, uint8_t startHour, uint8_t startMinute, uint8_t endHour, + uint8_t endMinute); + DlStatus GetYearDaySchedule(uint8_t yearDayIndex, uint16_t userIndex, + EmberAfPluginDoorLockYearDaySchedule &schedule); + DlStatus SetYearDaySchedule(uint8_t yearDayIndex, uint16_t userIndex, DlScheduleStatus status, + uint32_t localStartTime, uint32_t localEndTime); + DlStatus GetHolidaySchedule(uint8_t holidayIndex, EmberAfPluginDoorLockHolidaySchedule &schedule); + DlStatus SetHolidaySchedule(uint8_t holidayIndex, DlScheduleStatus status, uint32_t localStartTime, + uint32_t localEndTime, OperatingModeEnum operatingMode); + /** * @brief PIN code validator. * @@ -183,6 +196,8 @@ template class CredentialsManager void PrintCredential(CredentialTypeEnum type, uint16_t index); const char *GetCredentialName(CredentialTypeEnum type); void PrintUser(uint16_t userIndex); + /* @param userIndex is only needed for ScheduleType WeekDay and YearDay */ + void PrintSchedule(DoorLockData::ScheduleType scheduleType, uint16_t scheduleIndex, uint16_t userIndex = 0); #endif private: @@ -195,6 +210,24 @@ template class CredentialsManager using UsersIndexes = DoorLockData::IndexList; +#ifdef CONFIG_LOCK_SCHEDULES + struct WeekDayScheduleIndexes { + using ScheduleList = DoorLockData::IndexList; + ScheduleList mScheduleIndexes[CONFIG_LOCK_MAX_NUM_USERS]; + + ScheduleList &Get(uint16_t userIndex) { return mScheduleIndexes[userIndex - 1]; } + }; + + struct YearDayScheduleIndexes { + using ScheduleList = DoorLockData::IndexList; + ScheduleList mScheduleIndexes[CONFIG_LOCK_MAX_NUM_USERS]; + + ScheduleList &Get(uint16_t userIndex) { return mScheduleIndexes[userIndex - 1]; } + }; + + using HolidayScheduleIndexes = DoorLockData::IndexList; +#endif /* CONFIG_LOCK_SCHEDULES */ + CredentialsManager() = default; ~CredentialsManager() = default; CredentialsManager(const CredentialsManager &) = delete; @@ -202,7 +235,12 @@ template class CredentialsManager CredentialsManager &operator=(CredentialsManager &) = delete; void InitializeUsers(); - void LoadFromPersistentStorage(); + void InitializeSchedules(); + void LoadUsersFromPersistentStorage(); + void LoadCredentialsFromPersistentStorage(); +#ifdef CONFIG_LOCK_SCHEDULES + void LoadSchedulesFromPersistentStorage(); +#endif static CHIP_ERROR GetCredentialUserId(uint16_t credentialIndex, CredentialTypeEnum credentialType, uint32_t &userId); @@ -227,5 +265,18 @@ template class CredentialsManager DoorLockData::User mUsers[CONFIG_LOCK_MAX_NUM_USERS] = {}; UsersIndexes mUsersIndexes{}; +#ifdef CONFIG_LOCK_SCHEDULES + DoorLockData::Schedule + mWeekDaySchedule[CONFIG_LOCK_MAX_NUM_USERS][CONFIG_LOCK_MAX_WEEKDAY_SCHEDULES_PER_USER] = {}; + WeekDayScheduleIndexes mWeekDayScheduleIndexes = {}; + + DoorLockData::Schedule + mYearDaySchedule[CONFIG_LOCK_MAX_NUM_USERS][CONFIG_LOCK_MAX_YEARDAY_SCHEDULES_PER_USER] = {}; + YearDayScheduleIndexes mYearDayScheduleIndexes = {}; + + DoorLockData::Schedule mHolidaySchedule[CONFIG_LOCK_MAX_HOLIDAY_SCHEDULES]; + HolidayScheduleIndexes mHolidayScheduleIndexes = {}; +#endif /* CONFIG_LOCK_SCHEDULES */ + bool mRequirePINForRemoteOperation{ false }; }; diff --git a/samples/matter/lock/src/credentials/credentials_manager_credentials.cpp b/samples/matter/lock/src/credentials/credentials_manager_credentials.cpp new file mode 100644 index 000000000000..64fb0ea943b1 --- /dev/null +++ b/samples/matter/lock/src/credentials/credentials_manager_credentials.cpp @@ -0,0 +1,282 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include "credentials_manager.h" +#include "credentials_storage.h" + +#include + +#include + +LOG_MODULE_DECLARE(cr_manager, CONFIG_CHIP_APP_LOG_LEVEL); + +using namespace chip; +using namespace DoorLockData; + +template +bool CredentialsManager::GetCredential(uint16_t credentialIndex, CredentialTypeEnum credentialType, + EmberAfPluginDoorLockCredentialInfo &credential) +{ + VerifyOrReturnError(credentialIndex > 0 && credentialIndex <= CONFIG_LOCK_MAX_NUM_CREDENTIALS_PER_TYPE, false); + VerifyOrReturnError(CHIP_NO_ERROR == mCredentials.GetCredentials(credentialType, credential, credentialIndex), + false); + + return true; +} + +template +bool CredentialsManager::SetCredential(uint16_t credentialIndex, FabricIndex creator, + FabricIndex modifier, DlCredentialStatus credentialStatus, + CredentialTypeEnum credentialType, const ByteSpan &secret) +{ + /* Programming PIN is a special case - it is unique and its index assumed to be 0, currently we do not + * support it */ + VerifyOrReturnError(credentialType != CredentialTypeEnum::kProgrammingPIN, false); + VerifyOrReturnError(credentialIndex > 0 && credentialIndex <= CONFIG_LOCK_MAX_NUM_CREDENTIALS_PER_TYPE, false); + VerifyOrReturnError(secret.size() <= DoorLockData::kMaxCredentialLength, false); + + bool success{ false }; + auto &credentials = mCredentials.GetCredentialsTypes(credentialType, success); + VerifyOrReturnError(success, false); + auto &credential = credentials[credentialIndex - 1]; + return DoSetCredential(credential, credentialIndex, creator, modifier, credentialStatus, credentialType, + secret); +} + +template +bool CredentialsManager::DoSetCredential(DoorLockData::Credential &credential, uint16_t credentialIndex, + FabricIndex creator, FabricIndex modifier, + DlCredentialStatus credentialStatus, + CredentialTypeEnum credentialType, const ByteSpan &secret) +{ + uint32_t uniqueUserId; + credential.mInfo.mFields.mStatus = static_cast(credentialStatus); + credential.mInfo.mFields.mCredentialType = static_cast(credentialType); + credential.mInfo.mFields.mCreationSource = static_cast(DlAssetSource::kMatterIM); + credential.mInfo.mFields.mCreatedBy = static_cast(creator); + credential.mInfo.mFields.mModificationSource = static_cast(DlAssetSource::kMatterIM); + credential.mInfo.mFields.mLastModifiedBy = static_cast(modifier); + + if (!secret.empty()) { + credential.mSecret.mDataLength = secret.size(); + memcpy(credential.mSecret.mData, secret.data(), secret.size()); + } else if (CHIP_NO_ERROR == Instance().GetCredentialUserId(credentialIndex, credentialType, uniqueUserId)) { + /* Credential already exists, and the new secret is empty, so the credential is being cleared. + * Inform the higher layer about clearing the credential and provide current credential data. + */ + if (Instance().mClearCredentialCallback && DlCredentialStatus::kAvailable == credentialStatus) { + ByteSpan existingSecretToClear = { credential.mSecret.mData, credential.mSecret.mDataLength }; + Instance().mClearCredentialCallback(credentialIndex, credentialType, uniqueUserId, + existingSecretToClear); + } + memset(credential.mSecret.mData, 0, credential.mSecret.mDataLength); + credential.mSecret.mDataLength = 0; + } + + uint8_t credentialSerialized[DoorLockData::Credential::RequiredBufferSize()] = { 0 }; + size_t serializedSize = credential.Serialize(credentialSerialized, sizeof(credentialSerialized)); + + uint8_t credentialIndexesSerialized[CredentialsIndexes::CredentialList::RequiredBufferSize()] = { 0 }; + size_t credIndexesSerializedSize{ 0 }; + + /* Process the credential indexes only if the credential itself was properly serialized. */ + if (0 < serializedSize) { + auto &credIndexes = + Instance().mCredentialsIndexes.Get(static_cast(credentialType)); + + if (CHIP_ERROR_BUFFER_TOO_SMALL == credIndexes.AddIndex(credentialIndex)) { + return false; + } + + credIndexesSerializedSize = + credIndexes.Serialize(credentialIndexesSerialized, sizeof(credentialIndexesSerialized)); + } + + /* Store to persistent storage */ + if (0 < serializedSize && 0 < credIndexesSerializedSize) { + if (!CredentialsStorage::Instance().Store( + CredentialsStorage::Type::Credential, credentialSerialized, serializedSize, + static_cast(credentialType), credentialIndex)) { + LOG_ERR("Cannot store given credentials Type: %d Idx: %d", + static_cast(credentialType), credentialIndex); + } else { + if (!CredentialsStorage::Instance().Store( + CredentialsStorage::Type::CredentialsIndexes, credentialIndexesSerialized, + credIndexesSerializedSize, static_cast(credentialType))) { + LOG_ERR("Cannot store credentials counter. The persistent database will be corrupted."); + } + } + } else { + LOG_ERR("Cannot serialize given credentials Type: %d Idx: %d", static_cast(credentialType), + credentialIndex); + } + + LOG_INF("Setting lock credential %u: %s", static_cast(credentialIndex), + credential.mInfo.mFields.mStatus == static_cast(DlCredentialStatus::kAvailable) ? "available" : + "occupied"); + + if (CHIP_NO_ERROR == Instance().GetCredentialUserId(credentialIndex, credentialType, uniqueUserId)) { + if (Instance().mSetCredentialCallback && credential.mSecret.mDataLength > 0) { + Instance().mSetCredentialCallback(credentialIndex, credentialType, uniqueUserId); + } + } + + return true; +} + +template +CHIP_ERROR CredentialsManager::GetCredentialUserId(uint16_t credentialIndex, + CredentialTypeEnum credentialType, uint32_t &userId) +{ + for (size_t idxUsr = 0; idxUsr < CONFIG_LOCK_MAX_NUM_USERS; ++idxUsr) { + auto &user = Instance().mUsers[idxUsr]; + for (size_t idxCred = 0; idxCred < CONFIG_LOCK_MAX_NUM_CREDENTIALS_PER_USER; ++idxCred) { + auto &credentialStruct = user.mOccupiedCredentials.mData[idxCred]; + if (credentialStruct.credentialIndex == credentialIndex && + credentialStruct.credentialType == credentialType) { + userId = user.mInfo.mFields.mUserUniqueId; + return CHIP_NO_ERROR; + } + } + } + return CHIP_ERROR_NOT_FOUND; +} + +#ifdef CONFIG_LOCK_LEAVE_FABRIC_CLEAR_CREDENTIAL +template bool CredentialsManager::ClearAllCredentialsFromFabric() +{ + return mCredentials.ForEach([](DoorLockData::Credential &credential, uint8_t credIdx) { + /* At this point the door-lock-server already invalidated both mCreatedBy and mLastModifiedBy + of all credentials assigned to the fabric which is currently being removed */ + if (credential.mInfo.mFields.mCreatedBy == kUndefinedFabricIndex || + credential.mInfo.mFields.mLastModifiedBy == kUndefinedFabricIndex) { + return Instance().ClearCredential(credential, credIdx); + } + return true; + }); +} + +template +bool CredentialsManager::ClearCredential(DoorLockData::Credential &credential, uint8_t credIdx) +{ + credIdx++; /* DoSetCredential expects indexes starting from 1 */ + return DoSetCredential(credential, credIdx, kUndefinedFabricIndex, kUndefinedFabricIndex, + DlCredentialStatus::kAvailable, + static_cast(credential.mInfo.mFields.mCredentialType), + chip::ByteSpan()); +} +#endif + +template void CredentialsManager::LoadCredentialsFromPersistentStorage() +{ + uint8_t credentialData[DoorLockData::Credential::RequiredBufferSize()] = { 0 }; + uint8_t credentialIndexesSerialized[CredentialsIndexes::CredentialList::RequiredBufferSize()] = { 0 }; + size_t outSize{ 0 }; + + for (uint8_t type = CredentialTypeIndex::Pin; type <= CredentialTypeIndex::Max; ++type) { + bool success{ false }; + auto &credentials = mCredentials.GetCredentialsTypes(static_cast(type), success); + + if (!success) { + continue; + } + outSize = 0; + if (!CredentialsStorage::Instance().Load(CredentialsStorage::Type::CredentialsIndexes, + credentialIndexesSerialized, + sizeof(credentialIndexesSerialized), outSize, type)) { + LOG_INF("No stored indexes for credential of type: %d", type); + continue; + } + + auto &credIndexes = mCredentialsIndexes.Get(static_cast(type)); + if (CHIP_NO_ERROR != credIndexes.Deserialize(credentialIndexesSerialized, outSize)) { + LOG_ERR("Cannot deserialize indexes for credentials of type: %d", type); + continue; + } + + for (size_t idx = 0; idx < credIndexes.mList.mLength; idx++) { + /* Read the actual index from the indexList */ + uint16_t credentialIndex = credIndexes.mList.mIndexes[idx]; + outSize = 0; + if (!CredentialsStorage::Instance().Load(CredentialsStorage::Type::Credential, credentialData, + sizeof(credentialData), outSize, type, + credentialIndex)) { + LOG_ERR("Cannot load credentials of type %d for index: %d", static_cast(type), + credentialIndex); + } + + if (CHIP_NO_ERROR != credentials[credentialIndex - 1].Deserialize(credentialData, outSize)) { + LOG_ERR("Cannot deserialize credentials of type %d for index: %d", + static_cast(type), credentialIndex); + } + } + } + outSize = 0; + if (!CredentialsStorage::Instance().Load(CredentialsStorage::Type::RequirePIN, &mRequirePINForRemoteOperation, + sizeof(mRequirePINForRemoteOperation), outSize) || + outSize != sizeof(mRequirePINForRemoteOperation)) { + LOG_DBG("Cannot load RequirePINforRemoteOperation"); + } +} + +#ifdef CONFIG_LOCK_ENABLE_DEBUG + +template +const char *CredentialsManager::GetCredentialName(CredentialTypeEnum type) +{ + switch (type) { + case CredentialTypeEnum::kPin: + return "PIN"; + case CredentialTypeEnum::kRfid: + return "RFID"; + case CredentialTypeEnum::kFingerprint: + return "Fingerprint"; + case CredentialTypeEnum::kFingerVein: + return "Finger vein"; + case CredentialTypeEnum::kFace: + return "Face"; + default: + return "None"; + } +} + +template +void CredentialsManager::PrintCredential(CredentialTypeEnum type, uint16_t index) +{ + bool success{ false }; + auto &credentialList = mCredentials.GetCredentialsTypes(type, success); + + if (!success) { + LOG_INF("%s credential type is not supported. Nothing to print.", GetCredentialName(type)); + return; + } + + /* User is going to provide an index which is started from 1 instead of 0 */ + auto arrayIdx = index - 1; + + LOG_INF("\t -- Index: %d", index); + LOG_INF("\t -- Type: %s", GetCredentialName(type)); + LOG_INF("\t\t -- Status: %s", + static_cast(DlCredentialStatus::kAvailable) == credentialList[arrayIdx].mInfo.mFields.mStatus ? + "available" : + "occupied"); + LOG_INF("\t\t -- Creation source: %d", credentialList[arrayIdx].mInfo.mFields.mCreationSource); + LOG_INF("\t\t -- Created by: %d", credentialList[arrayIdx].mInfo.mFields.mCreatedBy); + LOG_INF("\t\t -- Modification source: %d", credentialList[arrayIdx].mInfo.mFields.mModificationSource); + LOG_INF("\t\t -- Modified by: %d", credentialList[arrayIdx].mInfo.mFields.mLastModifiedBy); + LOG_HEXDUMP_INF(credentialList[arrayIdx].mSecret.mData, credentialList[arrayIdx].mSecret.mDataLength, + "\t\t -- Credential data:"); +} + +#endif + +/* Explicitly instantiate supported template variants to avoid linker errors. */ +template class CredentialsManager; +template class CredentialsManager; +template class CredentialsManager; +template class CredentialsManager; +template class CredentialsManager; diff --git a/samples/matter/lock/src/credentials/credentials_manager_schedules.cpp b/samples/matter/lock/src/credentials/credentials_manager_schedules.cpp new file mode 100644 index 000000000000..b34ff223fa80 --- /dev/null +++ b/samples/matter/lock/src/credentials/credentials_manager_schedules.cpp @@ -0,0 +1,386 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#ifdef CONFIG_LOCK_SCHEDULES + +#include "credentials/credentials_data_types.h" +#include "credentials_manager.h" +#include "credentials_storage.h" + +#include + +#include + +LOG_MODULE_DECLARE(cr_manager, CONFIG_CHIP_APP_LOG_LEVEL); + +using namespace chip; +using namespace DoorLockData; + +template +DlStatus CredentialsManager::GetWeekDaySchedule(uint8_t weekdayIndex, uint16_t userIndex, + EmberAfPluginDoorLockWeekDaySchedule &schedule) +{ + VerifyOrReturnError(userIndex > 0 && userIndex <= CONFIG_LOCK_MAX_NUM_USERS, DlStatus::kFailure); + VerifyOrReturnError(weekdayIndex > 0 && weekdayIndex <= CONFIG_LOCK_MAX_WEEKDAY_SCHEDULES_PER_USER, + DlStatus::kFailure); + + if (CHIP_NO_ERROR != mWeekDaySchedule[userIndex - 1][weekdayIndex - 1].ConvertToPlugin(schedule)) { + return DlStatus::kNotFound; + } + + return DlStatus::kSuccess; +} + +template +DlStatus CredentialsManager::SetWeekDaySchedule(uint8_t weekdayIndex, uint16_t userIndex, + DlScheduleStatus status, DaysMaskMap daysMask, + uint8_t startHour, uint8_t startMinute, uint8_t endHour, + uint8_t endMinute) +{ + VerifyOrReturnError(userIndex > 0 && userIndex <= CONFIG_LOCK_MAX_NUM_USERS, DlStatus::kFailure); + VerifyOrReturnError(weekdayIndex > 0 && weekdayIndex <= CONFIG_LOCK_MAX_WEEKDAY_SCHEDULES_PER_USER, + DlStatus::kFailure); + + auto &schedule = mWeekDaySchedule[userIndex - 1][weekdayIndex - 1]; + + schedule.mScheduleParams.mWeekDay.mFields.mDaysMask = static_cast(daysMask); + schedule.mScheduleParams.mWeekDay.mFields.mStartHour = startHour; + schedule.mScheduleParams.mWeekDay.mFields.mStartMinute = startMinute; + schedule.mScheduleParams.mWeekDay.mFields.mEndHour = endHour; + schedule.mScheduleParams.mWeekDay.mFields.mEndMinute = endMinute; + + uint8_t scheduleSerialized[sizeof( + DoorLockData::Schedule::ScheduleParams::WeekDay)] = { 0 }; + size_t serializedSize = schedule.Serialize(scheduleSerialized, sizeof(scheduleSerialized)); + + uint8_t scheduleIndexesSerialized[WeekDayScheduleIndexes::ScheduleList::RequiredBufferSize()] = { 0 }; + size_t serializedIndexesSize{ 0 }; + + if (0 < serializedSize) { + auto &scheduleIndexes = mWeekDayScheduleIndexes.Get(userIndex); + if (CHIP_ERROR_BUFFER_TOO_SMALL == scheduleIndexes.AddIndex(weekdayIndex)) { + return DlStatus::kFailure; + } + + serializedIndexesSize = + scheduleIndexes.Serialize(scheduleIndexesSerialized, sizeof(scheduleIndexesSerialized)); + } + + /* Store to persistent storage */ + if (0 < serializedSize && 0 < serializedIndexesSize) { + if (!CredentialsStorage::Instance().Store(CredentialsStorage::Type::WeekDaySchedule, scheduleSerialized, + serializedSize, userIndex, weekdayIndex)) { + LOG_ERR("Cannot store WeekDaySchedule"); + return DlStatus::kFailure; + } else if (!CredentialsStorage::Instance().Store(CredentialsStorage::Type::WeekDayScheduleIndexes, + scheduleIndexesSerialized, serializedIndexesSize, + userIndex)) { + LOG_ERR("Cannot store WeekDaySchedule counter. The persistent database will be corrupted."); + return DlStatus::kFailure; + } + } + + return DlStatus::kSuccess; +} + +template +DlStatus CredentialsManager::GetYearDaySchedule(uint8_t yearDayIndex, uint16_t userIndex, + EmberAfPluginDoorLockYearDaySchedule &schedule) +{ + VerifyOrReturnError(userIndex > 0 && userIndex <= CONFIG_LOCK_MAX_NUM_USERS, DlStatus::kFailure); + VerifyOrReturnError(yearDayIndex > 0 && yearDayIndex <= CONFIG_LOCK_MAX_YEARDAY_SCHEDULES_PER_USER, + DlStatus::kFailure); + + if (CHIP_NO_ERROR != mYearDaySchedule[userIndex - 1][yearDayIndex - 1].ConvertToPlugin(schedule)) { + return DlStatus::kNotFound; + } + + return DlStatus::kSuccess; +} + +template +DlStatus CredentialsManager::SetYearDaySchedule(uint8_t yeardayIndex, uint16_t userIndex, + DlScheduleStatus status, uint32_t localStartTime, + uint32_t localEndTime) +{ + VerifyOrReturnError(userIndex > 0 && userIndex <= CONFIG_LOCK_MAX_NUM_USERS, DlStatus::kFailure); + VerifyOrReturnError(yeardayIndex > 0 && yeardayIndex <= CONFIG_LOCK_MAX_YEARDAY_SCHEDULES_PER_USER, + DlStatus::kFailure); + + auto &schedule = mYearDaySchedule[userIndex - 1][yeardayIndex - 1]; + + schedule.mScheduleParams.mYearDay.mFields.mLocalStartTime = localStartTime; + schedule.mScheduleParams.mYearDay.mFields.mLocalEndTime = localEndTime; + + uint8_t scheduleSerialized[sizeof( + DoorLockData::Schedule::ScheduleParams::YearDay)] = { 0 }; + size_t serializedSize = schedule.Serialize(scheduleSerialized, sizeof(scheduleSerialized)); + + uint8_t scheduleIndexesSerialized[YearDayScheduleIndexes::ScheduleList::RequiredBufferSize()] = { 0 }; + size_t serializedIndexesSize{ 0 }; + + if (0 < serializedSize) { + auto &scheduleIndexes = mYearDayScheduleIndexes.Get(userIndex); + if (CHIP_ERROR_BUFFER_TOO_SMALL == scheduleIndexes.AddIndex(yeardayIndex)) { + return DlStatus::kFailure; + } + + serializedIndexesSize = + scheduleIndexes.Serialize(scheduleIndexesSerialized, sizeof(scheduleIndexesSerialized)); + } + + /* Store to persistent storage */ + if (0 < serializedSize && 0 < serializedIndexesSize) { + if (!CredentialsStorage::Instance().Store(CredentialsStorage::Type::YearDaySchedule, scheduleSerialized, + serializedSize, userIndex, yeardayIndex)) { + LOG_ERR("Cannot store YearDaySchedule"); + return DlStatus::kFailure; + } else if (!CredentialsStorage::Instance().Store(CredentialsStorage::Type::YearDayScheduleIndexes, + scheduleIndexesSerialized, serializedIndexesSize, + userIndex)) { + LOG_ERR("Cannot store YearDaySchedule counter. The persistent database will be corrupted."); + return DlStatus::kFailure; + } + } + + return DlStatus::kSuccess; +} + +template +DlStatus CredentialsManager::GetHolidaySchedule(uint8_t holidayIndex, + EmberAfPluginDoorLockHolidaySchedule &schedule) +{ + VerifyOrReturnError(holidayIndex > 0 && holidayIndex <= CONFIG_LOCK_MAX_HOLIDAY_SCHEDULES, DlStatus::kFailure); + + if (CHIP_NO_ERROR != mHolidaySchedule[holidayIndex - 1].ConvertToPlugin(schedule)) { + return DlStatus::kNotFound; + } + + return DlStatus::kSuccess; +} + +template +DlStatus CredentialsManager::SetHolidaySchedule(uint8_t holidayIndex, DlScheduleStatus status, + uint32_t localStartTime, uint32_t localEndTime, + OperatingModeEnum operatingMode) +{ + VerifyOrReturnError(holidayIndex > 0 && holidayIndex <= CONFIG_LOCK_MAX_HOLIDAY_SCHEDULES, DlStatus::kFailure); + + auto &schedule = mHolidaySchedule[holidayIndex - 1]; + + schedule.mScheduleParams.mHoliday.mFields.mLocalStartTime = localStartTime; + schedule.mScheduleParams.mHoliday.mFields.mLocalEndTime = localEndTime; + schedule.mScheduleParams.mHoliday.mFields.mOperatingMode = static_cast(operatingMode); + + uint8_t scheduleSerialized[sizeof( + DoorLockData::Schedule::ScheduleParams::Holiday)] = { 0 }; + size_t serializedSize = schedule.Serialize(scheduleSerialized, sizeof(scheduleSerialized)); + + uint8_t scheduleIndexesSerialized[HolidayScheduleIndexes::RequiredBufferSize()] = { 0 }; + size_t serializedIndexesSize{ 0 }; + + if (0 < serializedSize) { + if (CHIP_ERROR_BUFFER_TOO_SMALL == mHolidayScheduleIndexes.AddIndex(holidayIndex)) { + return DlStatus::kFailure; + } + + serializedIndexesSize = + mHolidayScheduleIndexes.Serialize(scheduleIndexesSerialized, sizeof(scheduleIndexesSerialized)); + } + + /* Store to persistent storage */ + if (0 < serializedSize && 0 < serializedIndexesSize) { + if (!CredentialsStorage::Instance().Store(CredentialsStorage::Type::HolidaySchedule, scheduleSerialized, + serializedSize, holidayIndex)) { + LOG_ERR("Cannot store HolidaySchedule"); + return DlStatus::kFailure; + } else if (!CredentialsStorage::Instance().Store(CredentialsStorage::Type::HolidayScheduleIndexes, + scheduleIndexesSerialized, serializedIndexesSize)) { + LOG_ERR("Cannot store HolidaySchedule counter. The persistent database will be corrupted."); + return DlStatus::kFailure; + } + } + + return DlStatus::kSuccess; +} + +template void CredentialsManager::LoadSchedulesFromPersistentStorage() +{ + /* Load all Schedules from persistent storage */ + bool scheduleFound{ false }; + size_t outSize{ 0 }; + uint16_t scheduleIndex = 0; + + uint8_t scheduleWeekDayIndexesSerialized[WeekDayScheduleIndexes::ScheduleList::RequiredBufferSize()] = { 0 }; + uint8_t scheduleWeekDayData[sizeof(DoorLockData::Schedule::ScheduleParams::WeekDay)] = { + 0 + }; + + uint8_t scheduleYearDayIndexesSerialized[YearDayScheduleIndexes::ScheduleList::RequiredBufferSize()] = { 0 }; + uint8_t scheduleYearDayData[sizeof(DoorLockData::Schedule::ScheduleParams::YearDay)] = { + 0 + }; + + for (size_t userIndex = 1; userIndex <= CONFIG_LOCK_MAX_NUM_USERS; userIndex++) { + scheduleFound = false; + /* Load WeekDay schedules */ + if (!CredentialsStorage::Instance().Load( + CredentialsStorage::Type::WeekDayScheduleIndexes, scheduleWeekDayIndexesSerialized, + sizeof(scheduleWeekDayIndexesSerialized), outSize, userIndex)) { + } else { + if (CHIP_NO_ERROR == mWeekDayScheduleIndexes.Get(userIndex).Deserialize( + scheduleWeekDayIndexesSerialized, outSize)) { + scheduleFound = true; +#if CONFIG_LOCK_ENABLE_DEBUG + LOG_INF("Found WeekDay schedule indexes for user index: %d", userIndex); +#endif + } + } + + if (scheduleFound) { + auto &scheduleIndexes = mWeekDayScheduleIndexes.Get(userIndex); + for (size_t scheduleIdx = 0; scheduleIdx < scheduleIndexes.mList.mLength; scheduleIdx++) { + /* Read the actual index from the indexList */ + scheduleIndex = scheduleIndexes.mList.mIndexes[scheduleIdx]; + LOG_ERR("Checking schedule index: %d", scheduleIndex); + if (!CredentialsStorage::Instance().Load( + CredentialsStorage::Type::WeekDaySchedule, scheduleWeekDayData, + sizeof(scheduleWeekDayData), outSize, userIndex, scheduleIndex)) { + } else { + LOG_ERR("Found schedule index: %d", scheduleIndex); + if (CHIP_NO_ERROR != + mWeekDaySchedule[userIndex - 1][scheduleIndex - 1].Deserialize( + scheduleWeekDayData, outSize)) { + LOG_ERR("Cannot deserialize WeekDay Schedule %d for user index: %d", + scheduleIndex, userIndex); + } + } + } + } + + scheduleFound = false; + + /* Load YearDay schedules */ + if (!CredentialsStorage::Instance().Load( + CredentialsStorage::Type::YearDayScheduleIndexes, scheduleYearDayIndexesSerialized, + sizeof(scheduleYearDayIndexesSerialized), outSize, userIndex)) { + } else { + if (CHIP_NO_ERROR == mYearDayScheduleIndexes.Get(userIndex).Deserialize( + scheduleYearDayIndexesSerialized, outSize)) { + scheduleFound = true; +#if CONFIG_LOCK_ENABLE_DEBUG + LOG_INF("Found YeardDay schedule indexes for user index: %d", userIndex); +#endif + } + } + + if (scheduleFound) { + auto &scheduleIndexes = mYearDayScheduleIndexes.Get(userIndex); + for (size_t scheduleIdx = 0; scheduleIdx < scheduleIndexes.mList.mLength; scheduleIdx++) { + /* Read the actual index from the indexList */ + scheduleIndex = scheduleIndexes.mList.mIndexes[scheduleIdx]; + if (!CredentialsStorage::Instance().Load( + CredentialsStorage::Type::YearDaySchedule, scheduleYearDayData, + sizeof(scheduleYearDayData), outSize, userIndex, scheduleIndex)) { + } else { + if (CHIP_NO_ERROR != + mYearDaySchedule[userIndex - 1][scheduleIndex - 1].Deserialize( + scheduleYearDayData, outSize)) { + LOG_ERR("Cannot deserialize YearDay Schedule %d for user index: %d", + scheduleIndex, userIndex); + } + } + } + } + } + + scheduleFound = false; + + /* Load Holiday schedules */ + uint8_t scheduleHolidayIndexesSerialized[HolidayScheduleIndexes::RequiredBufferSize()] = { 0 }; + uint8_t scheduleHolidayData[sizeof(DoorLockData::Schedule::ScheduleParams::Holiday)] = { + 0 + }; + + if (!CredentialsStorage::Instance().Load(CredentialsStorage::Type::HolidayScheduleIndexes, + scheduleHolidayIndexesSerialized, + sizeof(scheduleHolidayIndexesSerialized), outSize, 0)) { + } else { + if (CHIP_NO_ERROR == mHolidayScheduleIndexes.Deserialize(scheduleHolidayIndexesSerialized, outSize)) { + scheduleFound = true; +#if CONFIG_LOCK_ENABLE_DEBUG + LOG_INF("Found Holiday schedule indexes"); +#endif + } + } + for (size_t scheduleIdx = 0; scheduleIdx < mHolidayScheduleIndexes.mList.mLength; scheduleIdx++) { + /* Read the actual index from the indexList */ + scheduleIndex = mHolidayScheduleIndexes.mList.mIndexes[scheduleIdx]; + if (!CredentialsStorage::Instance().Load(CredentialsStorage::Type::HolidaySchedule, scheduleHolidayData, + sizeof(scheduleHolidayData), outSize, scheduleIndex)) { + } else { + if (CHIP_NO_ERROR != + mHolidaySchedule[scheduleIndex - 1].Deserialize(scheduleHolidayData, outSize)) { + LOG_ERR("Cannot deserialize Holiday Schedule %d ", scheduleIndex); + } + } + } +} + +#ifdef CONFIG_LOCK_ENABLE_DEBUG + +template +void CredentialsManager::PrintSchedule(DoorLockData::ScheduleType scheduleType, uint16_t scheduleIndex, + uint16_t userIndex) +{ + switch (scheduleType) { + case DoorLockData::ScheduleType::WeekDay: { + auto *schedule = &mWeekDaySchedule[userIndex][scheduleIndex]; + if (schedule) { + LOG_INF("\t -- WeekDay Schedule for user %d", userIndex); + LOG_INF("\t\t -- Days Mask: %d", schedule->mScheduleParams.mWeekDay.mFields.mDaysMask); + LOG_INF("\t\t -- Start Hour: %d", schedule->mScheduleParams.mWeekDay.mFields.mStartHour); + LOG_INF("\t\t -- Start Minute: %d", schedule->mScheduleParams.mWeekDay.mFields.mStartMinute); + LOG_INF("\t\t -- End Hour: %d", schedule->mScheduleParams.mWeekDay.mFields.mEndHour); + LOG_INF("\t\t -- End Minute: %d", schedule->mScheduleParams.mWeekDay.mFields.mEndMinute); + } + } break; + case DoorLockData::ScheduleType::YearDay: { + auto *schedule = &mYearDaySchedule[userIndex][scheduleIndex]; + if (schedule) { + LOG_INF("\t -- YearDay Schedule for user %d", userIndex); + LOG_INF("\t\t -- Local Start Time: %d", + schedule->mScheduleParams.mYearDay.mFields.mLocalStartTime); + LOG_INF("\t\t -- Local End Time: %d", schedule->mScheduleParams.mYearDay.mFields.mLocalEndTime); + } + } break; + case DoorLockData::ScheduleType::Holiday: { + auto *schedule = &mHolidaySchedule[scheduleIndex]; + if (schedule) { + LOG_INF("\t -- Holiday Schedule"); + LOG_INF("\t\t -- Local Start Time: %d", + schedule->mScheduleParams.mHoliday.mFields.mLocalStartTime); + LOG_INF("\t\t -- Local End Time: %d", schedule->mScheduleParams.mHoliday.mFields.mLocalEndTime); + LOG_INF("\t\t -- Operating Mode: %d", + schedule->mScheduleParams.mHoliday.mFields.mOperatingMode); + } + } break; + default: + break; + } +} + +#endif + +/* Explicitly instantiate supported template variants to avoid linker errors. */ +template class CredentialsManager; +template class CredentialsManager; +template class CredentialsManager; +template class CredentialsManager; +template class CredentialsManager; + +#endif /* CONFIG_LOCK_SCHEDULES */ diff --git a/samples/matter/lock/src/credentials/credentials_manager_users.cpp b/samples/matter/lock/src/credentials/credentials_manager_users.cpp new file mode 100644 index 000000000000..7f6035b36332 --- /dev/null +++ b/samples/matter/lock/src/credentials/credentials_manager_users.cpp @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2024 Nordic Semiconductor ASA + * + * SPDX-License-Identifier: LicenseRef-Nordic-5-Clause + */ + +#include "credentials_manager.h" +#include "credentials_storage.h" + +#include + +#include + +LOG_MODULE_DECLARE(cr_manager, CONFIG_CHIP_APP_LOG_LEVEL); + +using namespace chip; +using namespace DoorLockData; + +template +bool CredentialsManager::GetUserInfo(uint16_t userIndex, EmberAfPluginDoorLockUserInfo &user) +{ + /* userIndex is guaranteed by the caller to be between 1 and CONFIG_LOCK_MAX_NUM_USERS */ + VerifyOrReturnError(userIndex > 0 && userIndex <= CONFIG_LOCK_MAX_NUM_USERS, false); + if (CHIP_NO_ERROR != mUsers[userIndex - 1].ConvertToPlugin(user)) { + return false; + } + + return true; +} + +template +bool CredentialsManager::SetUser(uint16_t userIndex, FabricIndex creator, FabricIndex modifier, + const CharSpan &userName, uint32_t uniqueId, UserStatusEnum userStatus, + UserTypeEnum userType, CredentialRuleEnum credentialRule, + const CredentialStruct *credentials, size_t totalCredentials) +{ + /* userIndex is guaranteed by the caller to be between 1 and CONFIG_LOCK_MAX_NUM_USERS */ + VerifyOrReturnError(userIndex > 0 && userIndex <= CONFIG_LOCK_MAX_NUM_USERS, false); + auto &user = mUsers[userIndex - 1]; + + VerifyOrReturnError(userName.size() <= DOOR_LOCK_MAX_USER_NAME_SIZE, false); + VerifyOrReturnError(totalCredentials <= CONFIG_LOCK_MAX_NUM_CREDENTIALS_PER_USER, false); + + user.mName.mSize = userName.size(); + memcpy(user.mName.mValue, userName.data(), userName.size()); + + for (size_t i = 0; i < totalCredentials; ++i) { + auto *currentCredentials = credentials + i; + memcpy(&user.mOccupiedCredentials.mData[i], currentCredentials, sizeof(CredentialStruct)); + } + user.mOccupiedCredentials.mSize = totalCredentials * sizeof(CredentialStruct); + + user.mInfo.mFields.mUserUniqueId = uniqueId; + user.mInfo.mFields.mUserStatus = static_cast(userStatus); + user.mInfo.mFields.mUserType = static_cast(userType); + user.mInfo.mFields.mCredentialRule = static_cast(credentialRule); + user.mInfo.mFields.mCreationSource = static_cast(DlAssetSource::kMatterIM); + user.mInfo.mFields.mCreatedBy = static_cast(creator); + user.mInfo.mFields.mModificationSource = static_cast(DlAssetSource::kMatterIM); + user.mInfo.mFields.mLastModifiedBy = static_cast(modifier); + + uint8_t userSerialized[DoorLockData::User::RequiredBufferSize()] = { 0 }; + size_t serializedSize = user.Serialize(userSerialized, sizeof(userSerialized)); + + uint8_t userIndexesSerialized[UsersIndexes::RequiredBufferSize()] = { 0 }; + size_t serializedIndexesSize{ 0 }; + + /* Process the user indexes only if the user itself was properly serialized. */ + if (0 < serializedSize) { + if (CHIP_ERROR_BUFFER_TOO_SMALL == mUsersIndexes.AddIndex(userIndex)) { + return false; + } + serializedIndexesSize = mUsersIndexes.Serialize(userIndexesSerialized, sizeof(userIndexesSerialized)); + } + + /* Store to persistent storage */ + if (0 < serializedSize && 0 < serializedIndexesSize) { + if (!CredentialsStorage::Instance().Store(CredentialsStorage::Type::User, userSerialized, + serializedSize, userIndex)) { + LOG_ERR("Cannot store given User Idx: %d", userIndex); + return false; + } else { + if (!CredentialsStorage::Instance().Store(CredentialsStorage::Type::UsersIndexes, + userIndexesSerialized, serializedIndexesSize, 0)) { + LOG_ERR("Cannot store users counter. The persistent database will be corrupted."); + } + } + } else { + LOG_ERR("Cannot serialize given User Idx: %d", userIndex); + } + + LOG_INF("Setting lock user %u: %s", static_cast(userIndex), + userStatus == UserStatusEnum::kAvailable ? "available" : "occupied"); + + return true; +} + +template void CredentialsManager::LoadUsersFromPersistentStorage() +{ + /* Load all Users from persistent storage */ + uint8_t userIndexesSerialized[UsersIndexes::RequiredBufferSize()] = { 0 }; + uint8_t userData[DoorLockData::User::RequiredBufferSize()] = { 0 }; + bool usersFound{ false }; + size_t outSize{ 0 }; + + if (!CredentialsStorage::Instance().Load(CredentialsStorage::Type::UsersIndexes, userIndexesSerialized, + sizeof(userIndexesSerialized), outSize, 0)) { + LOG_INF("No users indexes stored"); + } else { + if (CHIP_NO_ERROR == mUsersIndexes.Deserialize(userIndexesSerialized, outSize)) { + usersFound = true; + } + } + + if (usersFound) { + for (size_t idx = 0; idx < mUsersIndexes.mList.mLength; idx++) { + /* Read the actual index from the indexList */ + uint16_t userIndex = mUsersIndexes.mList.mIndexes[idx]; + outSize = 0; + if (!CredentialsStorage::Instance().Load(CredentialsStorage::Type::User, userData, + sizeof(userData), outSize, userIndex)) { + } else { + if (CHIP_NO_ERROR != mUsers[userIndex - 1].Deserialize(userData, outSize)) { + LOG_ERR("Cannot deserialize User index: %d", userIndex); + } + } + } + } +} + +#ifdef CONFIG_LOCK_ENABLE_DEBUG + +template void CredentialsManager::PrintUser(uint16_t userIndex) +{ + auto *user = &mUsers[userIndex - 1]; + + if (user) { + LOG_INF("\t -- User %d: %s", userIndex, user->mName.mValue); + LOG_INF("\t\t -- ID: %d", user->mInfo.mFields.mUserUniqueId); + LOG_INF("\t\t -- Type: %d", user->mInfo.mFields.mUserType); + LOG_INF("\t\t -- Status: %s", + static_cast(user->mInfo.mFields.mUserStatus) == UserStatusEnum::kAvailable ? + "available" : + "occupied"); + LOG_INF("\t\t -- Credential Rule: %d", user->mInfo.mFields.mCredentialRule); + LOG_INF("\t\t -- Creation Source: %d", user->mInfo.mFields.mCreationSource); + LOG_INF("\t\t -- Created By: %d", user->mInfo.mFields.mCreatedBy); + LOG_INF("\t\t -- Modification Source: %d", user->mInfo.mFields.mModificationSource); + LOG_INF("\t\t -- Modified By: %d", user->mInfo.mFields.mLastModifiedBy); + LOG_INF("\t\t -- Associated Credentials %d:", + user->mOccupiedCredentials.mSize / sizeof(CredentialStruct)); + for (uint8_t i = 0; i < user->mOccupiedCredentials.mSize / sizeof(CredentialStruct); i++) { + switch (user->mOccupiedCredentials.mData[i].credentialType) { + case CredentialTypeEnum::kPin: + LOG_INF("\t\t\t -- PIN | index: %d", + user->mOccupiedCredentials.mData[i].credentialIndex); + break; + case CredentialTypeEnum::kRfid: + LOG_INF("\t\t\t -- RFID | index: %d", + user->mOccupiedCredentials.mData[i].credentialIndex); + break; + case CredentialTypeEnum::kFingerprint: + case CredentialTypeEnum::kFingerVein: + LOG_INF("\t\t\t -- Fingerprint | index: %d", + user->mOccupiedCredentials.mData[i].credentialIndex); + break; + default: + break; + } + } + } +} + +#endif + +/* Explicitly instantiate supported template variants to avoid linker errors. */ +template class CredentialsManager; +template class CredentialsManager; +template class CredentialsManager; +template class CredentialsManager; +template class CredentialsManager; diff --git a/samples/matter/lock/src/credentials/credentials_storage.cpp b/samples/matter/lock/src/credentials/credentials_storage.cpp index 78f87053423a..b367b671c9fc 100644 --- a/samples/matter/lock/src/credentials/credentials_storage.cpp +++ b/samples/matter/lock/src/credentials/credentials_storage.cpp @@ -35,14 +35,17 @@ bool GetStorageFreeSpace(size_t &freeBytes) /* Currently the secure storage is available only for non-Wi-Fi builds, because NCS Wi-Fi implementation does not support PSA API yet. */ -#ifdef CONFIG_CHIP_WIFI +#if defined(CONFIG_NCS_SAMPLE_MATTER_SETTINGS_STORAGE_BACKEND) && !defined(NCS_SAMPLE_MATTER_SECURE_STORAGE_BACKEND) #define PSInit NonSecureInit #define PSStore NonSecureStore #define PSLoad NonSecureLoad -#else +#elif defined(CONFIG_NCS_SAMPLE_MATTER_SECURE_STORAGE_BACKEND) and \ + !defined(CONFIG_NCS_SAMPLE_MATTER_SETTINGS_STORAGE_BACKEND) #define PSInit SecureInit #define PSStore SecureStore #define PSLoad SecureLoad +#else +#error Please select only one of the possible credential backends: CONFIG_NCS_SAMPLE_MATTER_SECURE_STORAGE_BACKEND or CONFIG_NCS_SAMPLE_MATTER_SETTINGS_STORAGE_BACKEND #endif bool CredentialsStorage::Init() @@ -89,6 +92,32 @@ bool CredentialsStorage::PrepareKeyName(Type storageType, uint16_t index, uint16 case Type::RequirePIN: (void)snprintf(mKeyName, kMaxCredentialsName, "%s", kRequirePinPrefix); return true; +#ifdef CONFIG_LOCK_SCHEDULES + case Type::WeekDaySchedule: + (void)snprintf(mKeyName, kMaxCredentialsName, "%s/%s%s/%u/%u", kCredentialsPrefix, kSchedulePrefix, + kScheduleWeekDaySuffix, limitedIndex, limitedSubindex); + return true; + case Type::YearDaySchedule: + (void)snprintf(mKeyName, kMaxCredentialsName, "%s/%s%s/%u/%u", kCredentialsPrefix, kSchedulePrefix, + kScheduleYearDaySuffix, limitedIndex, limitedSubindex); + return true; + case Type::HolidaySchedule: + (void)snprintf(mKeyName, kMaxCredentialsName, "%s/%s%s/%u", kCredentialsPrefix, kSchedulePrefix, + kScheduleHolidaySuffix, limitedIndex); + return true; + case Type::WeekDayScheduleIndexes: + (void)snprintf(mKeyName, kMaxCredentialsName, "%s/%s%s/%u", kCredentialsPrefix, kScheduleCounterPrefix, + kScheduleWeekDaySuffix, limitedIndex); + return true; + case Type::YearDayScheduleIndexes: + (void)snprintf(mKeyName, kMaxCredentialsName, "%s/%s%s/%u", kCredentialsPrefix, kScheduleCounterPrefix, + kScheduleYearDaySuffix, limitedIndex); + return true; + case Type::HolidayScheduleIndexes: + (void)snprintf(mKeyName, kMaxCredentialsName, "%s/%s%s", kCredentialsPrefix, kScheduleCounterPrefix, + kScheduleHolidaySuffix); + return true; +#endif /* CONFIG_LOCK_SCHEDULES */ default: break; } diff --git a/samples/matter/lock/src/credentials/credentials_storage.h b/samples/matter/lock/src/credentials/credentials_storage.h index c8dfa7070a98..2a227ad9c402 100644 --- a/samples/matter/lock/src/credentials/credentials_storage.h +++ b/samples/matter/lock/src/credentials/credentials_storage.h @@ -21,18 +21,38 @@ * * /cr/ * // - * /cr_idxs (bytes)/ - * // - * / - * /usr_idxs (bytes)/ + * / cr_idxs (bytes) + * / / + * / + * /usr_idxs (bytes) * /usr/ - * // - * / / + * / / + * / + * /sch_/ + * /schedule_usr_idxs (bytes)/ + * / / + * / / + * / + * */ class CredentialsStorage { public: - enum class Type : uint8_t { User, Credential, UsersIndexes, CredentialsIndexes, RequirePIN }; + enum class Type : uint8_t { + User, + Credential, + UsersIndexes, + CredentialsIndexes, + RequirePIN, +#ifdef CONFIG_LOCK_SCHEDULES + WeekDaySchedule, + WeekDayScheduleIndexes, + YearDaySchedule, + YearDayScheduleIndexes, + HolidaySchedule, + HolidayScheduleIndexes +#endif /* CONFIG_LOCK_SCHEDULES */ + }; /** * @brief Initialize the persistent storage. @@ -81,6 +101,13 @@ class CredentialsStorage { constexpr static auto kUserPrefix = "usr"; constexpr static auto kUserCounterPrefix = "usr_idxs"; constexpr static auto kRequirePinPrefix = "pin_req"; +#ifdef CONFIG_LOCK_SCHEDULES + constexpr static auto kSchedulePrefix = "sch"; + constexpr static auto kScheduleWeekDaySuffix = "_w"; + constexpr static auto kScheduleYearDaySuffix = "_y"; + constexpr static auto kScheduleHolidaySuffix = "_h"; + constexpr static auto kScheduleCounterPrefix = "sch_idxs"; +#endif /* CONFIG_LOCK_SCHEDULES */ constexpr static auto kMaxCredentialsName = Nrf::PersistentStorageNode::kMaxKeyNameLength; char mKeyName[CredentialsStorage::kMaxCredentialsName]; diff --git a/samples/matter/lock/src/lock.zap b/samples/matter/lock/src/lock.zap index 603aa1c14779..1430429db65f 100644 --- a/samples/matter/lock/src/lock.zap +++ b/samples/matter/lock/src/lock.zap @@ -17,12 +17,6 @@ } ], "package": [ - { - "pathRelativity": "relativeToZap", - "path": "../../../../../modules/lib/matter/src/app/zap-templates/app-templates.json", - "type": "gen-templates-json", - "version": "chip-v1" - }, { "pathRelativity": "relativeToZap", "path": "../../../../../modules/lib/matter/src/app/zap-templates/zcl/zcl.json", @@ -30,6 +24,12 @@ "category": "matter", "version": 1, "description": "Matter SDK ZCL data" + }, + { + "pathRelativity": "relativeToZap", + "path": "../../../../../modules/lib/matter/src/app/zap-templates/app-templates.json", + "type": "gen-templates-json", + "version": "chip-v1" } ], "endpointTypes": [ @@ -1636,7 +1636,7 @@ "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -1652,7 +1652,7 @@ "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -1668,7 +1668,7 @@ "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -1684,7 +1684,7 @@ "storageOption": "External", "singleton": 0, "bounded": 0, - "defaultValue": "", + "defaultValue": null, "reportable": 1, "minInterval": 1, "maxInterval": 65534, @@ -4569,6 +4569,102 @@ "isIncoming": 1, "isEnabled": 1 }, + { + "name": "SetWeekDaySchedule", + "code": 11, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "GetWeekDaySchedule", + "code": 12, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "GetWeekDayScheduleResponse", + "code": 12, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + }, + { + "name": "ClearWeekDaySchedule", + "code": 13, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "SetYearDaySchedule", + "code": 14, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "GetYearDaySchedule", + "code": 15, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "GetYearDayScheduleResponse", + "code": 15, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + }, + { + "name": "ClearYearDaySchedule", + "code": 16, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "SetHolidaySchedule", + "code": 17, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "GetHolidaySchedule", + "code": 18, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, + { + "name": "GetHolidayScheduleResponse", + "code": 18, + "mfgCode": null, + "source": "server", + "isIncoming": 0, + "isEnabled": 1 + }, + { + "name": "ClearHolidaySchedule", + "code": 19, + "mfgCode": null, + "source": "client", + "isIncoming": 1, + "isEnabled": 1 + }, { "name": "SetUser", "code": 26, @@ -4723,6 +4819,54 @@ "maxInterval": 65534, "reportableChange": 0 }, + { + "name": "NumberOfWeekDaySchedulesSupportedPerUser", + "code": 20, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "5", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "NumberOfYearDaySchedulesSupportedPerUser", + "code": 21, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "5", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, + { + "name": "NumberOfHolidaySchedulesSupported", + "code": 22, + "mfgCode": null, + "side": "server", + "type": "int8u", + "included": 1, + "storageOption": "RAM", + "singleton": 0, + "bounded": 0, + "defaultValue": "5", + "reportable": 1, + "minInterval": 1, + "maxInterval": 65534, + "reportableChange": 0 + }, { "name": "MaxPINCodeLength", "code": 23, @@ -5005,7 +5149,7 @@ "storageOption": "RAM", "singleton": 0, "bounded": 0, - "defaultValue": "0x181", + "defaultValue": "0xD91", "reportable": 1, "minInterval": 1, "maxInterval": 65534, diff --git a/samples/matter/lock/src/zap-generated/IMClusterCommandHandler.cpp b/samples/matter/lock/src/zap-generated/IMClusterCommandHandler.cpp index f61dc9b24ca5..9407d939d7fd 100644 --- a/samples/matter/lock/src/zap-generated/IMClusterCommandHandler.cpp +++ b/samples/matter/lock/src/zap-generated/IMClusterCommandHandler.cpp @@ -187,6 +187,87 @@ namespace app } break; } + case Commands::SetWeekDaySchedule::Id: { + Commands::SetWeekDaySchedule::DecodableType commandData; + TLVError = DataModel::Decode(aDataTlv, commandData); + if (TLVError == CHIP_NO_ERROR) { + wasHandled = emberAfDoorLockClusterSetWeekDayScheduleCallback( + apCommandObj, aCommandPath, commandData); + } + break; + } + case Commands::GetWeekDaySchedule::Id: { + Commands::GetWeekDaySchedule::DecodableType commandData; + TLVError = DataModel::Decode(aDataTlv, commandData); + if (TLVError == CHIP_NO_ERROR) { + wasHandled = emberAfDoorLockClusterGetWeekDayScheduleCallback( + apCommandObj, aCommandPath, commandData); + } + break; + } + case Commands::ClearWeekDaySchedule::Id: { + Commands::ClearWeekDaySchedule::DecodableType commandData; + TLVError = DataModel::Decode(aDataTlv, commandData); + if (TLVError == CHIP_NO_ERROR) { + wasHandled = emberAfDoorLockClusterClearWeekDayScheduleCallback( + apCommandObj, aCommandPath, commandData); + } + break; + } + case Commands::SetYearDaySchedule::Id: { + Commands::SetYearDaySchedule::DecodableType commandData; + TLVError = DataModel::Decode(aDataTlv, commandData); + if (TLVError == CHIP_NO_ERROR) { + wasHandled = emberAfDoorLockClusterSetYearDayScheduleCallback( + apCommandObj, aCommandPath, commandData); + } + break; + } + case Commands::GetYearDaySchedule::Id: { + Commands::GetYearDaySchedule::DecodableType commandData; + TLVError = DataModel::Decode(aDataTlv, commandData); + if (TLVError == CHIP_NO_ERROR) { + wasHandled = emberAfDoorLockClusterGetYearDayScheduleCallback( + apCommandObj, aCommandPath, commandData); + } + break; + } + case Commands::ClearYearDaySchedule::Id: { + Commands::ClearYearDaySchedule::DecodableType commandData; + TLVError = DataModel::Decode(aDataTlv, commandData); + if (TLVError == CHIP_NO_ERROR) { + wasHandled = emberAfDoorLockClusterClearYearDayScheduleCallback( + apCommandObj, aCommandPath, commandData); + } + break; + } + case Commands::SetHolidaySchedule::Id: { + Commands::SetHolidaySchedule::DecodableType commandData; + TLVError = DataModel::Decode(aDataTlv, commandData); + if (TLVError == CHIP_NO_ERROR) { + wasHandled = emberAfDoorLockClusterSetHolidayScheduleCallback( + apCommandObj, aCommandPath, commandData); + } + break; + } + case Commands::GetHolidaySchedule::Id: { + Commands::GetHolidaySchedule::DecodableType commandData; + TLVError = DataModel::Decode(aDataTlv, commandData); + if (TLVError == CHIP_NO_ERROR) { + wasHandled = emberAfDoorLockClusterGetHolidayScheduleCallback( + apCommandObj, aCommandPath, commandData); + } + break; + } + case Commands::ClearHolidaySchedule::Id: { + Commands::ClearHolidaySchedule::DecodableType commandData; + TLVError = DataModel::Decode(aDataTlv, commandData); + if (TLVError == CHIP_NO_ERROR) { + wasHandled = emberAfDoorLockClusterClearHolidayScheduleCallback( + apCommandObj, aCommandPath, commandData); + } + break; + } case Commands::SetUser::Id: { Commands::SetUser::DecodableType commandData; TLVError = DataModel::Decode(aDataTlv, commandData); diff --git a/samples/matter/lock/src/zap-generated/access.h b/samples/matter/lock/src/zap-generated/access.h index 2fb8ad2e3814..4cca078c43f7 100644 --- a/samples/matter/lock/src/zap-generated/access.h +++ b/samples/matter/lock/src/zap-generated/access.h @@ -213,6 +213,15 @@ 0x0000003F, /* Cluster: Group Key Management, Command: KeySetRead, Privilege: administer */ \ 0x0000003F, /* Cluster: Group Key Management, Command: KeySetRemove, Privilege: administer */ \ 0x0000003F, /* Cluster: Group Key Management, Command: KeySetReadAllIndices, Privilege: administer */ \ + 0x00000101, /* Cluster: Door Lock, Command: SetWeekDaySchedule, Privilege: administer */ \ + 0x00000101, /* Cluster: Door Lock, Command: GetWeekDaySchedule, Privilege: administer */ \ + 0x00000101, /* Cluster: Door Lock, Command: ClearWeekDaySchedule, Privilege: administer */ \ + 0x00000101, /* Cluster: Door Lock, Command: SetYearDaySchedule, Privilege: administer */ \ + 0x00000101, /* Cluster: Door Lock, Command: GetYearDaySchedule, Privilege: administer */ \ + 0x00000101, /* Cluster: Door Lock, Command: ClearYearDaySchedule, Privilege: administer */ \ + 0x00000101, /* Cluster: Door Lock, Command: SetHolidaySchedule, Privilege: administer */ \ + 0x00000101, /* Cluster: Door Lock, Command: GetHolidaySchedule, Privilege: administer */ \ + 0x00000101, /* Cluster: Door Lock, Command: ClearHolidaySchedule, Privilege: administer */ \ 0x00000101, /* Cluster: Door Lock, Command: SetUser, Privilege: administer */ \ 0x00000101, /* Cluster: Door Lock, Command: GetUser, Privilege: administer */ \ 0x00000101, /* Cluster: Door Lock, Command: ClearUser, Privilege: administer */ \ @@ -250,6 +259,15 @@ 0x00000001, /* Cluster: Group Key Management, Command: KeySetRead, Privilege: administer */ \ 0x00000003, /* Cluster: Group Key Management, Command: KeySetRemove, Privilege: administer */ \ 0x00000004, /* Cluster: Group Key Management, Command: KeySetReadAllIndices, Privilege: administer */ \ + 0x0000000B, /* Cluster: Door Lock, Command: SetWeekDaySchedule, Privilege: administer */ \ + 0x0000000C, /* Cluster: Door Lock, Command: GetWeekDaySchedule, Privilege: administer */ \ + 0x0000000D, /* Cluster: Door Lock, Command: ClearWeekDaySchedule, Privilege: administer */ \ + 0x0000000E, /* Cluster: Door Lock, Command: SetYearDaySchedule, Privilege: administer */ \ + 0x0000000F, /* Cluster: Door Lock, Command: GetYearDaySchedule, Privilege: administer */ \ + 0x00000010, /* Cluster: Door Lock, Command: ClearYearDaySchedule, Privilege: administer */ \ + 0x00000011, /* Cluster: Door Lock, Command: SetHolidaySchedule, Privilege: administer */ \ + 0x00000012, /* Cluster: Door Lock, Command: GetHolidaySchedule, Privilege: administer */ \ + 0x00000013, /* Cluster: Door Lock, Command: ClearHolidaySchedule, Privilege: administer */ \ 0x0000001A, /* Cluster: Door Lock, Command: SetUser, Privilege: administer */ \ 0x0000001B, /* Cluster: Door Lock, Command: GetUser, Privilege: administer */ \ 0x0000001D, /* Cluster: Door Lock, Command: ClearUser, Privilege: administer */ \ @@ -287,6 +305,15 @@ chip::Access::Privilege::kAdminister, /* Cluster: Group Key Management, Command: KeySetRead, Privilege: administer */ \ chip::Access::Privilege::kAdminister, /* Cluster: Group Key Management, Command: KeySetRemove, Privilege: administer */ \ chip::Access::Privilege::kAdminister, /* Cluster: Group Key Management, Command: KeySetReadAllIndices, Privilege: administer */ \ + chip::Access::Privilege::kAdminister, /* Cluster: Door Lock, Command: SetWeekDaySchedule, Privilege: administer */ \ + chip::Access::Privilege::kAdminister, /* Cluster: Door Lock, Command: GetWeekDaySchedule, Privilege: administer */ \ + chip::Access::Privilege::kAdminister, /* Cluster: Door Lock, Command: ClearWeekDaySchedule, Privilege: administer */ \ + chip::Access::Privilege::kAdminister, /* Cluster: Door Lock, Command: SetYearDaySchedule, Privilege: administer */ \ + chip::Access::Privilege::kAdminister, /* Cluster: Door Lock, Command: GetYearDaySchedule, Privilege: administer */ \ + chip::Access::Privilege::kAdminister, /* Cluster: Door Lock, Command: ClearYearDaySchedule, Privilege: administer */ \ + chip::Access::Privilege::kAdminister, /* Cluster: Door Lock, Command: SetHolidaySchedule, Privilege: administer */ \ + chip::Access::Privilege::kAdminister, /* Cluster: Door Lock, Command: GetHolidaySchedule, Privilege: administer */ \ + chip::Access::Privilege::kAdminister, /* Cluster: Door Lock, Command: ClearHolidaySchedule, Privilege: administer */ \ chip::Access::Privilege::kAdminister, /* Cluster: Door Lock, Command: SetUser, Privilege: administer */ \ chip::Access::Privilege::kAdminister, /* Cluster: Door Lock, Command: GetUser, Privilege: administer */ \ chip::Access::Privilege::kAdminister, /* Cluster: Door Lock, Command: ClearUser, Privilege: administer */ \ diff --git a/samples/matter/lock/src/zap-generated/endpoint_config.h b/samples/matter/lock/src/zap-generated/endpoint_config.h index 3f68d14352a0..781a3297f718 100644 --- a/samples/matter/lock/src/zap-generated/endpoint_config.h +++ b/samples/matter/lock/src/zap-generated/endpoint_config.h @@ -72,7 +72,7 @@ } // This is an array of EmberAfAttributeMetadata structures. -#define GENERATED_ATTRIBUTE_COUNT 204 +#define GENERATED_ATTRIBUTE_COUNT 207 #define GENERATED_ATTRIBUTES \ { \ /* Endpoint: 0, Cluster: Descriptor (server) */ \ @@ -480,6 +480,12 @@ */ \ { ZAP_SIMPLE_DEFAULT(10), 0x00000012, 2, ZAP_TYPE(INT16U), 0 }, /* NumberOfPINUsersSupported \ */ \ + { ZAP_SIMPLE_DEFAULT(5), 0x00000014, 1, ZAP_TYPE(INT8U), 0 }, /* NumberOfWeekDaySchedulesSupportedPerUser \ + */ \ + { ZAP_SIMPLE_DEFAULT(5), 0x00000015, 1, ZAP_TYPE(INT8U), 0 }, /* NumberOfYearDaySchedulesSupportedPerUser \ + */ \ + { ZAP_SIMPLE_DEFAULT(5), 0x00000016, 1, ZAP_TYPE(INT8U), 0 }, /* NumberOfHolidaySchedulesSupported \ + */ \ { ZAP_SIMPLE_DEFAULT(8), 0x00000017, 1, ZAP_TYPE(INT8U), 0 }, /* MaxPINCodeLength */ \ { ZAP_SIMPLE_DEFAULT(6), 0x00000018, 1, ZAP_TYPE(INT8U), 0 }, /* MinPINCodeLength */ \ { ZAP_SIMPLE_DEFAULT(1), 0x0000001B, 1, ZAP_TYPE(BITMAP8), 0 }, /* CredentialRulesSupport */ \ @@ -506,7 +512,7 @@ */ \ { ZAP_SIMPLE_DEFAULT(0), 0x00000033, 1, ZAP_TYPE(BOOLEAN), \ ZAP_ATTRIBUTE_MASK(WRITABLE) }, /* RequirePINforRemoteOperation */ \ - { ZAP_SIMPLE_DEFAULT(0x181), 0x0000FFFC, 4, ZAP_TYPE(BITMAP32), 0 }, /* FeatureMap */ \ + { ZAP_SIMPLE_DEFAULT(0xD91), 0x0000FFFC, 4, ZAP_TYPE(BITMAP32), 0 }, /* FeatureMap */ \ { ZAP_SIMPLE_DEFAULT(7), 0x0000FFFD, 2, ZAP_TYPE(INT16U), 0 }, /* ClusterRevision */ \ } @@ -632,6 +638,15 @@ 0x00000000 /* LockDoor */, \ 0x00000001 /* UnlockDoor */, \ 0x00000003 /* UnlockWithTimeout */, \ + 0x0000000B /* SetWeekDaySchedule */, \ + 0x0000000C /* GetWeekDaySchedule */, \ + 0x0000000D /* ClearWeekDaySchedule */, \ + 0x0000000E /* SetYearDaySchedule */, \ + 0x0000000F /* GetYearDaySchedule */, \ + 0x00000010 /* ClearYearDaySchedule */, \ + 0x00000011 /* SetHolidaySchedule */, \ + 0x00000012 /* GetHolidaySchedule */, \ + 0x00000013 /* ClearHolidaySchedule */, \ 0x0000001A /* SetUser */, \ 0x0000001B /* GetUser */, \ 0x0000001D /* ClearUser */, \ @@ -639,7 +654,10 @@ 0x00000024 /* GetCredentialStatus */, \ 0x00000026 /* ClearCredential */, \ chip::kInvalidCommandId /* end of list */, \ - /* GeneratedCommandList (index=70)*/ \ + /* GeneratedCommandList (index=79)*/ \ + 0x0000000C /* GetWeekDayScheduleResponse */, \ + 0x0000000F /* GetYearDayScheduleResponse */, \ + 0x00000012 /* GetHolidayScheduleResponse */, \ 0x0000001C /* GetUserResponse */, \ 0x00000023 /* SetCredentialResponse */, \ 0x00000025 /* GetCredentialStatusResponse */, \ @@ -890,12 +908,12 @@ /* Endpoint: 1, Cluster: Door Lock (server) */ \ .clusterId = 0x00000101, \ .attributes = ZAP_ATTRIBUTE_INDEX(183), \ - .attributeCount = 21, \ - .clusterSize = 34, \ + .attributeCount = 24, \ + .clusterSize = 37, \ .mask = ZAP_CLUSTER_MASK(SERVER) | ZAP_CLUSTER_MASK(ATTRIBUTE_CHANGED_FUNCTION) | ZAP_CLUSTER_MASK(SHUTDOWN_FUNCTION) | ZAP_CLUSTER_MASK(PRE_ATTRIBUTE_CHANGED_FUNCTION), \ .functions = chipFuncArrayDoorLockServer, \ .acceptedCommandList = ZAP_GENERATED_COMMANDS_INDEX( 60 ), \ - .generatedCommandList = ZAP_GENERATED_COMMANDS_INDEX( 70 ), \ + .generatedCommandList = ZAP_GENERATED_COMMANDS_INDEX( 79 ), \ .eventList = nullptr, \ .eventCount = 0, \ },\ @@ -908,7 +926,7 @@ // This is an array of EmberAfEndpointType structures. #define GENERATED_ENDPOINT_TYPES \ { \ - { ZAP_CLUSTER_INDEX(0), 16, 110 }, { ZAP_CLUSTER_INDEX(16), 3, 43 }, \ + { ZAP_CLUSTER_INDEX(0), 16, 110 }, { ZAP_CLUSTER_INDEX(16), 3, 46 }, \ } // Largest attribute size is needed for various buffers @@ -921,7 +939,7 @@ static_assert(ATTRIBUTE_LARGEST <= CHIP_CONFIG_MAX_ATTRIBUTE_STORE_ELEMENT_SIZE, #define ATTRIBUTE_SINGLETONS_SIZE (35) // Total size of attribute storage -#define ATTRIBUTE_MAX_SIZE (153) +#define ATTRIBUTE_MAX_SIZE (156) // Number of fixed endpoints #define FIXED_ENDPOINT_COUNT (2) diff --git a/samples/matter/lock/src/zcl_callbacks.cpp b/samples/matter/lock/src/zcl_callbacks.cpp index e2c6212c7f66..10336b74a069 100644 --- a/samples/matter/lock/src/zcl_callbacks.cpp +++ b/samples/matter/lock/src/zcl_callbacks.cpp @@ -122,6 +122,61 @@ void emberAfDoorLockClusterInitCallback(EndpointId endpoint) logOnFailure(DoorLock::Attributes::RequirePINforRemoteOperation::Set(endpoint, BoltLockMgr().GetRequirePIN()), "require PIN code for the remote operation"); +#ifdef CONFIG_LOCK_SCHEDULES + logOnFailure(DoorLock::Attributes::NumberOfWeekDaySchedulesSupportedPerUser::Set( + endpoint, CONFIG_LOCK_MAX_WEEKDAY_SCHEDULES_PER_USER), + "number of WeekDay schedules per user"); + logOnFailure(DoorLock::Attributes::NumberOfYearDaySchedulesSupportedPerUser::Set( + endpoint, CONFIG_LOCK_MAX_YEARDAY_SCHEDULES_PER_USER), + "number of YearDay schedules per user"); + logOnFailure(DoorLock::Attributes::NumberOfHolidaySchedulesSupported::Set(endpoint, + CONFIG_LOCK_MAX_HOLIDAY_SCHEDULES), + "number of holiday schedules"); +#endif /* CONFIG_LOCK_SCHEDULES */ + AppTask::Instance().UpdateClusterState(BoltLockMgr().GetState(), BoltLockManager::OperationSource::kUnspecified); } + +#ifdef CONFIG_LOCK_SCHEDULES + +DlStatus emberAfPluginDoorLockGetSchedule(chip::EndpointId endpointId, uint8_t weekdayIndex, uint16_t userIndex, + EmberAfPluginDoorLockWeekDaySchedule &schedule) +{ + return BoltLockMgr().GetWeekDaySchedule(weekdayIndex, userIndex, schedule); +} + +DlStatus emberAfPluginDoorLockGetSchedule(chip::EndpointId endpointId, uint8_t yearDayIndex, uint16_t userIndex, + EmberAfPluginDoorLockYearDaySchedule &schedule) +{ + return BoltLockMgr().GetYearDaySchedule(yearDayIndex, userIndex, schedule); +} + +DlStatus emberAfPluginDoorLockGetSchedule(chip::EndpointId endpointId, uint8_t holidayIndex, + EmberAfPluginDoorLockHolidaySchedule &holidaySchedule) +{ + return BoltLockMgr().GetHolidaySchedule(holidayIndex, holidaySchedule); +} + +DlStatus emberAfPluginDoorLockSetSchedule(chip::EndpointId endpointId, uint8_t weekdayIndex, uint16_t userIndex, + DlScheduleStatus status, DaysMaskMap daysMask, uint8_t startHour, + uint8_t startMinute, uint8_t endHour, uint8_t endMinute) +{ + return BoltLockMgr().SetWeekDaySchedule(weekdayIndex, userIndex, status, daysMask, startHour, startMinute, + endHour, endMinute); +} + +DlStatus emberAfPluginDoorLockSetSchedule(chip::EndpointId endpointId, uint8_t yearDayIndex, uint16_t userIndex, + DlScheduleStatus status, uint32_t localStartTime, uint32_t localEndTime) +{ + return BoltLockMgr().SetYearDaySchedule(yearDayIndex, userIndex, status, localStartTime, localEndTime); +} + +DlStatus emberAfPluginDoorLockSetSchedule(chip::EndpointId endpointId, uint8_t holidayIndex, DlScheduleStatus status, + uint32_t localStartTime, uint32_t localEndTime, + OperatingModeEnum operatingMode) +{ + return BoltLockMgr().SetHolidaySchedule(holidayIndex, status, localStartTime, localEndTime, operatingMode); +} + +#endif /* CONFIG_LOCK_SCHEDULES */